總目錄
從C#到TypeScript - Proxy
我們知道在C#中要實(shí)現(xiàn)代理功能需要自己來(lái)實(shí)現(xiàn)代理類,并且每個(gè)類需要不同的代理類,使用起來(lái)不方便,雖然借助一些AOP框架可以一定程度實(shí)現(xiàn)攔截,但畢竟框架級(jí)別的還是太重了。
現(xiàn)在ES6倒是出了個(gè)解決方案,Proxy是ES6推出的用于攔截操作的一層代理實(shí)現(xiàn),TypeScript當(dāng)然也同樣支持,下面來(lái)看下Proxy是怎么用的。
Proxy使用
Proxy本身是一個(gè)類,可以通過(guò)new
來(lái)實(shí)例化一個(gè)代理。
let p = new Proxy(target, handle)
Proxy有兩個(gè)參數(shù):target
指所要代理的對(duì)象。handle
也是一個(gè)對(duì)象,對(duì)象里包含對(duì)target
操作的攔截。
看個(gè)例子:
let obj = { name: 'brook' };let p = new Proxy(obj, { get(target, property){ return 'cnblogs'; } });console.info(obj.name); // brookconsole.info(p.name); // cnblogs
可以看到,p
做為obj
的代理,在handle里加了對(duì)目標(biāo)對(duì)象的屬性get
操作進(jìn)行攔截,所以第一次直接輸出obj
的name是'brook',用代理p
輸出就變成'cnblogs'了。
因?yàn)閔andle里對(duì)獲取屬性操作進(jìn)行了重新定義。get
函數(shù)同樣有兩個(gè)參數(shù),target
仍然是操作對(duì)象,另一個(gè)property
則是要訪問(wèn)的屬性的名字。
Proxy可攔截的操作
get(target, propKey, receiver)
set(target, propKey, value, receiver)
apply(target, object, args)
defineProperty(target, propKey, propDesc)
deleteProperty(target, propKey)
has(target, propKey)
ownKeys(target)
construct(target, args)
getPrototypeOf(target)
setPrototypeOf(target, proto)
getOwnPropertyDescriptor(target, propKey)
isExtensible(target)
preventExtensions(target)
看過(guò)上一篇Reflect的有沒(méi)有很熟,沒(méi)錯(cuò),Reflect里的操作Proxy里都同樣有一份,這樣在做Proxy的時(shí)候,如果要回到原始的結(jié)果,直接調(diào)用Reflect對(duì)應(yīng)的操作就好。
接下來(lái)挑幾個(gè)重要的看看。
get
get(target, propKey, receiver)
上面提到過(guò)get
,不過(guò)沒(méi)說(shuō)第三個(gè)參數(shù),其實(shí)receiver
指的就是new出來(lái)的Proxy對(duì)象。
let obj = { name: 'brook' };let p = new Proxy(obj, { get(target, property, receiver){ console.info(receiver === p); // true return 'cnblogs' } });console.info(p.name);
再來(lái)個(gè)例子來(lái)看看get
能做到什么程度,我們知道數(shù)組的索引不能為負(fù)數(shù),現(xiàn)在我們通過(guò)Proxy來(lái)讓數(shù)組來(lái)支持它:
let arr = ["b", "r", "o", "o", "k"];let p = new Proxy(arr, { get(target, property){ let index = Math.abs(Number(property)); // 取負(fù)數(shù)的絕對(duì)值 return arr[index]; } });console.info(arr[2]); // 輸出oconsole.info(p[-2]); //同樣輸出o
set
set(target, propKey, value, receiver)set
用來(lái)攔截屬性的賦值操作,比如number
類型的數(shù)組,可以讓它接受任何類型的值,當(dāng)不是number
的時(shí)候就給值0
,當(dāng)然這只是個(gè)不符合實(shí)際使用的功能演示:
let arr = new Array<number>();let p = new Proxy(arr, { set(target, property, value, receiver){ if(typeof value != 'number'){ // 不是number就設(shè)為0 value = 0; } return Reflect.set(target, property, value); } }); p[0] = 11; p[1] = "brook";console.info(arr[0]); // 11console.info(arr[1]); // 0
現(xiàn)在前端MVVM很火,而用set
就可以輕松做到設(shè)置屬性值的同時(shí)更新Dom對(duì)象,實(shí)現(xiàn)綁定的效果。
apply
apply(target, object, args)
這可以攔截函數(shù)的調(diào)用,第一個(gè)和第三個(gè)參數(shù)的意思很明確,分別指函數(shù)和函數(shù)的參數(shù)。
第二個(gè)參數(shù)是指上下文的this
,this的不同會(huì)可能導(dǎo)致函數(shù)里變量值的不同。
class Test1{ text: string = 'test1'; func(){ console.info('call test1 func') console.info(`I am brook in ${this.text}`); } }class Test2{ text: string = 'test2'; func(){ console.info('call test2 func') console.info(`I am brook in ${this.text}`); } }let t1 = new Test1();let t2 = new Test2();let p = new Proxy(t1.func, { apply(target, thisArg, args){ Reflect.apply(target, t2, args); } }); p();
上面代碼輸出信息如下:
call test1 func I am brook in test2
也就是實(shí)際調(diào)用的還是Test1的func,所以第一條輸出為call test1 func
,雖然Proxy代理的是Test1的func,但實(shí)際執(zhí)行時(shí)傳的this是t2,所以函數(shù)里的this指向了Test2,取的也就是test2
中要實(shí)現(xiàn)代理功能需要自己來(lái)實(shí)現(xiàn)代理類,并且每個(gè)類需要不同的代理類,使用起來(lái)不方便,雖然借助一些AOP框架可以一定程度實(shí)現(xiàn)攔截,但畢竟框架級(jí)別的還是太重了。
上面介紹了幾個(gè)常用的,其他的意思也很明顯就不多說(shuō)了,Proxy的應(yīng)用場(chǎng)景除了上面說(shuō)過(guò)的MVVM外,還可以用在ORM中,把對(duì)象的行為映射到數(shù)據(jù)庫(kù)中,還有數(shù)據(jù)訪問(wèn)的代理,總之想用到代理的可以考慮Proxy。
還有就是要記住Proxy不是透明代理,它有自己的this,使用時(shí)需要注意。