總目錄

從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í)需要注意。