Proxy
在代理普通对象的时候,只需要使用set
和get
两个拦截函数就可以实现一些效果。
思考一下,我们在调用Map
或Set
对象的时候,只会用到get
拦截,如map.get('key')
、map.set('key','value)
、map.has('key')
,实际传入的参数都是在Map
对象的方法里接收的,想要达到拦截的目的,实际需要操作的是Map
对象的get()
、set()
这些方法。
函数拦截
我们需要对Map
或Set
对象的方法进行拦截,在Proxy
中的get
拦截时,我们可以获取到调用的函数名。
// 自定义的拦截器,在这里对调用的Map方法进行拦截
const interceptors = {
get(key) {
console.log(key);
},
set(key, value) {
console.log(key, value);
},
};
// 创建代理
const map = new Proxy(new Map(), {
get(target, key, receiver) {
// 如果调用的key存在于我们自定义的拦截器里,就用我们的拦截器
target = interceptors.hasOwnProperty(key) ? interceptors : target;
return Reflect.get(target, key, receiver);
},
});
map.set("key", "value"); // 输出 key value
map.get("key"); // 输出 key
根据输出可以看出,调用map
的set
、get
方法都调用了我们自己的函数,下一步就需要在自己的函数里,去调用map
对应的函数来实现拦截的功能且不影响原有功能。
如何在interceptors
拦截器里获取当前调用的对象呢?Reflect.get
的第三个参数,即是在调用时的this
,所以我们在interceptors
中访问的this
指向receiver
的值为经过Proxy
后的map
对象。
// 自定义的拦截器,在这里对调用的Map方法进行拦截
const interceptors = {
get(key) {
return this.get(key);
},
// ...
};
// ...
map.get("key");
然而如果直接在调用this.get
是不行的,this
指向Proxy
后的对象,再通过此对象访问又会进入我们自定义的拦截器,就死循环了,所以我们需要在自定义对象中,通过this
这个Proxy
后的对象来找到原始的对象。
所以我们需要在创建Proxy
对象时,把原始对象和Proxy
对象之间建立一个映射,这样就可以通过Proxy
对象找到原始对象了。
改写一下代码:
// 存储代理对象和原始对象的映射
const proxyToRaw = new WeakMap();
// 自定义的拦截器,在这里对调用的Map方法进行拦截
const interceptors = {
get(key) {
// 通过代理对象获取原始对象 (proxy => map)
const rawTarget = proxyToRaw.get(this);
return rawTarget.get(key);
},
set(key, value) {
const rawTarget = proxyToRaw.get(this);
return rawTarget.set(key, value);
},
};
// 创建Proxy对象,同时将代理对象和原始对象建立一个映射
const createProxy = (obj) => {
const proxy = new Proxy(obj, {
get(target, key, receiver) {
// 如果调用的key存在于我们自定义的拦截器里,就用我们的拦截器
target = interceptors.hasOwnProperty(key) ? interceptors : target;
return Reflect.get(target, key, receiver);
},
});
// 让proxy后的对象指向原始对象
proxyToRaw.set(proxy, obj);
return proxy;
};
const map = createProxy(new Map());
map.set("key", "value");
map.get("key"); // 输出value
通过代理对象到原始对象的映射,我们可以通过this
来找到传入的原始对象,再调用原始对象上的方法达到正确的效果,get
和set
的代理就完成了。
其他方法还有Set
对象实现的方法也是同理。
this问题
const proxy = new Proxy(new Map(), {
get(target, key, receiver) {
return Reflect.get(target, key, receiver);
},
});
proxy.get("key"); // TypeError: Method Map.prototype.get called on incompatible receiver
在什么操作都没有做的时候,直接返回Reflect.get(target, key, receiver)
,会出现这样一个错误,因为Map
的方法都在原型链Map.prototype
上,而值存在原对象内部槽[[MapData]]
上,而在通过代理后的Proxy
对象上没有[[MapData]]
,所以报错了,要解决这个问题,还是得修改this
指向。
const proxy = new Proxy(new Map(), {
get(target, key, receiver) {
return Reflect.get(target, key, receiver).bind(target); // bind this
},
});
这个问题是我的个人见解,可能不一定对,欢迎讨论。
本文参考: