1. 概述
-
Proxy可以理解成,在目標(biāo)對象之前架設(shè)一層“攔截”,外界對該對象的訪問,都必須先通過這層攔截,因此提供了一種機(jī)制,可以對外界的訪問進(jìn)行過濾和改寫。Proxy 這個詞的原意是代理,用在這里表示由它來“代理”某些操作,可以譯為“代理器”。 - Proxy 對象的所有用法,都是上面這種形式,不同的只是handler參數(shù)的寫法。其中,
new Proxy()表示生成一個Proxy實(shí)例,target參數(shù)表示所要攔截的目標(biāo)對象,即如果沒有Proxy的介入,操作原來要訪問的就是這個對象;handler第二個參數(shù)是一個配置對象,對于每一個被代理的操作,需要提供一個對應(yīng)的處理函數(shù),該函數(shù)將攔截對應(yīng)的操作。
var proxy = new Proxy({}, {
get: function(target, property) {
return 35;
}
});
proxy.time // 35
proxy.name // 35
proxy.title // 35
下面是 Proxy 支持的攔截操作一覽,一共 13 種。
get(target, propKey, receiver):攔截對象屬性的讀取,接收三個參數(shù),依次為目標(biāo)對象、屬性名和proxy實(shí)例本身(即this關(guān)鍵字指向的那個對象),其中最后一個參數(shù)可選set(target, propKey, value, receiver):用來攔截某個屬性的賦值操作,可以接受四個參數(shù),依次為目標(biāo)對象、屬性名、屬性值和 Proxy 實(shí)例本身,其中最后一個參數(shù)可選。has(target, propKey):has方法用來攔截HasProperty操作,即判斷對象是否具有某個屬性時(shí),這個方法會生效。典型的操作就是in運(yùn)算符。deleteProperty(target, propKey):方法用于攔截delete操作,如果這個方法拋出錯誤或者返回false,當(dāng)前屬性就無法被delete命令刪除。ownKeys(target):攔截Object.getOwnPropertyNames(proxy)、Object.getOwnPropertySymbols(proxy)、Object.keys(proxy),返回一個數(shù)組。該方法返回目標(biāo)對象所有自身的屬性的屬性名,而Object.keys()的返回結(jié)果僅包括目標(biāo)對象自身的可遍歷屬性。getOwnPropertyDescriptor(target, propKey):攔截Object.getOwnPropertyDescriptor(),返回一個屬性描述對象或者undefined。defineProperty(target, propKey, propDesc):defineProperty方法攔截了Object.defineProperty操作。返回false,導(dǎo)致添加新屬性會拋出錯誤preventExtensions(target):攔截Object.preventExtensions()。該方法必須返回一個布爾值,否則會被自動轉(zhuǎn)為布爾值。getPrototypeOf(target):主要用來攔截獲取對象原型。具體來說,攔截下面這些操作。
Object.prototype.__proto__
Object.prototype.isPrototypeOf()
Object.getPrototypeOf()
Reflect.getPrototypeOf()
instanceofisExtensible(target):攔截Object.isExtensible(proxy),返回一個布爾值。
setPrototypeOf(target, proto):攔截Object.setPrototypeOf(proxy, proto),返回一個布爾值。如果目標(biāo)對象是函數(shù),那么還有兩種額外操作可以攔截。apply(target, object, args):apply方法可以接受三個參數(shù),分別是目標(biāo)對象、目標(biāo)對象的上下文對象(this)和目標(biāo)對象的參數(shù)數(shù)組。construct(target, args):方法用于攔截new命令,方法可以接受兩個參數(shù)。target: 目標(biāo)對象 ,args:構(gòu)建函數(shù)的參數(shù)對象
3. Proxy.revocable()
Proxy.revocable方法返回一個可取消的 Proxy 實(shí)例。
let target = {};
let handler = {};
let {proxy, revoke} = Proxy.revocable(target, handler);
proxy.foo = 123;
proxy.foo // 123
revoke();
proxy.foo // TypeError: Revoked
Proxy.revocable的一個使用場景是,目標(biāo)對象不允許直接訪問,必須通過代理訪問,一旦訪問結(jié)束,就收回代理權(quán),不允許再次訪問。
4. this 問題
雖然 Proxy 可以代理針對目標(biāo)對象的訪問,但它不是目標(biāo)對象的透明代理,即不做任何攔截的情況下,也無法保證與目標(biāo)對象的行為一致。主要原因就是在 Proxy 代理的情況下,目標(biāo)對象內(nèi)部的this關(guān)鍵字會指向 Proxy 代理。
5. 實(shí)例:Web 服務(wù)的客戶端
Proxy 對象可以攔截目標(biāo)對象的任意屬性,這使得它很合適用來寫 Web 服務(wù)的客戶端。
const service = createWebService('http://example.com/data');
service.employees().then(json => {
const employees = JSON.parse(json);
// ···
});
上面代碼新建了一個 Web 服務(wù)的接口,這個接口返回各種數(shù)據(jù)。Proxy 可以攔截這個對象的任意屬性,所以不用為每一種數(shù)據(jù)寫一個適配方法,只要寫一個 Proxy 攔截就可以了。
function createWebService(baseUrl) {
return new Proxy({}, {
get(target, propKey, receiver) {
return () => httpGet(baseUrl+'/' + propKey);
}
});
}