Proxy代理物件
阿新 • • 發佈:2021-12-21
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()函式,代理立即失效。比如我們在使用一個不信任的第三方庫,就可以在使用結束後切斷代理。functionf(){ 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,代理器會丟擲TypeErrorlet 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