为什么ES2015中的Map对象的Proxy无法正常工作

时间:2017-04-05 16:05:49

标签: javascript google-chrome dictionary proxy

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

#header{
  position: absolute;
  z-index: 1000;
  top: 200px;
  left: 200px;
}
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();

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

NOTE: Requires a browser supporting ES2015's Proxy

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

我的理解是,应该能够为所有本机ES5和ES2015 javascript对象生成Proxy对象。数组似乎在相同的代理处理程序下运行良好。 我误解了规格吗? 我的代码遗漏了什么吗? Chrome中是否存在已知错误? (我进行了搜索,发现Chrome没有关于此主题的任何缺陷。)

2 个答案:

答案 0 :(得分:6)

您收到错误的原因是代理没有参与p1.set调用(除set陷阱之外 - 无关,尽管名称相同 - 正在调用检索函数引用)。因此,一旦检索到函数引用,就会调用它,this设置为代理,而不是Map - Map不喜欢。

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

var loggingProxyHandler = {
    "get" : function(targetObj, propName, receiverProxy) {
        let ret = Reflect.get(targetObj, propName, receiverProxy);
        console.log("get("+propName.toString()+"="+ret+")");
        if (typeof ret === "function") { // ***
          ret = ret.bind(targetObj);     // ***
        }                                // ***
        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");
    console.log(p1.get("a")); // "aval"
}

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

如果您的目标只是截取Map#getMap#set,则根本不需要代理。之一:

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

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

  3. set实例上的getMap方法替换为您自己的版本。

  4. 这是#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"));

答案 1 :(得分:1)

让我添加更多内容

许多内置对象,例如MapSetDatePromise,其他内置对象则使用所谓的内部插槽 >。

这些类似于属性,但保留给内部使用,仅用于规范目的。例如,Map将项目存储在内部插槽[[MapData]]中。内置方法可以直接访问它们,而无需通过[[Get]]/[[Set]]内部方法。因此Proxy无法拦截。

例如:

let map = new Map();

let proxy = new Proxy(map, {});

proxy.set('name', 'Pravin'); // Error

在内部,Map将所有数据存储在其[[MapData]]内部插槽中。 代理没有这样的插槽。内置方法Map.prototype.set尝试访问内部属性this.[[MapData]],但是由于 this = proxy ,无法在代理中找到它,只是失败了。

有一种解决方法:

let map = new Map();
let proxy = new Proxy(map,{
    get(target,prop,receiver){
        let value = Reflect.get(...arguments);
        return typeof value === 'function'?value.bind(target):value;
    }
});
proxy.set('name','Pravin');
console.log(proxy.get('name')); //Pravin (works!)

现在它可以正常工作,因为get陷阱将函数属性(例如map.set)绑定到目标对象(地图)本身。因此,proxy.set(...)中的this值将不是 proxy ,而是原始的 map 。因此,当set的内部实现尝试访问this.[[MapData]]内部插槽时,它将成功。