初識(shí)TS裝飾器

寫在最前:本文轉(zhuǎn)自掘金

前言

我們平常開發(fā)中或多或少的聽說(shuō)使用過(guò)裝飾器,也切身感受到它帶給我們的遍歷削罩。本文將聚焦ts的裝飾器欣舵,去探討什么是裝飾器荆永,如何使用憋他。

裝飾器的演變

  • 2015-3-24
    stage1 階段释牺,也是目前廣為使用的用法,也基本等同ts開啟了experimentalDecorators的用法微服。
  • 2018-09
    進(jìn)入到stage2階段趾疚,用法和stage1很大不同
  • 2012-12
    針對(duì)stage2天進(jìn)行了一次修改。
  • 2022-03
    正是進(jìn)入stage3以蕴,去掉了matedata部分糙麦,使用方式?jīng)]有太大變化。

js裝飾器和ts裝飾器

js原生目前不支持裝飾器丛肮,只能通過(guò)babel體驗(yàn)裝飾器這個(gè)新特性赡磅。

裝飾器是一種特殊類型的聲明,它能夠被附加到類聲明宝与,方法焚廊,訪問(wèn)符,屬性或參數(shù)上习劫。裝飾器使用@expression這種形式咆瘟,expression求職后必須為一個(gè)函數(shù),它會(huì)再運(yùn)行時(shí)被調(diào)用榜聂,被裝飾的聲明信息作為參數(shù)傳入搞疗。

由于裝飾器目前還是實(shí)驗(yàn)中的特定嗓蘑,在js中處于stage-3階段须肆。在ts中已經(jīng)作為一項(xiàng)實(shí)驗(yàn)性予以支持。開啟裝飾器需要在tsconfig.json文件中啟用 experimentalDecorators 編譯器選項(xiàng)桩皿。

裝飾器的使用

類裝飾器

類裝飾器是我們最常使用到的豌汇,它的通常作用是,為該類擴(kuò)展功能

  • 類裝飾器有且只有一個(gè)泄隔,參數(shù)為類的構(gòu)造函數(shù)constructor
  • 如果類裝飾器返回一個(gè)值拒贱,它會(huì)使用提供的構(gòu)造函數(shù)來(lái)替換類的聲明

設(shè)想有這樣一個(gè)場(chǎng)景。目前有一個(gè)Tank類,有一個(gè)Plane類逻澳,有一個(gè)Animal類闸天。這三個(gè)類都需要一個(gè)公共的方法來(lái)獲取他們所在的位置。我們第一可能想到使用繼承來(lái)實(shí)現(xiàn)斜做。

class BaseClass {
    getPosition() {
        return {
            x: 100,
            y: 200,
            z: 300,
        }
    }
}
class Tank extends BaseClass{}
class Plane extends BaseClass {}
class Animal extends BaseClass {}

這樣三個(gè)類都可以調(diào)用getPosition方法來(lái)獲取各自的位置了苞氮。到目前為止看起來(lái)沒(méi)有什么問(wèn)題。
現(xiàn)在又有新需求瓤逼,Tank類和Plane類需要一個(gè)新的方法addPetrol來(lái)給坦克和飛機(jī)加油笼吟。而動(dòng)物不需要加油。此時(shí)這種寫法好像不能繼續(xù)進(jìn)行下去了霸旗。而js目前沒(méi)有直接語(yǔ)法提供多繼承的功能贷帮,我們的繼承方向好像行不通了。這個(gè)時(shí)候類裝飾器可以很完美的實(shí)現(xiàn)這樣的功能诱告。

裝飾器功能之一——能力擴(kuò)展
我們把getPositionaddPertrol都抽象成一個(gè)單獨(dú)的功能撵枢,它們得作用是給宿主擴(kuò)展對(duì)應(yīng)的功能。

const getPositionDecorator: ClassDecorator = (constructor: Function) => {
    constructor.prototype.getPosition = () => {
        return [100, 200]
    }
}

const addPetrolDecorator: ClassDecorator = (constructor: Function) => {
    constructor.prototype.addPetrol = () => {
        // do something
        console.log(`${constructor.name}進(jìn)行加油`);
    }
}

@addPetrolDecorator
@getPositionDecorator
class Tank {}
@addPetrolDecorator
@getPositionDecorator
class Plane {}

@getPositionDecorator
class Animal {}

這樣的話精居,假如日后我們有其他的需求诲侮,都可以對(duì)他進(jìn)行能力擴(kuò)展,讓其具有加油的能力箱蟆。
注意沟绪,多個(gè)裝飾器疊加的時(shí)候,執(zhí)行順序?yàn)殡x被裝飾對(duì)象越近的裝飾器越先執(zhí)行空猜。

裝飾器功能之二——重載構(gòu)造函數(shù)
在類裝飾器中如果返回一個(gè)值绽慈,它會(huì)使用提供的構(gòu)造函數(shù)來(lái)替換類的聲明。

function classDecorator<T extends {new(...args:any[]):{}}>(constructor:T) {
    return class extends constructor {
        newProperty = "new property";
        hello = "override";
    }
}

@classDecorator
class Greeter {
    property = "property";
    hello: string;
    constructor(m: string) {
        this.hello = m;
    }
}

方法裝飾器

方法裝飾器接收三個(gè)參數(shù):

  • 對(duì)于靜態(tài)方法辈毯,第一個(gè)參數(shù)為類的構(gòu)造函數(shù)坝疼。對(duì)于實(shí)例方法,為類的原型對(duì)象
  • 第二個(gè)參數(shù)為方法名谆沃。
  • 第三個(gè)參數(shù)為方法描述符钝凶。
  • 方法裝飾器可以有返回值,返回值會(huì)作為方法的屬性描述符

裝飾器功能之一——能力增強(qiáng)
我們代碼編寫時(shí)唁影,經(jīng)常會(huì)做一些錯(cuò)誤catch耕陷,使用裝飾器對(duì)每個(gè)方法進(jìn)行增加,使它們自動(dòng)獲取catch錯(cuò)誤的能力~

const ErrorDecorator: MethodDecorator = (target: Object, propertyKey: string | symbol, descriptor: PropertyDescriptor) => {
    const sourceMethod = descriptor.value;
    descriptor.value = async function (...args: any) {
        try {
            await sourceMethod.apply(this, args);
        } catch (error) {
            console.error('捕獲到了錯(cuò)誤');
            // do something
        }
    }
}
class MusicSystem {
    getMusicById(name: string): Promise<{name: string, singer: string}> {
        return new Promise((resolve, reject) => {
            setTimeout(() => {
                if (Math.round(Math.random())) {
                    resolve({name: '鳳凰傳奇', singer: '玲花|曾毅'});
                } else {
                    reject()
                }
            }, 1000);
        })
    }
    
    @ErrorDecorator
    async play(name: string) {
        const music = await this.getMusicById(name);
        // ... do something
        console.log(`在曲庫(kù)中找到了名為${music.name}的音樂(lè)据沈,由${music.singer}進(jìn)行演唱哟沫,敬請(qǐng)欣賞。`);

    }

    @ErrorDecorator
    async deleteByName(name: string) {
        const music = await this.getMusicById(name);
        // ... do something
        console.log(`${music.name}音樂(lè)刪除成功锌介!`);
    }
}

const musicSystem = new MusicSystem();
musicSystem.play('鳳凰傳奇');
musicSystem.deleteByName('鳳凰傳奇');

細(xì)心的同學(xué)可以發(fā)現(xiàn)了嗜诀,我們?cè)诜椒ㄑb飾器中無(wú)法捕獲到實(shí)際的錯(cuò)誤猾警,比如精準(zhǔn)報(bào)錯(cuò)哪首歌沒(méi)找到。很遺憾隆敢,目前裝飾器的原生能力发皿,是無(wú)法獲取到我們調(diào)用時(shí)候傳入的具體參數(shù)的。因?yàn)檠b飾器實(shí)在編譯階段執(zhí)行的拂蝎。但是雳窟,我們可以通過(guò)其他方式實(shí)現(xiàn)這樣的功能,這就是大名鼎鼎的 metadata 匣屡。我們會(huì)在文章的末尾提到它封救。

裝飾器功能之一——descriptor修改
通過(guò)修改descriptor,我們可以實(shí)現(xiàn)對(duì)方法進(jìn)行重新描述捣作。比如設(shè)置方法禁止修改誉结,禁止刪除等。

const DescriptorDecorator: MethodDecorator = (target: Object, propertyKey: string | symbol, descriptor: PropertyDescriptor) : object => {
    return {
        value: () => {
            console.log('eat方法被替換')
        },
        writable: true,
        enumerable: true,
        configurable: true,
    };
}

class Pig {
    name = 'peiqi';
    @DescriptorDecorator
    eat() {

    }
}

同樣的券躁,也可以直接對(duì)descriptor進(jìn)行修改

descriptor.value = () => {console.log('eat方法被替換')};
descriptor.writable = true;
descriptor.enumerable = true;
descriptor.configurable = true;

方法裝飾器的使用方式很多惩坑,大多數(shù)的使用方式是對(duì)descriptor的value屬性進(jìn)行替換,攔截等實(shí)現(xiàn)功能也拜。

【下邊的三個(gè)裝飾器類型以舒,相對(duì)來(lái)說(shuō)使用比較少,有興趣的小伙伴可以查看原文】

屬性裝飾器

參數(shù)裝飾器

訪問(wèn)器裝飾器

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末慢哈,一起剝皮案震驚了整個(gè)濱河市蔓钟,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌卵贱,老刑警劉巖滥沫,帶你破解...
    沈念sama閱讀 206,482評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異键俱,居然都是意外死亡兰绣,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,377評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門编振,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)缀辩,“玉大人,你說(shuō)我怎么就攤上這事踪央⊥涡” “怎么了?”我有些...
    開封第一講書人閱讀 152,762評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵杯瞻,是天一觀的道長(zhǎng)镐牺。 經(jīng)常有香客問(wèn)我,道長(zhǎng)魁莉,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,273評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮旗唁,結(jié)果婚禮上畦浓,老公的妹妹穿的比我還像新娘。我一直安慰自己检疫,他們只是感情好讶请,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,289評(píng)論 5 373
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著屎媳,像睡著了一般夺溢。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上烛谊,一...
    開封第一講書人閱讀 49,046評(píng)論 1 285
  • 那天风响,我揣著相機(jī)與錄音,去河邊找鬼丹禀。 笑死状勤,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的双泪。 我是一名探鬼主播持搜,決...
    沈念sama閱讀 38,351評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼焙矛!你這毒婦竟也來(lái)了葫盼?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,988評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤村斟,失蹤者是張志新(化名)和其女友劉穎剪返,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體邓梅,經(jīng)...
    沈念sama閱讀 43,476評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡脱盲,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,948評(píng)論 2 324
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了日缨。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片钱反。...
    茶點(diǎn)故事閱讀 38,064評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖匣距,靈堂內(nèi)的尸體忽然破棺而出面哥,到底是詐尸還是另有隱情,我是刑警寧澤毅待,帶...
    沈念sama閱讀 33,712評(píng)論 4 323
  • 正文 年R本政府宣布尚卫,位于F島的核電站,受9級(jí)特大地震影響尸红,放射性物質(zhì)發(fā)生泄漏吱涉。R本人自食惡果不足惜刹泄,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,261評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望怎爵。 院中可真熱鬧特石,春花似錦、人聲如沸鳖链。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,264評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)芙委。三九已至逞敷,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間灌侣,已是汗流浹背推捐。 一陣腳步聲響...
    開封第一講書人閱讀 31,486評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留顶瞳,地道東北人玖姑。 一個(gè)月前我還...
    沈念sama閱讀 45,511評(píng)論 2 354
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像慨菱,于是被迫代替她去往敵國(guó)和親焰络。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,802評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容