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

 

posted @ 2021-12-21 20:34  邢韬  阅读(401)  评论(0编辑  收藏  举报