Proxy
创建一个Proxy
const proxy = new Proxy(target, handler)
target:是要包装的对象,可以是任何东西,包括函数。
handler: 是代理配置:带有“捕捉器”或者“拦截器”的对象。比如 get 捕捉器用于读取 target 的属性,set 捕捉器用于写入 target 的属性,等等。
- 设置简单的get拦截
const user= {name:'san'}
const userProxy = new Proxy(user,{
get(target,prop){
return target[prop]
}
}) // userProxy.name: 'san'
- 设置get的默认值和参数说明
const array = [1,2,3,4,5,6]
const arrayProxy = new Proxy(array,{
get(target,prop){
if(prop in target){
return target[prop]
}
return '你的是个吨的代码'
}
})
arrayProxy[5] // 输出 6
arrayProxy[888] // 输出 '你的是个吨的代码'
以上的get都用到了target和prop两个参数。但是get有三个参数,详解如下
- target: 是目标对象,该对象被作为第一个参数传递给 new Proxy。
- property: 目标属性名。
- receiver: 如果目标属性是一个 getter 访问器属性,则 receiver 就是本次读取属性所在的 this 对象。通常,这就是 proxy 对象本身(或者,如果我们从 proxy 继承,则是从该 proxy 继承的对象)。
下面是来自官方的建议(去看看)
代理应该在所有地方都完全替代目标对象。目标对象被代理后,任何人都不应该再引用目标对象。否则很容易搞砸。
tips:所以我上面为了区分原始对象和代理对象的写法是不对的
tips:所以我上面为了区分原始对象和代理对象的写法是不对的
tips:所以我上面为了区分原始对象和代理对象的写法是不对的
- 设置set拦截
let user = {}
user = new Proxy(user,{
set(target, prop, val){
if (prop) {
console.log(prop)
target[prop] = val
return true
}
return false
}
})
以上的set使用了set()的三个参数,set还有一个和get一样的receiver
- target: 是目标对象,该对象被作为第一个参数传递给 new Proxy,
- property: 目标属性名称,
- value: 目标属性的值,
- receiver: 与 get 捕捉器类似,仅与 setter 访问器属性相关。
- Proxy的全部拦截器
以下的各个拦截器其内部方法和Object的方法作用一样,名称大致相同,handler方法为Proxy Handler方法 触发时机如下表和图所示:(object的函数解释:个人版传送,官网版传送)
内部方法 | Handler 方法 | 何时触发 |
---|---|---|
[[Get]] | get | 读取属性 |
[[Set]] | set | 写入属性 |
[[HasProperty]] | has | in 操作符 |
[[Delete]] | deleteProperty | delete 操作符 |
[[Call]] | apply | 函数调用 |
[[Construct]] | construct | new 操作符 |
[[GetPrototypeOf]] | getPrototypeOf | Object.getPrototypeOf |
[[SetPrototypeOf]] | setPrototypeOf | Object.setPrototypeOf |
[[IsExtensible]] | isExtensible | Object.isExtensible |
[[PreventExtensions]] | preventExtensions | Object.preventExtensions |
[[DefineOwnProperty]] | defineProperty | Object.defineProperty, Object.defineProperties |
[[GetOwnProperty]] | getOwnPropertyDescriptor | Object.getOwnPropertyDescriptor, for…in, Object.keys/values/entries |
[[OwnPropertyKeys]] | ownKeys | Object.getOwnPropertyNames, Object.getOwnPropertySymbols, for…in, Object.keys/values/entries |
Reflect
Reflect 是ES6之后的一个内建对象,可简化 Proxy 的创建。
前面所讲过的内部方法,例如 [[Get]] 和 [[Set]] 等,都只是规范性的,不能直接调用。
Reflect 对象使调用这些内部方法成为了可能。它的方法是内部方法的最小包装。
- Reflect 调用的示例
操作 | Reflect 调用 | 内部方法 |
---|---|---|
obj[prop] | Reflect.get(obj, prop) | [[Get]] |
obj[prop] = value | Reflect.set(obj, prop, value) | [[Set]] |
delete obj[prop] | Reflect.deleteProperty(obj, prop) | [[Delete]] |
new F(value) | Reflect.construct(F, value) | [[Construct]] |
和get相同… | 和get相同… | 和get相同… |
对于每个可被 Proxy 捕获的内部方法,在 Reflect 中都有一个对应的方法,其名称和参数与 Proxy 捕捉器相同。
- ReFlect 基本用法
let user = {};
Reflect.set(user, 'name', 'John');
- 用ReFlect重写一下前面的get、set
let user = {name: '张三'}
user = new Proxy(user,{
get(target,prop,receiver){
alert(prop)
return Reflect.get(...arguments)
},
set(target, prop, val, receiver){
alert(prop,val)
return Reflect.set(...arguments)
}
})
- Proxy不是万能的有些对象不能直接代理
以下代码直接报错
let map = new Map()
map = new Proxy(map,{})
map.set('test',1)
// Uncaught TypeError: Method Map.prototype.set called on incompatible receiver #<Map>
// at Proxy.set (<anonymous>)
// at <anonymous>:1:5
- 解决方案
let map = new Map()
map = new Proxy(map,{
get(target,prop,receiver){
const value = Reflect.get(...arguments)
return typeof value == 'function' ? value.bind(target) : value
}
})
map.set('test',1)
map.get('test') // 输出 1
总结
Proxy 是对象的包装器,将代理上的操作转发到对象,并可以选择捕获其中一些操作。它可以包装任何类型的对象,包括类和函数。
语法为:
let proxy = new Proxy(target, {
/* trap */
});
·······然后,在任何地方都应该使用proxy而不是使用target。代理没有自己的属性或者方法,如果提供了捕捉器(trap),它将捕获操作,否则将直接转发给target对象。
- 读取(get),写入(set),删除(deleteProperty)属性(甚至是不存在的属性)。
- 函数调用(apply 捕捉器)。
- new 操作(construct 捕捉器)。
- 许多其他操作(可以参考文章上面的或者文档)
Reflect API 旨在补充 Proxy。对于任意 Proxy 捕捉器,都有一个带有相同参数的 Reflect 调用。我们应该使用它们将调用转发给目标对象。
Proxy 有一些局限性:
- 内建对象具有“内部插槽”,对这些对象的访问无法被代理。(解决方案如上文)
- 私有类字段也是如此,因为它们也是在内部使用插槽实现的。因此,代理方法的调用必须具有目标对象作为 this 才能访问它们。
- 对象的严格相等性检查 === 无法被拦截。
- 性能:基准测试(benchmark)取决于引擎,但通常使用最简单的代理访问属性所需的时间也要长几倍。实际上,这仅对某些“瓶颈”对象来说才重要。
扩展
已知目前浅学了Proxy、ReFlect、Object对象、原形、以及VUE2的双向绑定简单实现是由Object.defineProperty拦截get,set等知识。尝试简单实现VUE3的双向绑定简单实现。