什么是Proxy代理?
Proxy 用于修改某些操作的默認(rèn)行為,等同于在語(yǔ)言層面做出修改,所以屬于一種“元編程”(meta programming),即對(duì)編程語(yǔ)言進(jìn)行編程。
Proxy 可以理解成,在目標(biāo)對(duì)象之前架設(shè)一層“攔截”,外界對(duì)該對(duì)象的訪問(wèn),都必須先通過(guò)這層攔截,因此提供了一種機(jī)制,可以對(duì)外界的訪問(wèn)進(jìn)行過(guò)濾和改寫。Proxy 這個(gè)詞的原意是代理,用在這里表示由它來(lái)“代理”某些操作,可以譯為“代理器”。
var obj = new Proxy({}, {
get: function (target, propKey, receiver) {
console.log(`getting ${propKey}!`);
return Reflect.get(target, propKey, receiver);
},
set: function (target, propKey, value, receiver) {
console.log(`setting ${propKey}!`);
return Reflect.set(target, propKey, value, receiver);
}
});
上面代碼對(duì)一個(gè)空對(duì)象架設(shè)了一層攔截,重定義了屬性的讀?。?code>get)和設(shè)置(set
)行為。這里暫時(shí)先不解釋具體的語(yǔ)法,只看運(yùn)行結(jié)果。對(duì)設(shè)置了攔截行為的對(duì)象obj
,去讀寫它的屬性,就會(huì)得到下面的結(jié)果。
obj.count = 1
// setting count!
++obj.count
// getting count!
// setting count!
// 2
上面代碼說(shuō)明,Proxy 實(shí)際上重載(overload)了點(diǎn)運(yùn)算符,即用自己的定義覆蓋了語(yǔ)言的原始定義。
如何定義Proxy代理?
ES6 原生提供 Proxy 構(gòu)造函數(shù),用來(lái)生成 Proxy 實(shí)例。
var proxy = new Proxy(target, handler);
Proxy 對(duì)象的所有用法,都是上面這種形式,不同的只是handler參數(shù)的寫法。其中,new Proxy()表示生成一個(gè)Proxy實(shí)例,target參數(shù)表示所要攔截的目標(biāo)對(duì)象,handler參數(shù)也是一個(gè)對(duì)象,用來(lái)定制攔截行為。
下面是另一個(gè)攔截讀取屬性行為的例子。
var proxy = new Proxy({}, {
get: function(target, propKey) {
return 35;
}
});
proxy.time // 35
proxy.name // 35
proxy.title // 35
上面代碼中,作為構(gòu)造函數(shù),Proxy接受兩個(gè)參數(shù)。第一個(gè)參數(shù)是所要代理的目標(biāo)對(duì)象(上例是一個(gè)空對(duì)象),即如果沒(méi)有Proxy的介入,操作原來(lái)要訪問(wèn)的就是這個(gè)對(duì)象;第二個(gè)參數(shù)是一個(gè)配置對(duì)象,對(duì)于每一個(gè)被代理的操作,需要提供一個(gè)對(duì)應(yīng)的處理函數(shù),該函數(shù)將攔截對(duì)應(yīng)的操作。比如,上面代碼中,配置對(duì)象有一個(gè)get方法,用來(lái)攔截對(duì)目標(biāo)對(duì)象屬性的訪問(wèn)請(qǐng)求。get方法的兩個(gè)參數(shù)分別是目標(biāo)對(duì)象和所要訪問(wèn)的屬性。可以看到,由于攔截函數(shù)總是返回35,所以訪問(wèn)任何屬性都得到35。
注意,要使得Proxy起作用,必須針對(duì)Proxy實(shí)例(上例是proxy對(duì)象)進(jìn)行操作,而不是針對(duì)目標(biāo)對(duì)象(上例是空對(duì)象)進(jìn)行操作。
如果handler沒(méi)有設(shè)置任何攔截,那就等同于直接通向原對(duì)象。
var target = {};
var handler = {};
var proxy = new Proxy(target, handler);
proxy.a = 'b';
target.a // "b"
上面代碼中,handler是一個(gè)空對(duì)象,沒(méi)有任何攔截效果,訪問(wèn)proxy就等同于訪問(wèn)target。
一個(gè)技巧是將 Proxy 對(duì)象,設(shè)置到object.proxy屬性,從而可以在object對(duì)象上調(diào)用。
var object = { proxy: new Proxy(target, handler) };
Proxy 實(shí)例也可以作為其他對(duì)象的原型對(duì)象。
var proxy = new Proxy({}, {
get: function(target, propKey) {
return 35;
}
});
let obj = Object.create(proxy);
obj.time // 35
上面代碼中,proxy對(duì)象是obj對(duì)象的原型,obj對(duì)象本身并沒(méi)有time屬性,所以根據(jù)原型鏈,會(huì)在proxy對(duì)象上讀取該屬性,導(dǎo)致被攔截。
同一個(gè)攔截器函數(shù),可以設(shè)置攔截多個(gè)操作。
var handler = {
get: function(target, name) {
if (name === 'prototype') {
return Object.prototype;
}
return 'Hello, ' + name;
},
apply: function(target, thisBinding, args) {
return args[0];
},
construct: function(target, args) {
return {value: args[1]};
}
};
var fproxy = new Proxy(function(x, y) {
return x + y;
}, handler);
fproxy(1, 2) // 1
new fproxy(1, 2) // {value: 2}
fproxy.prototype === Object.prototype // true
fproxy.foo === "Hello, foo" // true
對(duì)于可以設(shè)置、但沒(méi)有設(shè)置攔截的操作,則直接落在目標(biāo)對(duì)象上,按照原先的方式產(chǎn)生結(jié)果。
下面是 Proxy 支持的攔截操作一覽,一共 13 種。
- get(target, propKey, receiver):攔截對(duì)象屬性的讀取,比如proxy.foo和proxy['foo']。
- set(target, propKey, value, receiver):攔截對(duì)象屬性的設(shè)置,比如proxy.foo = v或proxy['foo'] = v,返回一個(gè)布爾值。
- has(target, propKey):攔截propKey in proxy的操作,返回一個(gè)布爾值。
- deleteProperty(target, propKey):攔截delete proxy[propKey]的操作,返回一個(gè)布爾值。
- ownKeys(target):攔截Object.getOwnPropertyNames(proxy)、Object.getOwnPropertySymbols(proxy)、Object.keys(proxy)、for...in循環(huán),返回一個(gè)數(shù)組。該方法返回目標(biāo)對(duì)象所有自身的屬性的屬性名,而Object.keys()的返回結(jié)果僅包括目標(biāo)對(duì)象自身的可遍歷屬性。
- getOwnPropertyDescriptor(target, propKey):攔截Object.getOwnPropertyDescriptor(proxy, propKey),返回屬性的描述對(duì)象。
- defineProperty(target, propKey, propDesc):攔截Object.defineProperty(proxy, propKey, propDesc)、Object.defineProperties(proxy, propDescs),返回一個(gè)布爾值。
- preventExtensions(target):攔截Object.preventExtensions(proxy),返回一個(gè)布爾值。
- getPrototypeOf(target):攔截Object.getPrototypeOf(proxy),返回一個(gè)對(duì)象。
- isExtensible(target):攔截Object.isExtensible(proxy),返回一個(gè)布爾值。
- setPrototypeOf(target, proto):攔截Object.setPrototypeOf(proxy, proto),返回一個(gè)布爾值。如果目標(biāo)對(duì)象是函數(shù),那么還有兩種額外操作可以攔截。
- apply(target, object, args):攔截 Proxy 實(shí)例作為函數(shù)調(diào)用的操作,比如proxy(...args)、proxy.call(object, ...args)、proxy.apply(...)。
- construct(target, args):攔截 Proxy 實(shí)例作為構(gòu)造函數(shù)調(diào)用的操作,比如new proxy(...args)。
如果你想了解更多ES6 Proxy內(nèi)容和詳細(xì)使用方法,這里有最通俗易懂的教程《ES6 Proxy》。