为什么 ES2015 中映射对象的代理不起作用



我正在通过谷歌浏览器版本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中删除陷阱函数(使其成为空对象),但仍然收到相同的错误。

我的理解是,代理对象应该能够为所有本机 ES5 和 ES2015 JavaScript 对象生成。 数组似乎在同一个代理处理程序下运行良好。 我误解了规格吗?
我的代码是否缺少某些内容? Chrome 中是否存在已知错误?(我做了一个搜索,发现Chrome在这个主题上没有缺陷。

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

如果您真的试图拦截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();
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"));

让我补充更多。

许多内置对象,例如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)绑定到目标对象(map)本身。因此,这个内部proxy.set(...)的值将不是代理,而是原始映射。因此,当set的内部实现尝试访问内部插槽this.[[MapData]]它就会成功。

最新更新