溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點(diǎn)擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

從C#到TypeScript - 裝飾器

發(fā)布時(shí)間:2020-08-04 07:55:19 來源:網(wǎng)絡(luò) 閱讀:186 作者:胡壯壯 欄目:網(wǎng)絡(luò)安全

從C#到TypeScript - 裝飾器

在C#里面如果想要不直接修改類或方法,但給類或方法添加一些額外的信息或功能,可以想到用Attribute,這是一個(gè)十分方便的功能裝飾器。
用TypeScript同樣也可以利用裝飾器來給類、函數(shù)、屬性以及參數(shù)添加附加功能,裝飾器是ES7的一個(gè)提案,在TypeScript里已經(jīng)有實(shí)現(xiàn)可用,不過需要在tsconfig.json里啟用experimentalDecorators

"compilerOptions": {
    ..., // other options
    "experimentalDecorators": true}

裝飾器介紹

TypeScript中裝飾器可以應(yīng)用到類、方法、屬性及函數(shù)參數(shù)上,而且可以同時(shí)應(yīng)用多個(gè)。
裝飾器的寫法是@name(),()可以不要,也可以在里面寫一些參數(shù)。

@Testable
@Log('controller')class Controller{

    @GET
    getContent(@QueryParam arg: string): string{        return '';
    }
}

裝飾器的實(shí)現(xiàn)

裝飾器根據(jù)實(shí)現(xiàn)可以分兩種:
一種是不帶括號,和屬性一樣,如@Testable。

function Testable(target: Function) { // 類、方法、屬性、方法參數(shù)的參數(shù)各不相同
    //這里可以記錄一些信息到target,或者針對target做一些處理,如seal}

另外一種是帶括號的,和函數(shù)一樣,如@Log('controller'),實(shí)現(xiàn)函數(shù)里的參數(shù)就是括號里的參數(shù),而且需要返回一個(gè)function。

function Log(name: string) { // name就是傳進(jìn)來的參數(shù)'controller'
    return function(target: Function) { // 類、方法、屬性、方法參數(shù)的參數(shù)各不相同
        // 這里可以根據(jù)name和target來做一些處理
    }
}

類裝飾器

上面的(target: Function)其實(shí)就是類的裝飾器參數(shù),指向的是類的構(gòu)造函數(shù),如果想給類加一個(gè)簡單的seal功能,可以這樣做:

function sealed(target: Function) {    Object.seal(target);    Object.seal(target.prototype);
}

@sealedclass Test{
    
}

Test.prototype.test = ''; // 運(yùn)行時(shí)出錯,不能添加

上面的sealed就是類的裝飾器,target指構(gòu)造函數(shù),類裝飾器就這么一個(gè)參數(shù)。

方法裝飾器

方法裝飾器的使用方法和類裝飾器類似,只是參數(shù)不一樣,方法裝飾器有三個(gè)參數(shù):

  1. 如果裝飾的是靜態(tài)方法,則是類的構(gòu)造函數(shù),如果是實(shí)例方法則是類的原型。

  2. 方法的名字。

  3. 方法的PropertyDescriptor。
    PropertyDescriptor即屬性描述符,有
    configurable 是否可以配置,如動態(tài)添加刪除函數(shù)屬性之類
    writable 是否可寫,可以用來設(shè)置只讀屬性
    enumerable 是否可枚舉,即是否能在for...in中能枚舉到
    value 對象或?qū)傩缘闹?/p>

有了這些參數(shù)就可以很好的給方法添加一些功能,比如下面實(shí)現(xiàn)類型WebApi里的Get的路由:

const Router = Symbol(); // 唯一key,用來存裝飾器的信息function GET(path?: string) { // GET帶了個(gè)可選參數(shù)
    return (target: any, name: string) => setMethodDecorator(target, name, 'GET', path);
}//把method和path存起來,路由查找的時(shí)候就可以用了function setMethodDecorator(target: any, name: string, method: string, path?: string){
    target[Router] = target[Router] || {};
    target[Router][name] = target[Router][name] || {};
    target[Router][name].method = method;
    target[Router][name].path = path;
}// 通過PropertyDescriptor來設(shè)置enumerablefunction Enumerable(enumerable: boolean) { 
    return (target: any, name: string, descriptor: PropertyDescriptor) => {
        descriptor.enumerable = enumerable;
    };
}class Controller{

    @GET
    @Enumerable(true)
    getContent(arg: string): string{        return '';
    }
}

參數(shù)裝飾器

方法參數(shù)同樣可以有裝飾器,同樣有三個(gè)參數(shù),前兩個(gè)參數(shù)和方法的一致,最后一個(gè)參數(shù)是所裝飾的參數(shù)的位置。
能過參數(shù)裝飾器可以給方法動態(tài)的檢查或設(shè)置參數(shù)值,下面是檢查參數(shù)是否為空,為空則拋出異常。

const CheckNullKey = Symbol();const Router = Symbol();// 把CheckNull裝飾的參數(shù)存起來function CheckNull(target: any, name: string, index: number) {
    target[Router] = target[Router] || {};
    target[Router][name] = target[Router][name] || {};
    target[Router][name].params = target[Router][name].params || [];
    target[Router][name].params[index] = CheckNullKey;
}// 找出CheckNull的參數(shù),并檢查參數(shù)值,為空則拋異常,否則繼續(xù)執(zhí)行方法function Check(target: any, name: string, descriptor: PropertyDescriptor) {    let method = descriptor.value;
    descriptor.value = function () {        let params = target[Router][name].params;        if (params) {            for (let index = 0; index < params.length; index++) {                if (params[index] == CheckNullKey &&  // 找到CheckNull的參數(shù)并拋異常
                    (arguments[index] === undefined || arguments[index] === null)) {                    throw new Error("Missing required argument.");
                }
            }
        }        return method.apply(this, arguments);
    }
}class Controller{

    @Check
    getContent(@CheckNull id: string): string{        console.info(id);        return id;
    }
}new Controller().getContent(null); // error : Missing required argument.

屬性裝飾器

用法同上,參數(shù)只有兩個(gè),和類裝飾器的前兩個(gè)一樣,常用來標(biāo)識屬性的特性。

function Column(target: any, name: string) {    //把name存起來,這個(gè)column僅僅是標(biāo)識出來對應(yīng)數(shù)據(jù)庫中的列,常用在ORM框架中}class Table{

    @Column  
    name: string;
}

另外還有屬性訪問器的裝飾器,和方法基本一樣,同樣的三個(gè)參數(shù),不過同個(gè)屬性的getset只能有一個(gè)有,而且必須是先聲明的那個(gè)。

class User {    private _name: string;

    @Enumerable(true)    get name(){        return this._name;
    }    set name(value: string) {        this._name = value;
    }
}

多個(gè)裝飾器的執(zhí)行順序

一個(gè)聲明可以添加多個(gè)裝飾器,所以會有個(gè)執(zhí)行先后順序。
首先從上到下執(zhí)行裝飾器函數(shù),然后再從下往上應(yīng)用帶括號的裝飾器返回的函數(shù)。

function Test1(){    console.info('eval test1');    return function(target: any, name: string, descriptor: PropertyDescriptor){        console.info('apply test1');
    }
}function Test2(){    console.info('eval test2');    return function(target: any, name: string, descriptor: PropertyDescriptor){        console.info('apply test2');
    }
}class User1{

    @test1()
    @Test2()
    getName(){

    }
}

結(jié)果是:

eval test1eval test2
apply test2
apply test1

總之,裝飾器等于引入了天然的裝飾模式,給類,方法等添加額外功能。不過裝飾器目前還不算太穩(wěn)定,但是由于確實(shí)方便,已經(jīng)有成熟項(xiàng)目在使用了。


向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI