javascript - 为什么 ES2015 中 Map 对象的代理不起作用

标签 javascript google-chrome dictionary proxy

我正在通过 Google Chrome 版本 57.0.2987.133 运行以下脚本:

var loggingProxyHandler = {
    "get" : function(targetObj, propName, receiverProxy) {
        let ret = Reflect.get(targetObj, propName, receiverProxy);
        console.log("get("+propName.toString()+"="+ret+")");
        return ret;
     },

     "set" : function(targetObj, propName, propValue, receiverProxy) {
         console.log("set("+propName.toString()+"="+propValue+")");
         return Reflect.set(targetObj, propName, propValue, receiverProxy);
     }
};

function onRunTest()
{
    let m1 = new Map();
    let p1 = new Proxy(m1, loggingProxyHandler);
    p1.set("a", "aval");   // Exception thrown from here
}

onRunTest();
NOTE: Requires a browser supporting ES2015's Proxy

运行时,我看到处理程序的 get 陷阱被调用以返回 Map 的 set 函数 然后我收到以下错误:

"Uncaught TypeError: Method Map.prototype.set called on incompatible receiver [object Object]"
at Proxy.set (native)
...

我尝试从 loggingProxyHandler 中删除陷阱函数(使其成为一个空对象),但仍然收到相同的错误。

我的理解是 Proxy 对象应该能够为所有原生 ES5 和 ES2015 javascript 对象生成。数组似乎在同一个代理处理程序下运行良好。 我是不是误解了规范?
我的代码缺少什么吗? Chrome 中是否存在已知错误? (我进行了搜索,发现 Chrome 在这个问题上没有任何缺陷。)

最佳答案

您收到错误的原因是代理没有参与 p1.set() 方法调用(除了 get 陷阱用于检索函数引用)。因此,一旦检索到函数引用,就会调用它,并将 this 设置为代理 p1,而不是映射 m1 Map 的 set 方法不喜欢。

如果您真的想拦截 Map 上的所有属性访问调用,您可以通过绑定(bind)从 get 返回的任何函数引用来修复它(请参阅*** 行):

const loggingProxyHandler = {
    get(target, name/*, receiver*/) {
        let ret = Reflect.get(target, name);
        console.log(`get(${name}=${ret})`);
        if (typeof ret === "function") {    // ***
          ret = ret.bind(target);           // ***
        }                                   // ***
        return ret;
     },

     set(target, name, value/*, receiver*/) {
         console.log(`set(${name}=${value})`);
         return Reflect.set(target, name, value);
     }
};

function onRunTest() {
    const m1 = new Map();
    const p1 = new Proxy(m1, loggingProxyHandler);
    p1.set("a", "aval");
    console.log(p1.get("a")); // "aval"
    console.log(p1.size);     // 1
}

onRunTest();
NOTE: Requires a browser supporting ES2015's Proxy

请注意,在调用 Reflect.getReflect.set 时,我们不会传递接收器(事实上,我们没有使用 receiver 参数,所以我已经注释掉了参数)。这意味着他们将使用目标本身作为接收者,如果属性是访问器(如 Mapsize 属性)并且他们需要他们的 this 成为实际实例(如 Mapsize 所做的那样)。


如果您的目标只是拦截 Map#getMap#set,那么您根本不需要代理。要么:

  1. 创建一个 Map 子类并实例化它。不过,假设您控制 Map 实例的创建。

  2. 创建一个继承自Map实例的新对象,并覆盖getset;您不必控制原始 Map 的创建。

  3. 用您自己的版本替换 Map 实例上的 setget 方法。

这是#1:

class MyMap extends Map {
  set(...args) {
    console.log("set called");
    return super.set(...args);
  }
  get(...args) {
    console.log("get called");
    return super.get(...args);
  }
}

const m1 = new MyMap();
m1.set("a", "aval");
console.log(m1.get("a"));

#2:

const m1 = new Map();
const p1 = Object.create(m1, {
  set: {
    value: function(...args) {
      console.log("set called");
      return m1.set(...args);
    }
  },
  get: {
    value: function(...args) {
      console.log("get called");
      return m1.get(...args);
    }
  }
});

p1.set("a", "aval");
console.log(p1.get("a"));

#3:

const m1 = new Map();
const m1set = m1.set; // Yes, we know these are `Map.prototype.set` and
const m1get = m1.get; // `get`, but in the generic case, we don't necessarily
m1.set = function(...args) {
  console.log("set called");
  return m1set.apply(m1, args);
};
m1.get = function(...args) {
  console.log("get called");
  return m1get.apply(m1, args);
}

m1.set("a", "aval");
console.log(m1.get("a"));

关于javascript - 为什么 ES2015 中 Map 对象的代理不起作用,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/43236329/

相关文章:

javascript - 如何为 Meteor 事件创建另一个输入范围

javascript - 转换/解析 javascript 对象键值对

silverlight - 关闭 Chrome 中的 Silverlight 插件警告

python - 如果键和值是元组,如何获取字典中的键和值

c# - 为什么 Dictionary.ContainsKey() 和 ToString() 会导致 GC Alloc?

带有@property的Python对象来听写

javascript - 选择并更改 ReactJS 中的特定元素

javascript - 浏览器内音频播放器如何工作?

asp.net - 页面在谷歌浏览器中加载两次

javascript - 按 ESC 退出全屏模式消息