使用代理观察对Map的更改

时间:2018-01-25 22:23:46

标签: javascript proxy ecmascript-6

我使用Map javascript对象。我想观察使用Proxy对象对Map实例的更改。但是,无论我尝试输入代理的任何处理程序对象,我都会一直收到错误Method Map.prototype.set called on incompatible receiver。我究竟做错了什么?我希望工作的惯用例看起来像

var map = new Map();
var proxy = new Proxy(map, {
    set(obj, prop, value) {
        console.log(value);
    }
});
proxy.set(1, 1); --> error

我也尝试为apply设置处理程序,但无济于事。

1 个答案:

答案 0 :(得分:1)

首先,请了解您的错误也会仅使用

重现
var map = new Map();
var proxy = new Proxy(map, {});
proxy.set(1, 1);

set(obj, prop, value)的使用无关。

为什么失败

要进一步打破这一点,请理解这与执行

基本相同
var map = new Map();
var proxy = new Proxy(map, {});
Map.prototype.set.call(proxy, 1, 1);

其中也有错误。您正在为set个实例调用Map函数,但是传递Proxy而不是Map个实例。

这就是问题的核心。 Map使用专门与map对象本身关联的私有内部插槽存储其数据。代理不会100%透明地表现。它们允许您拦截对象上的某组操作,并在它们发生时执行逻辑,这通常意味着将该逻辑代理到某个其他对象,在您的情况下从proxymap。 / p>

不幸的是,对于您的情况,代理访问Map实例的私有内部插槽不是可以拦截的行为之一。你可以想象它像

var PRIVATE = new WeakMap();
var obj = {};

PRIVATE.set(obj, "private stuff");

var proxy = new Proxy(obj, {});

PRIVATE.get(proxy) === undefined // true
PRIVATE.get(obj) === "private stuff" // true

因为对象作为this传递给Map.prototype.set不是真正的Map,它无法找到所需的数据并会抛出异常。

解决方案

此处的解决方案意味着您确实需要将正确的this传递给Map.prototype.set

对于代理,最简单的方法是实际拦截对.set的访问,例如

var map = new Map();

var proxy = new Proxy(map, {
  get(target, prop, receiver) {
    // Perform the normal non-proxied behavior.
    var value = Reflect.get(target, prop, receiver);

    // If something is accessing the property `proxy.set`, override it
    // to automatically do `proxy.set.bind(map)` so that when the
    // function is called `this` will be `map` instead of `proxy`.
    if (prop === "set" && typeof value === "function") value = value.bind(target);

    return value;
  }
});

proxy.set(1, 1); 

当然,这并未解决您关于拦截对.set的实际调用的问题,因此您可以对此进行扩展以进行此操作

var map = new Map();

var proxy = new Proxy(map, {
  get(target, prop, receiver) {
    var value = Reflect.get(target, prop, receiver);
    if (prop === "set" && typeof value === "function") {
      // When `proxy.set` is accessed, return your own
      // fake implementation that logs the arguments, then
      // calls the original .set() behavior.
      const origSet = value;
      value = function(key, value) {
        console.log(key, value);

        return origSet.apply(map, arguments);
      };
    }
    return value;
  }
});

proxy.set(1, 1);