TypeScript(ts)學(xué)習(xí)筆記(九):裝飾器

學(xué)習(xí)裝飾器之前我們首先要明確一個(gè)概念:
裝飾器本質(zhì)上是一個(gè)函數(shù),@expression 的形式其實(shí)是一個(gè)語(yǔ)法糖根悼, expression 求值后必須也是一個(gè)函數(shù),它會(huì)在運(yùn)行時(shí)被調(diào)用士败,被裝飾的聲明信息做為參數(shù)傳入控汉。
Typescript 中使用裝飾器需要我們?cè)?tsconfig.json 里面開(kāi)啟支持選項(xiàng) experimentalDecorators

// tsconfig.json
"experimentalDecorators": true

類(lèi)裝飾器

聲明一個(gè)函數(shù)去給 Classage屬性賦值:

function addAge(constructor: Function) {
  constructor.prototype.age = 22;
}

@addAge
class Person {
  name: string;
  age!: number;
  constructor() {
    this.name = "朱小明"
  }
}
let p = new Person();

console.log(p.age); //22

這段代碼實(shí)際上基本等同于:

Person = addAge(function Person() { ... });

方法裝飾器

方法裝飾器聲明在一個(gè)方法的聲明之前钟病,它會(huì)被應(yīng)用到方法的屬性描述符descriptor上萧恕,可以用來(lái)監(jiān)視,修改或者替換方法定義肠阱。
方法裝飾器會(huì)在運(yùn)行時(shí)當(dāng)作函數(shù)被調(diào)用票唆,傳入3個(gè)參數(shù):

  • 對(duì)于靜態(tài)成員來(lái)說(shuō)是類(lèi)的構(gòu)造函數(shù),對(duì)于實(shí)例成員是類(lèi)的原型對(duì)象屹徘。
  • 成員的名字走趋。
  • 成員的屬性描述符。
    如果方法裝飾器返回一個(gè)值噪伊,它會(huì)被用作方法的屬性描述符簿煌。如果代碼輸出目標(biāo)版本小于ES5返回值會(huì)被忽略氮唯。
class Person {
  name: string;
  constructor(name:string) {
    this.name = name;
  }
  @fun(false)
  static say() {
    return "hello" + this.name
  }
}
function fun(value: boolean) {
  return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
    console.log("target",target);
    console.log("propertyKey",propertyKey);
    descriptor.enumerable = value
    console.log(descriptor);
  }
}

const p = new Person("zhangsan")

輸出結(jié)果如下:

target [Function: Person] { say: [Function] }
propertyKey say
{
  value: [Function],
  writable: true,
  enumerable: false,
  configurable: true
}

當(dāng) @fun(false) 被調(diào)用時(shí),它會(huì)修改屬性描述符的 enumerable 屬性姨伟。

訪問(wèn)器裝飾器

TypeScript 不允許同時(shí)裝飾一個(gè)成員的getset訪問(wèn)器惩琉,一個(gè)成員的所有裝飾的必須應(yīng)用在文檔順序的第一個(gè)訪問(wèn)器上。這是因?yàn)槎峄模谘b飾器應(yīng)用于一個(gè)屬性描述符時(shí)瞒渠,它聯(lián)合了getset訪問(wèn)器,而不是分開(kāi)聲明的技扼。
訪問(wèn)器裝飾器被調(diào)用時(shí)伍玖,傳入3個(gè)參數(shù):

  • 對(duì)于靜態(tài)成員來(lái)說(shuō)是類(lèi)的構(gòu)造函數(shù),對(duì)于實(shí)例成員是類(lèi)的原型對(duì)象淮摔。
  • 成員的名字私沮。
  • 成員的屬性描述符。
class Person {
  private name: string;
  constructor(name: string) {
    this.name = name;
  }
  @configurable(false)
  get getName() {
    return this.name
  }
}
function configurable(value: boolean) {
  return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
    console.log(descriptor);
    descriptor.configurable = value
  }
}
const p = new Person("丁大寶")
console.log(p.getName); 

輸出結(jié)果如下:

{
  get: [Function: get],
  set: undefined,
  enumerable: true,
  configurable: true
}
丁大寶

屬性裝飾器

屬性裝飾器聲明在一個(gè)屬性聲明之前和橙,屬性裝飾器表達(dá)式在運(yùn)行時(shí)被調(diào)用仔燕,傳入下列2個(gè)參數(shù):

  • 對(duì)于靜態(tài)成員來(lái)說(shuō)是類(lèi)的構(gòu)造函數(shù),對(duì)于實(shí)例成員是類(lèi)的原型對(duì)象魔招。
  • 成員的名字晰搀。
注意:屬性描述符不會(huì)做為參數(shù)傳入屬性裝飾器
class Person{
  @format("尼古拉斯·趙四")
  name: string|undefined;
  constructor() {
  }
}
function format(value: string) {
  return function (target: any, propertyKey: string) {
    target[propertyKey] = value
  }
}
const p = new Person()
console.log(p.name); //尼古拉斯·趙四

參數(shù)裝飾器

參數(shù)裝飾器聲明在一個(gè)參數(shù)聲明之前(緊靠著參數(shù)聲明)。
參數(shù)裝飾器表達(dá)式需要傳入下列3個(gè)參數(shù):

  • 對(duì)于靜態(tài)成員來(lái)說(shuō)是類(lèi)的構(gòu)造函數(shù)办斑,對(duì)于實(shí)例成員是類(lèi)的原型對(duì)象外恕。
  • 參數(shù)名稱(chēng)
  • 參數(shù)的索引
function getParams(target: Object, propertyKey: string, index: number) {
  console.log(target,propertyKey,index);
}
class Person {
  getInfo(@getParams name: string, @getParams age: number) {
    return `${name}${age}歲了`
  }
}

const p = new Person();
console.log(p.getInfo("zhangsan",20));

輸出結(jié)果如下:

Person { getInfo: [Function] } getInfo 1
Person { getInfo: [Function] } getInfo 0
zhangsan20歲了

裝飾器工廠

如果我們需要幾個(gè)裝飾器分別裝飾到不同的成員上。我們可能會(huì)用下面這種寫(xiě)法:

@logClass
class Person{
  @logProperty
  name: string;
  constructor(name:string) {
    this.name = name;
  }
  @logMethod
  getName() {
    return this.name
  }
}
// 類(lèi)裝飾器
function logClass(target: typeof Person) {
  console.log(target);
}
// 屬性裝飾器
function logProperty(target: any, propertyKey: string) {
  console.log(propertyKey);
}
// 方法裝飾器
function logMethod(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
  console.log(descriptor);
}

雖然可以達(dá)到我們想要的效果乡翅,但我們還可以繼續(xù)優(yōu)化鳞疲,用一個(gè)裝飾器工廠來(lái)進(jìn)一步抽象上述代碼:

function log(...args: any[]) {
  switch (args.length) {
    case 1:
      return logClass.apply(this, args);
    case 2:
      return logProperty.apply(this, args);
    case 3:
      return logMethod.apply(this, args)
    default: 
      throw new Error("error")
  }
}

裝飾器組合

TypeScript里,當(dāng)多個(gè)裝飾器應(yīng)用在一個(gè)聲明上時(shí)會(huì)進(jìn)行如下步驟的操作:

  • 由上至下依次對(duì)裝飾器表達(dá)式求值蠕蚜。
  • 求值的結(jié)果會(huì)被當(dāng)作函數(shù)尚洽,由下至上依次調(diào)用。
    如果我們使用[裝飾器工廠]的話(huà)靶累,可以通過(guò)下面的例子來(lái)觀察它們求值的順序:
function f() {
    console.log("f(): evaluated");
    return function (target, propertyKey: string, descriptor: PropertyDescriptor) {
        console.log("f(): called");
    }
}

function g() {
    console.log("g(): evaluated");
    return function (target, propertyKey: string, descriptor: PropertyDescriptor) {
        console.log("g(): called");
    }
}

class C {
    @f()
    @g()
    method() {}
}

輸出結(jié)果如下:

f(): evaluated
g(): evaluated
g(): called
f(): called

裝飾器求值順序:

  1. 參數(shù)裝飾器腺毫,然后依次是方法裝飾器,訪問(wèn)符裝飾器挣柬,或?qū)傩匝b飾器應(yīng)用到每個(gè)實(shí)例成員潮酒。
  2. 參數(shù)裝飾器,然后依次是方法裝飾器邪蛔,訪問(wèn)符裝飾器急黎,或?qū)傩匝b飾器應(yīng)用到每個(gè)靜態(tài)成員。
  3. 參數(shù)裝飾器應(yīng)用到構(gòu)造函數(shù)。
  4. 類(lèi)裝飾器應(yīng)用到類(lèi)叁熔。
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末委乌,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子荣回,更是在濱河造成了極大的恐慌遭贸,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,723評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件心软,死亡現(xiàn)場(chǎng)離奇詭異壕吹,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)删铃,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,485評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門(mén)耳贬,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人猎唁,你說(shuō)我怎么就攤上這事咒劲。” “怎么了诫隅?”我有些...
    開(kāi)封第一講書(shū)人閱讀 152,998評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵腐魂,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我逐纬,道長(zhǎng)蛔屹,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,323評(píng)論 1 279
  • 正文 為了忘掉前任豁生,我火速辦了婚禮兔毒,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘甸箱。我一直安慰自己育叁,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,355評(píng)論 5 374
  • 文/花漫 我一把揭開(kāi)白布芍殖。 她就那樣靜靜地躺著擂红,像睡著了一般。 火紅的嫁衣襯著肌膚如雪围小。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 49,079評(píng)論 1 285
  • 那天树碱,我揣著相機(jī)與錄音肯适,去河邊找鬼。 笑死成榜,一個(gè)胖子當(dāng)著我的面吹牛框舔,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 38,389評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼刘绣,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼樱溉!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起纬凤,我...
    開(kāi)封第一講書(shū)人閱讀 37,019評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤福贞,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后停士,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體挖帘,經(jīng)...
    沈念sama閱讀 43,519評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,971評(píng)論 2 325
  • 正文 我和宋清朗相戀三年恋技,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了拇舀。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,100評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡蜻底,死狀恐怖骄崩,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情薄辅,我是刑警寧澤要拂,帶...
    沈念sama閱讀 33,738評(píng)論 4 324
  • 正文 年R本政府宣布,位于F島的核電站长搀,受9級(jí)特大地震影響宇弛,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜源请,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,293評(píng)論 3 307
  • 文/蒙蒙 一枪芒、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧谁尸,春花似錦舅踪、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,289評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至决瞳,卻和暖如春货徙,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背皮胡。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,517評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工痴颊, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人屡贺。 一個(gè)月前我還...
    沈念sama閱讀 45,547評(píng)論 2 354
  • 正文 我出身青樓蠢棱,卻偏偏與公主長(zhǎng)得像锌杀,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,834評(píng)論 2 345