Proxy
通过Proxy,我们可以创建一个对象的代理对象,不过在日常使用中还是需要注意一些细节
内置插槽 & 内部方法
在JS中,内部插槽和内部方法用于描述对象内部状态和行为的概念。他只是一种规范。不同引擎的具体实现存在着一些不同,但都应该符合这套规范。
一些对象存在着一些奇特的内部属性和方法,用于在该对象上记录特定的状态和行为。并且仅供于对象内部访问,不对外暴露(可以在控制台打印对象查看)。这类对象和方法通常以[[]]
这样的形式来表示。对象可以拥有多个内部插槽实现和内部方法实现(以下简称内部方法和内部插槽)
例如
[[GetPrototypeOf]]
这个内部方法被所有对象所实现,并返回其的prototype
,这个prototype
其实也是包裹在一个内部插槽属性[[Prototype]]
中,当我们调用Object.getPrototypeOf
方法时,JS引擎就会调用[[GetPrototypeOf]]
内部方法
当一个对象的内部方法被调用,我们称这个对象为调用的"target"
如果对一个没有实现内部方法[[GetPrototypeOf]]
的target调用他的getPrototypeOf,那么会抛出TypeError
为什么在Proxy中返回一个fn,需要bind?
因为Proxy存在一定的局限性,对于一些JS中的内置对象,比如Map,Promise等等,这些都使用了内部插槽。
例如Map将项目存储在[[MapData]],内置的方法就可以直接访问他们,不会通过[[Get]/[[Set]]],所以Proxy拦截不到。
对于proxy,它会将目标对象上的方法和访问器内部的this的指向修改为代理对象,即使Proxy
未代理任何操作(handler为)
这些对象被代理之后,调用proxy.set相当于Map.prototype.set.call(proxy, 1, 2)
,Map.prototype.set
会去访问内部槽proxy.[[MapData]]
。由于proxy
对象没有此内部槽,将抛出错误。所以调用内置的方法将会失败
const map = new Map();
const proxy = new Proxy(map, {});
console.log(proxy.set === new Map().set)//true
proxy.set('test', 1); // Error
所以我们需要手动将this指向改为正确的对象
const proxy = new Proxy(map, {
get(target, property, receiver) {
const value = Reflect.get(target, property, receiver)
if (target instanceof Map) {
return value.bind(target)
}
return value
},
})
对于Promise
实例,proxy
上没有 [[PromiseState]]内部槽,所以需要将then进行bind
为什么Await一个Proxy会产生对then的一次拦截?
thenable:thenable对象指的是具有then方法的对象
thenable = {then: function (resolve, reject) {resolve(42);}};
这是因为 await v
,会将其转换为Promise.resolve(v)
。
而Promise.resolve()
静态方法将给定值“解析”为 Promise
,如果值是 promise,则返回该 promise;如果该值是 thenable,则将调用其then方法,并为其准备的两个回调;
如果要确定一个对象是否是thenable,需要判断其是否具有then方法,所以表现在proxy中,就是对其then的一次拦截
注意一点避免被绕进去,判断具有then方法并返回,指的是拦截then之后返回的是thenable的then方法,而不是这个thenable对象,如果不是then方法,await 得到的是proxy