5.8.1 如何代理Set和Map
直接用 Proxy 代理 Set / Map,读
size、调delete都会报错,原因是内部槽(internal slot)不在代理对象上。
一、读取 size 报错
const s = new Set([1, 2, 3])
const p = new Proxy(s, {
get(target, key, receiver) {
return Reflect.get(target, key, receiver)
}
})
console.log(p.size)
// TypeError: Method get Set.prototype.size called on incompatible receiver
原因:
Set.prototype.size是一个访问器属性,通过代理对象p访问时this就是p;- getter 内部会调用抽象方法
RequireInternalSlot(S, [[SetData]])检查S是否具有内部槽 [[SetData]]; - 代理对象没有内部槽,于是抛错。
修复:读取 size 时把 receiver 指定为原始 target,让 getter 的 this 指向原始 Set:
get(target, key, receiver) {
if (key === 'size') {
// 第三个参数指定为原始对象 target
return Reflect.get(target, key, target)
}
return Reflect.get(target, key, receiver)
}
console.log(p.size) // 3
二、调用 delete 报错
p.delete(1)
// TypeError: Method Set.prototype.delete called on incompatible receiver [object Object]
与 size 的差别:
size | delete | |
|---|---|---|
| 类型 | 访问器属性 | 方法 |
| 执行时机 | 访问属性时 getter 立即执行 | 访问时只是取值,p.delete(1) 调用时才执行 |
| 能否靠 receiver 修复 | 可以 | 不可以,delete 执行时的 this 永远指向代理对象 p |
修复:把方法绑定到原始数据对象上再返回:
get(target, key, receiver) {
if (key === 'size') {
return Reflect.get(target, key, target)
}
// 将方法与原始数据对象 target 绑定后返回
return target[key].bind(target)
}
这样 p.delete(1) 中 delete 的 this 始终指向原始 Set。
三、并入 createReactive
function createReactive(obj, isShallow = false, isReadonly = false) {
return new Proxy(obj, {
get(target, key, receiver) {
if (key === 'raw') return target
// 代理 Set / Map:size 属性
if (key === 'size') {
return Reflect.get(target, key, target)
}
// 代理 Set / Map:原型方法一律绑定原始对象
if ((target instanceof Set || target instanceof Map) && target[key] instanceof Function) {
return target[key].bind(target)
}
// ...数组的重写、track、深响应等逻辑
}
// ...set / has / ownKeys / deleteProperty
})
}
相关
- 5.8 代理 Set 和 Map:集合类型要代理哪些接口
- 5.8.2 建立响应联系:在
size、add、set、delete等操作上实现 track/trigger