Proxy代理对象
Proxy对象提供了一种途径,让我们能够自己实现基础操作,创建具有普通对象无法企及能力的代理对象,创建代理对象时,需要指定另外两个对象,即目标对象(target)和处理器对象(handler)
let proxy = new Proxy(target, handler)
创建Proxy
代理对象没有自己的状态或行为,每次对它执行某个操作(读写、定义新属性、查询、调用)时,他只会把相应的操作发送给处理器对象或目标对象。代理对象支持的操作就是反射API定义的那些操作。
假设P是一个代理对象,我们想执行delete p.x,而Reflect.delectProperty()函数具有与delete操作符相同的行为,使用delete操作符删除代理对象上的一个属性时,代理对象会在处理器对象上查找deleteProperty()方法,如果存在,代理对象就调用它,如果不存在,代理对象就在目标对象上执行属性删除操作。对所有的基础操作,代理都这样处理。
let t = {x:1, y:2} let p = new Proxy(t, {}) p.x // 1 delete p.y // true, 在代理上执行删除 t.y // undefined, 其实是从目标对象上删除 p.z = 3 // 在代理上定义一个新属性 t.z // 3, 其实是在目标对象上定义
可撤销代理
上述这种透明包装其实本质上就是底层目标对象,没什么意义,然而,透明包装器在创建"可撤销代理"时有用。
创建可撤销代理使用Proxy.revocable(),这个函数返回一个对象,其中包含代理对象和一个revoke()函数。调用revoke()函数,代理立即失效。比如我们在使用一个不信任的第三方库,就可以在使用结束后切断代理。
function f(){ return 44 } // 代理既可封装目标函数,也可封装目标对象 let {proxy, revoke} = Proxy.revocable(f, {}) proxy() // 42 revoke() // 关闭代理通道 proxy() // TypeError
例子
1
创建一个对象,他有任意属性,属性值就是他的名字,所有属性都是只读的
通过这个代理可以看出,做出反射API相应的行为时,会先从处理器对象中查找相应方法
let identity = new Proxy({}, { get(o, name, target){ return name }, has(o, name){ return true }, ownKeys(o){throw new RangeError("can not enumerable")}, getOwnPropertyDescriptor(o, name){ return{ value:name, enumerable:false, writable:false, configurable:false, } }, set(o, name, value, target){ return false }, deleteProperty(o, name){ return false }, defineProperty(o, name, description){ return false }, isExtensible(o){ return false }, getPrototypeOf(o){ return null }, setPrototypeOf(o, proto){ return false } }) identity.x // x identity[0] // 0 identity.y = 1 // 设置无效 identity.y // y delete identity.x // false
2
上述例子只使用了处理器对象中的方法,没有用到目标对象本身的方法
function readOnlyProxy(o){ function readOnly(){ throw new TypeError("ReadOnly") } return new Proxy(o, { set: readOnly, defineProperty: readOnly, deleteProperty: readOnly, setPrototypeOf: readOnly }) } let x = {a:1, b:2} let y = readOnlyProxy(x) y.a // 1 读取属性正常,且采用的是目标对象中的基本方法 y.c = 3 // TypeError: ReadOnly delete y.b // TypeError: ReadOnly y.__proto__ = {} // TypeError: ReadOnly
代理不变式
前面的readOnly中,修改目标对象会返回TypeError,但是因为我们没有在处理器对象中设置isExtensible()和getOwnPropertyDescriptor(),当使用Reflect.isExtensible()和Reflect.和getOwnPropertyDescriptor()时,都会告诉我们对象是可配置的,此时,我们可以在处理器对象中添加相应方法,或保持这种轻微的不一致性
但是,代理禁止我们创建不一致离谱的代理对象,Proxy类在创建代理时会进行合理性检查,Proxy的不变式,几乎都与不可扩展的目标对象和目标对象上不可配置的属性有关。
例:我们创建了一个不可配置对象,但在代理中它的isExtensible()处理器返回为true,代理器会抛出TypeError
let target = Object.preventExtensions({}) let u = new Proxy(target, { isExtensible(){ return true } }) Reflect.isExtensible(u) // TypeError
不可扩展的代理对象就不能定义返回真正原型之外其他值的getPrototypeOf()处理器,如果对象某个属性不可写不可配置,那么get()返回跟属性实际值不一样的结果也会报错
let v = Object.freeze({x: 1}) let m = new Proxy(v, { get(){ return 99 } }) m.x // TypeError