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

Ran*_*and 3 javascript proxy dictionary google-chrome

我通过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();
Run Code Online (Sandbox Code Playgroud)
NOTE: Requires a browser supporting ES2015's Proxy
Run Code Online (Sandbox Code Playgroud)

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

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

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

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

T.J*_*der 8

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

如果你真的试图拦截所有的属性访问调用Map,你可以通过绑定你返回的任何函数引用来修复它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();
Run Code Online (Sandbox Code Playgroud)
NOTE: Requires a browser supporting ES2015's Proxy
Run Code Online (Sandbox Code Playgroud)

如果你的目标仅仅是拦截Reflect.getReflect.set,虽然,你并不需要一个代理在所有.或者:

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

  2. 创建一个从size实例继承的新对象,并覆盖thisMap; 你不必控制原作size的创作.

  3. 用您自己的版本替换实例上的Map#getMap#set方法Map.

这是#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"));
Run Code Online (Sandbox Code Playgroud)

#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"));
Run Code Online (Sandbox Code Playgroud)

#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"));
Run Code Online (Sandbox Code Playgroud)


Pra*_*iya 6

让我补充一点。

许多内置对象,例如MapSetDatePromise和其他人利用所谓的内部插槽

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

例如:

let map = new Map();

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

proxy.set('name', 'Pravin'); // Error
Run Code Online (Sandbox Code Playgroud)

在内部,aMap将所有数据存储在其[[MapData]]内部插槽中。该代理没有这样的插槽。内置方法Map.prototype.setmethod 尝试访问内部属性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!)
Run Code Online (Sandbox Code Playgroud)

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

  • “内部插槽”是一个实现细节。它们不是对象属性。JavaScript 程序员不需要了解内部槽,但对于解释一些 JavaScript 行为非常有用。希望这会有所帮助。 (2认同)