<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          一文讀懂@Decorator裝飾器——理解VS Code源碼的基礎(chǔ)(下)

          共 14798字,需瀏覽 30分鐘

           ·

          2021-08-28 10:59


          導(dǎo)語 | 本人在讀VS Code源碼的時(shí)候,發(fā)現(xiàn)其用了大量的@Decorator裝飾器語法,由于對(duì)裝飾器的語法比較陌生,它成為了我理解VS Code的攔路虎。其實(shí)不止VS Code,Angular、Node.js框架Nest.js、TypeORM、Mobx(5) 和Theia等都深度用到了裝飾器語法,為了讀懂各大優(yōu)秀開源項(xiàng)目,讓我們先一起來把@Decorator裝飾器的原理以及用法徹底弄懂。



          一、@Decorator裝飾器語法糖


          文讀懂@Decorator裝飾器——理解VS Code源碼的基礎(chǔ)(上)》中手寫的裝飾器函數(shù)存在兩個(gè)可優(yōu)化的點(diǎn):


          • 是否可以讓裝飾器函數(shù)更關(guān)注業(yè)務(wù)邏輯?


          文讀懂@Decorator裝飾器——理解VS Code源碼的基礎(chǔ)(上)》Step1, Step2是通用邏輯的,每個(gè)裝飾器都需要實(shí)現(xiàn),簡單來說就是可復(fù)用的。

          • 是否可以讓裝飾器寫法更簡單?


          純函數(shù)實(shí)現(xiàn)的裝飾器,每裝飾一個(gè)屬性都要手動(dòng)執(zhí)行裝飾器函數(shù),詳細(xì)內(nèi)容文讀懂@Decorator裝飾器——理解VS Code源碼的基礎(chǔ)(上)》中Step4步驟。

           

          針對(duì)上述優(yōu)化點(diǎn),裝飾器草案中有一顆特別甜的語法糖,也就是@Decorator,它能夠幫你省去很多繁瑣的步驟來用上裝飾器。只需要在想使用的裝飾器前加上@符號(hào),裝飾器就會(huì)被應(yīng)用到目標(biāo)上。


           (一)@Decorator語法糖的便捷性


          下面我們用@Decorator的寫法,來實(shí)現(xiàn)同樣的功能,看看代碼量可以精簡多少:


          // Step2 編寫裝飾器函數(shù)業(yè)務(wù)邏輯代碼function logTime(target, key, descriptor) {  const oldMethed = descriptor.value  const logTime = function (...arg) {    let start = +new Date()    try {      return oldMethed.apply(this, arg) // 調(diào)用之前的函數(shù)    } finally {      let end = +new Date()      console.log(`耗時(shí): ${end - start}ms`)    }  }  descriptor.value = logTime  return descriptor}
          class GuanYu { // Step4 利用 @ 語法糖裝飾指定屬性 @logTime attack() { console.log('揮了一次大刀') } // Step4 利用 @ 語法糖裝飾指定屬性 @logTime run() { console.log('跑了一段距離') }}
          const guanYu = new GuanYu()guanYu.attack()// [LOG]: 揮了一次大刀// [LOG]: 耗時(shí): 3msguanYu.run()// [LOG]: 跑了一段距離// [LOG]: 耗時(shí): 3ms


          為了讓大家更直觀了解上述代碼是否可以編譯后正常執(zhí)行,我們可以從TypeScript Playground(https://www.typescriptlang.org/play) 直接看到編譯后的代碼以及運(yùn)行結(jié)果。


          注意!為了方便理解,記得關(guān)閉配置emitDecoratorMetadata禁止輸出元數(shù)據(jù),元數(shù)據(jù)是另一個(gè)比較復(fù)雜的知識(shí)點(diǎn),我們本篇文章先跳過。關(guān)閉后編譯的代碼會(huì)更簡單!


          我們打開上面代碼的在線Playground鏈接,點(diǎn)擊Run運(yùn)行按鈕,即可看到其正常運(yùn)行和輸出結(jié)果:



          對(duì)比純手寫的裝飾器,用@Decorator語法糖可以省去2個(gè)重復(fù)的步驟:


          • Step1 備份原來類構(gòu)造器(Class.prototype)的屬性描述符(Descriptor)


          const oldDescriptor = Object.getOwnPropertyDescriptor(targetPrototype, key)


          • Step3 用裝飾器函數(shù)覆蓋原來屬性描述符的value


          Object.defineProperty(targetPrototype, key, {  ...oldDescriptor,  value: logTime})


          開發(fā)者僅需兩步即可實(shí)現(xiàn)裝飾器的功能,可以更專注于裝飾器本身的業(yè)務(wù)邏輯:


          • Step2 編寫裝飾器函數(shù)業(yè)務(wù)邏輯代碼


          function logTime(target, key, descriptor) {  const oldMethed = descriptor.value  const logTime = function (...arg) {    let start = +new Date()    try {      return oldMethed.apply(this, arg) // 調(diào)用之前的函數(shù)    } finally {      let end = +new Date()      console.log(`耗時(shí): ${end - start}ms`)    }  }  descriptor.value = logTime  return descriptor}


          • Step4 利用@語法糖裝飾指定屬性


          @logTimeattack() {  console.log('揮了一次大刀')}



          (二)分析@Decorator語法糖編譯后的代碼【重點(diǎn)】


          @Decorator語法糖很甜,但卻不能直接食用。因?yàn)檠b飾器目前僅僅是ECMAScript的語言提案,還處于stage-2階段。

          (https://github.com/tc39/proposal-decorators)


          無論是最新版的Chrome瀏覽器還是Node.js都不能直接運(yùn)行帶有@Decorator語法糖的代碼。


          我們需要借助TypeScript或者Babel的能力,將源碼編譯后才能正常運(yùn)行。而在TypeSciprt Playground上,我們可以直接看到編譯后代碼。


          為了更清晰容易理解,我們把編譯后的業(yè)務(wù)代碼先注釋掉,只看裝飾器實(shí)現(xiàn)的相關(guān)代碼:


          "use strict";// Part1 裝飾器工具函數(shù)(__decorate)的定義var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {    var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;    if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);    else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;    return c > 3 && r && Object.defineProperty(target, key, r), r;};
          function logTime(target, key, descriptor) { // ...}
          class GuanYu { // ...}
          // Part2 裝飾器工具函數(shù)(__decorate)的執(zhí)行__decorate([logTime], GuanYu.prototype, "attack", null);__decorate([logTime], GuanYu.prototype, "run", null);
          // ...


          上述代碼核心點(diǎn)是兩個(gè)部分,一個(gè)是定義,一個(gè)是執(zhí)行。定義部分較為復(fù)雜,我們先從執(zhí)行入手:

           

          Part2 裝飾器工具函數(shù)(__decorate)的執(zhí)行會(huì)傳入以下4個(gè)參數(shù):


          1. 裝飾器業(yè)務(wù)邏輯函數(shù)

          2. 類的構(gòu)造器

          3. 類的構(gòu)造器屬性名

          4. 屬性描述符(可以為null)

           

          為了方便理解Part1裝飾器工具函數(shù)__decorate的定義,我們需要來精簡__decorate的函數(shù)代碼,讓它變成最簡單的樣子,而精簡代碼的前提是收集條件:


          • 條件1 (this&&this.__decorate) 可刪除


          這里的this是指window對(duì)象,這一步的含義是避免重復(fù)定義__decorate函數(shù),屬于輔助代碼,可刪掉。

           

          • 條件2 c<3===false


          Part1的c=arguments.length代表參數(shù)的個(gè)數(shù),由Part2我們知道工具函數(shù)會(huì)傳入4個(gè)參數(shù),因此在本次案例中c<3參數(shù)個(gè)數(shù)小于3的情況不存在,即c<3===false。

           

          • 條件3 c>3===true


          本次案例中c>3參數(shù)大于3的情況存在,即c>3===true。

           

          • 條件4 desc===null

          同時(shí)在Part1我們知道第四個(gè)參數(shù)desc傳入的值就是null,即desc===null。

           

          • 條件5 typeof Reflect!=="object"

          Reflect反射是ES6的語法,本文為了更容易理解,暫不引入新的ES6特性和語法,讓環(huán)境默認(rèn)為ES5,即不存在Reflect對(duì)象,即typeof Reflect!=="object"。

           

          有了上述條件后,我們可以進(jìn)一步精簡__decorate的方法:


          代碼片段1:


          r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc
          // 根據(jù) c < 3 === false , desc === null 條件// 精簡后
          r = desc = Object.getOwnPropertyDescriptor(target, key)// r 和 desc 此時(shí)代表的是屬性的描述符 Descriptor


          代碼片段2:


          if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
          // 根據(jù) c < 3 === false , c > 3 === true 條件// 精簡后
          if (d = decorators[i]) r = d(target, key, r) || r;


          代碼片段3:


          if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
          // 為了方便理解,本案例暫認(rèn)為 Reflect 不存在// 精簡后
          // 空


          代碼片段4:


          return c > 3 && r && Object.defineProperty(target, key, r), r;
          // 根據(jù) c > 3 === true, r 是屬性描述符,必定存在// 精簡后
          Object.defineProperty(target, key, r)return r;


          精簡后整體代碼:


          var __decorate = function (decorators, target, key, desc) {    var c = arguments.length;    // Step1 備份原來類構(gòu)造器 (Class.prototype) 的屬性描述符 (Descriptor)    var r = desc = Object.getOwnPropertyDescriptor(target, key),    var d;      for (var i = decorators.length - 1; i >= 0; i--) {      // d 為裝飾器業(yè)務(wù)邏輯函數(shù)      if (d = decorators[i]) {        // 執(zhí)行 d,并傳入 target 類構(gòu)造器,key 屬性名,r 屬性描述符        r = d(target, key, r) || r;      }    }      // Step3 用裝飾器函數(shù)覆蓋原來屬性描述符    Object.defineProperty(target, key, r)    return r;};


          代碼經(jīng)過精簡之后核心原理還是和我們文讀懂@Decorator裝飾器——理解VS Code源碼的基礎(chǔ)(上)》手寫一個(gè)裝飾器函數(shù)的原理是一樣的。


          • Step1 備份原來類構(gòu)造器 (Class.prototype) 的屬性描述符(Descriptor)

          利用Object.getOwnPropertyDescriptor獲取


          • Step3 用裝飾器函數(shù)覆蓋原來屬性描述符的value

          利用Object.defineProperty代理


          TypeScript對(duì)裝飾器編譯后的代碼,只不過是把裝飾器可復(fù)用的邏輯抽離成一個(gè)工具函數(shù),方便復(fù)用而已。


          分析到這里,是不是對(duì)@Decorator裝飾器最根本的實(shí)現(xiàn)有了更深入的了解?

           

          從上面的例子,我們也進(jìn)一步驗(yàn)證了:


          1. Decorator Pattern裝飾器模式的設(shè)計(jì)理念:在不修改原有代碼情況下,對(duì)功能進(jìn)行擴(kuò)展。

          2. Decorator裝飾器的具體實(shí)現(xiàn),本質(zhì)是函數(shù),參數(shù)有target, key, descriptor。

          3. @Decoretor是裝飾器的一種語法糖,只是一種便捷寫法,編譯后本質(zhì)還是一個(gè)函數(shù)。

           


          二、帶參數(shù)的裝飾器:裝飾器工廠函數(shù)


          文讀懂@Decorator裝飾器——理解VS Code源碼的基礎(chǔ)(上)》的「記錄函數(shù)耗時(shí)」例子中,如果我們希望在日志前面加個(gè)可變的標(biāo)簽,如何實(shí)現(xiàn)?


          答案是使用帶參數(shù)的裝飾器。


          重點(diǎn):logTime是個(gè)高階函數(shù),可以理解成裝飾器工廠函數(shù),其接收參數(shù)執(zhí)行后,返回一個(gè)裝飾器函數(shù)。

           

          function logTime(tag) { // 這是一個(gè)裝飾器工廠函數(shù)
          return function(target, key, descriptor) { // 這是裝飾器 const oldMethed = descriptor.value const logTime = function (...arg) { let start = +new Date() try { return oldMethed.apply(this, arg) } finally { let end = +new Date() console.log(`【${tag}】耗時(shí): ${end - start}ms`) } } descriptor.value = logTime return descriptor }}
          class GuanYu { @logTime('攻擊') attack() { console.log('揮了一次大刀') }, @logTime('奔跑') run() { console.log('跑了一段距離') }}
          // ...


          編譯后:


          // ...
          __decorate([logTime('攻擊')], GuanYu.prototype, "attack", null);__decorate([logTime('奔跑')], GuanYu.prototype, "run", null);
          // ...


          看了編譯后的代碼,我們就很容易知道帶參數(shù)裝飾器的具體實(shí)現(xiàn)原理,無非是直接先執(zhí)行裝飾器工廠函數(shù),此時(shí)傳入對(duì)應(yīng)參數(shù),然后返回一個(gè)新的裝飾器業(yè)務(wù)邏輯的函數(shù)。



          三、五種裝飾器


          我們前面學(xué)了那么多裝飾器的內(nèi)容,其實(shí)只學(xué)了一種裝飾器:方法裝飾器。


          而裝飾器一共有5種類型可被我們使用:

          1. 類裝飾器

          2. 屬性裝飾器

          3. 方法裝飾器

          4. 訪問器裝飾器

          5. 參數(shù)裝飾器

          先來個(gè)全家福,然后我們逐一攻破:


          // 類裝飾器@classDecoratorclass GuanYu {
          // 屬性裝飾器 @propertyDecorator name: string; // 方法裝飾器 @methodDecorator attack ( // 參數(shù)裝飾器 @parameterDecorator meters: number ) {} // 訪問器裝飾器 @accessorDecorator get horse() {}}


          (一)類裝飾器


          類型聲明:


          // 類裝飾器function classDecorator(target: any) {  return // ...};


          • @參數(shù):只接受一個(gè)參數(shù)


          target: 類的構(gòu)造器


          • @返回:如果類裝飾器返回了一個(gè)值,她將會(huì)被用來代替原有的類構(gòu)造器的聲明

           

          因此,類裝飾器適合用于繼承一個(gè)現(xiàn)有類并添加一些屬性和方法。


          例如我們可以添加一個(gè)addToJsonString方法給所有的類來新增一個(gè)toString方法:


          function addToJSONString(target) {  return class extends target {    toJSONString() {      return JSON.stringify(this);    }  };}
          @addToJSONStringclass C { public foo = "foo"; public num = 24;}
          console.log(new C().toJSONString())// [LOG]: "{"foo":"foo","num":24}"



          (二)方法裝飾器


          已經(jīng)在前面章節(jié)介紹過利用方法裝飾器來實(shí)現(xiàn)「記錄函數(shù)耗時(shí)」功能,現(xiàn)在我們重新復(fù)習(xí)下。


          類型聲明:


          // 方法裝飾器function methodDecorator(target: any, propertyKey: string, descriptor: PropertyDescriptor) {  return // ...};


          • @參數(shù):

          1. target: 對(duì)于靜態(tài)成員來說是類的構(gòu)造器,對(duì)于實(shí)例成員來說是類的原型鏈。

          2. propertyKey: 屬性的名稱。

          3. descriptor: 屬性的描述器。

          https://developer.mozilla.org/zh- CN/docs/Web/JavaScript/Reference/Global_Objects/Object/getOwnPropertyDescriptor


          • @返回:如果返回了值,它會(huì)被用于替代屬性的描述器。

           

          利用方法裝飾器我們可以實(shí)現(xiàn)更多的具體場(chǎng)景,比如「打印Request的請(qǐng)求參數(shù)和結(jié)果」功能:


          function loggerParamsResult(target, propertyKey, descriptor) {  const original = descriptor.value;
          descriptor.value = async function (...args) { let result let error try { result = await original.call(this, ...args); } catch(e) { error = new Error(e) } if (error) { console.error('請(qǐng)求失?。?) console.error('請(qǐng)求參數(shù): ', ...args) console.error('失敗原因: ', error) } else { console.log('請(qǐng)求成功!') console.log('請(qǐng)求參數(shù)', ...args) console.log('請(qǐng)求結(jié)果: ', result) } return result; }}
          class App { @loggerParamsResult request(data) { return new Promise((resolve, reject) => { const random = Math.random() > 0.5 if (random) { resolve(random) } else { reject(random) } }) }}
          const app = new App();app.request({ url: 'https://www.tencent.com'});
          // [LOG]: "請(qǐng)求成功!" // [LOG]: "請(qǐng)求參數(shù)", {// "url": "https://www.tencent.com"// } // [LOG]: "請(qǐng)求結(jié)果: ", true

          // [ERR]: "請(qǐng)求失??!" // [ERR]: "請(qǐng)求參數(shù): ", {// "url": "https://www.tencent.com"// } // [ERR]: "失敗原因: ", false


          總結(jié):無論是「記錄函數(shù)耗時(shí)」還是「打印Request的請(qǐng)求參數(shù)和結(jié)果」,本質(zhì)都是在實(shí)現(xiàn)Before/After鉤子,因此我們只需要記住方法裝飾器可以實(shí)現(xiàn)與Before/After鉤子相關(guān)的場(chǎng)景功能。



          (三)屬性裝飾器


          類型聲明:


          // 屬性裝飾器function propertyDecorator(target: any, propertyKey: string) {
          }


          • @參數(shù): 只接受兩個(gè)參數(shù),少了descriptor描述器


          1. target: 對(duì)于靜態(tài)成員來說是類的構(gòu)造器,對(duì)于實(shí)例成員來說是類的原型鏈。

          2. propertyKey: 屬性的名稱。

          • @返回: 返回的結(jié)果將被忽略

           

          利用屬性裝飾器,我們可以實(shí)現(xiàn)一個(gè)非常簡單的屬性監(jiān)聽功能,當(dāng)屬性改變時(shí)觸發(fā)指定函數(shù):


          function observable(fnName) {  // 裝飾器工廠函數(shù)  return function (target: any, key: string): any {  // 裝飾器    let prev = target[key];    Object.defineProperty(target, key, {      set(next) {        target[fnName](prev, next);        prev = next;      }    })  }}
          class Store { @observable('onCountChange') count = -1;
          onCountChange(prev, next) { console.log('>>> count has changed!') console.log('>>> prev: ', prev) console.log('>>> next: ', next) }}
          const store = new Store();store.count = 10
          // [LOG]: ">>> count has changed!" // [LOG]: ">>> prev: ", undefined // [LOG]: ">>> next: ", -1 // [LOG]: ">>> count has changed!" // [LOG]: ">>> prev: ", -1 // [LOG]: ">>> next: ", 10



          (四)訪問器裝飾器


          訪問器裝飾器總體上講和方法裝飾器很接近,唯一的區(qū)別在于第三個(gè)參數(shù)descriptor描述器中有的key不同:


          • 方法裝飾器的描述器的key為:

          1. value

          2. writable

          3. enumerable

          4. configurable

          • 訪問器裝飾器的描述器的key為:

          1. get

          2. set

          3. enumerable

          4. configurable


          類型聲明:


          // 訪問器裝飾器function methodDecorator(target: any, propertyKey: string, descriptor: PropertyDescriptor) {  return // ...};


          例如,我們可以將某個(gè)屬性在賦值的時(shí)候做一層代理,額外相加一個(gè)值:


          function addExtraNumber(num) {  // 裝飾器工廠函數(shù)  return function (target, propertyKey, descriptor) { // 裝飾器    const original = descriptor.set;
          descriptor.set = function (value) { const newObj = {} Object.keys(value).forEach(key => { newObj[key] = value[key] + num }) return original.call(this, newObj) } }}
          class C { private _point = { x: 0, y: 0 }
          @addExtraNumber(2) set point(value: { x: number, y: number }) { this._point = value; }
          get point() { return this._point; }}
          const c = new C();c.point = { x: 1, y: 1 };
          console.log(c.point)
          // [LOG]: {// "x": 3,// "y": 3// }



          (五)參數(shù)裝飾器


          類型聲明:


          // 參數(shù)裝飾器function parameterDecorator(target: any, methodKey: string, parameterIndex: number) {  }


          • @參數(shù):接收三個(gè)參數(shù)

          1. target: 對(duì)于靜態(tài)成員來說是類的構(gòu)造器,對(duì)于實(shí)例成員來說是類的原型鏈。

          2. methodKey: 方法的名稱,注意!是方法的名稱,而不是參數(shù)的名稱。

          3. parameterIndex: 參數(shù)在方法中所處的位置的下標(biāo)。

          @返回:返回的值將會(huì)被忽略

           

          單獨(dú)的參數(shù)裝飾器能做的事情很有限,它一般都被用于記錄可被其它裝飾器使用的信息。


          function Log(target, methedKey, parameterIndex) {  console.log(`方法名稱 ${methedKey}`);  console.log(`參數(shù)順序 ${parameterIndex}`);}
          class GuanYu { attack(@Log person, @Log dog) { console.log(`向 ${person} 揮了一次大刀`) }}
          // [LOG]: "方法名稱 attack" // [LOG]: "參數(shù)順序 0"



          (六)裝飾器參數(shù)總結(jié)




          四、裝飾器順序


          (一)同種裝飾器組合順序:洋蔥模型


          如果同一個(gè)方法有多個(gè)裝飾器,其執(zhí)行順序是怎樣的?


          答案:以方法裝飾器為例,同種裝飾器組合后,其順序會(huì)像剝洋蔥一樣,先從外到內(nèi)進(jìn)入,然后由內(nèi)向外執(zhí)行。和Koa的中間件順序類似。


          function dec(id){  console.log('裝飾器初始化', id);  return function (target, property, descriptor) {    console.log('裝飾器執(zhí)行', id);  }}
          class Example { @dec(1) @dec(2) method(){}}
          // 裝飾器初始化 1// 裝飾器初始化 2// 裝飾器執(zhí)行 2// 裝飾器執(zhí)行 1



          其原理,看編譯后的代碼就非常清楚:


          重點(diǎn):


          1. dec(1), dec(2)初始化時(shí)就執(zhí)行。

          2. for(var i=decorators.length-1; i>=0;i--) 是從右向左,倒敘執(zhí)行。


          // 由于本段代碼不存在 c < 3 (參數(shù)少于3個(gè)) 的情況,為了方便理解已精簡了部分不可能執(zhí)行的代碼var __decorate = function (decorators, target, key, desc) {    var c = arguments.length,         r = desc = Object.getOwnPropertyDescriptor(target, key),        d;    for (var i = decorators.length - 1; i >= 0; i--)       if (d = decorators[i]) r = d(target, key, r) || r;    Object.defineProperty(target, key, r)    return r;};
          function dec(id) { console.log('裝飾器初始化', id); return function (target, property, descriptor) { console.log('裝飾器執(zhí)行', id); };}class Example { method() { }}__decorate([ dec(1), dec(2)], Example.prototype, "method", null);
          // 裝飾器初始化 1// 裝飾器初始化 2// 裝飾器執(zhí)行 2// 裝飾器執(zhí)行 1



          (二)不同類型裝飾器順序:有規(guī)則有規(guī)律


          • 實(shí)例成員:(參數(shù)>方法) 訪問器 屬性 裝飾器 (按順序)

          • 靜態(tài)成員:(參數(shù)>方法) 訪問器 屬性 裝飾器 (按順序)

          • 構(gòu)造器:參數(shù)裝飾器

          • 類裝飾器

          多種裝飾器優(yōu)先級(jí)為:實(shí)例成員最高,內(nèi)部成員里面的裝飾器則按定義順序執(zhí)行,依次排下來,類裝飾器最低。


          function f(key: string): any {  // console.log("初始化: ", key);  return function () {    console.log("執(zhí)行: ", key);  };}
          @f("8. 類")class C { @f("4. 靜態(tài)屬性") static prop?: number;
          @f("5. 靜態(tài)方法") static method(@f("6. 靜態(tài)方法參數(shù)") foo) {}
          constructor(@f("7. 構(gòu)造器參數(shù)") foo) {}
          @f("2. 實(shí)例方法") method(@f("1. 實(shí)例方法參數(shù)") foo) {}
          @f("3. 實(shí)例屬性") prop?: number;}
          // "執(zhí)行: ", "1. 實(shí)例方法參數(shù)" // "執(zhí)行: ", "2. 實(shí)例方法" // "執(zhí)行: ", "3. 實(shí)例屬性" // "執(zhí)行: ", "4. 靜態(tài)屬性" // "執(zhí)行: ", "6. 靜態(tài)方法參數(shù)" // "執(zhí)行: ", "5. 靜態(tài)方法" // "執(zhí)行: ", "7. 構(gòu)造器參數(shù)" // "執(zhí)行: ", "8. 類"



          五、裝飾器總結(jié)


          (一)應(yīng)用場(chǎng)景


          裝飾器很像是組合一系列函數(shù),類似于高階函數(shù)和類。合理利用裝飾器對(duì)一些非內(nèi)部邏輯相關(guān)的代碼進(jìn)行封裝提煉,能夠幫助我們快速完成重復(fù)性的工作,節(jié)省時(shí)間,極大提高開發(fā)效率。


          1. 類裝飾器

            可添加額外的方法和屬性,比如:擴(kuò)展toJSONString方法。

          2. 方法裝飾器

            可實(shí)現(xiàn)Before/After鉤子功能,比如:記錄函數(shù)耗時(shí),打印request參數(shù)結(jié)果,節(jié)流防抖。

          3. 屬性裝飾器

            可監(jiān)聽屬性改變觸發(fā)其他事件,比如:實(shí)現(xiàn)count監(jiān)聽器。

          4. 訪問器裝飾器

          5. 參數(shù)裝飾器

           

          當(dāng)然,還有更多可以使用裝飾器的場(chǎng)景等著我們?nèi)グl(fā)現(xiàn):

          • 運(yùn)行時(shí)類型檢查

          • 依賴注入



          (二)優(yōu)點(diǎn)


          • 在不修改原有代碼情況下,對(duì)功能進(jìn)行擴(kuò)展。也就是對(duì)擴(kuò)展開放,對(duì)修改關(guān)閉

          • 優(yōu)雅地把“輔助性功能邏輯”從“業(yè)務(wù)邏輯”中分離,解耦出來。(AOP 面向切面編程的設(shè)計(jì)理念)

          • 裝飾類和被裝飾類可以獨(dú)立發(fā)展,不會(huì)相互耦合。

          • 裝飾模式是Class繼承的一個(gè)替代模式,可以理解成組合。

          (三)缺點(diǎn)

          但是糖再好吃,也不要吃太多,容易壞牙齒的,濫用過多裝飾器會(huì)導(dǎo)致很多問題:


          • 理解成本:過多帶業(yè)務(wù)功能的裝飾器會(huì)使代碼本身邏輯變得撲朔迷離。


          • 調(diào)試成本:裝飾器層次增多,會(huì)增加調(diào)試成本,很難追溯到一個(gè)Bug是在哪一層包裝導(dǎo)致的。



          (四)注意事項(xiàng)


          • 裝飾器的功能邏輯代碼一定是輔助性的。

          比如日志記錄,性能統(tǒng)計(jì)等,這樣才符合AOP面向切面編程的思想,如果把過多的業(yè)務(wù)邏輯寫在了裝飾器上,效果會(huì)適得其反。

          • 裝飾器語法尚未定案以及未被納入ES標(biāo)準(zhǔn),標(biāo)準(zhǔn)化的過程還需要很長時(shí)間。

          由于裝飾器語法未來制定的標(biāo)準(zhǔn)可能與當(dāng)前的裝飾器實(shí)現(xiàn)方案有所不同,Mobx6出于兼容性的考慮,放棄了裝飾器的用法,并建議使用makeObservable/makeAutoObservable來進(jìn)行代替。

          https://zh.mobx.js.org/observable-state.html) 


          詳情請(qǐng)查看:https://zh.mobx.js.org/enabling-decorators.html

          裝飾器提案進(jìn)度:https://github.com/tc39/proposal-decorators



           作者簡介


          阮易強(qiáng)(easonruan)

          騰訊高級(jí)前端開發(fā)工程師

          騰訊高級(jí)前端開發(fā)工程師,曾負(fù)責(zé)過「粵省事」、「穗康」等大型小程序項(xiàng)目,目前是WeDa微搭低代碼平臺(tái)(專有版)的核心開發(fā)人員,有豐富低代碼平臺(tái)研發(fā)經(jīng)驗(yàn)。



           推薦閱讀


          一文讀懂@Decorator裝飾器——理解VS Code源碼的基礎(chǔ)(上)

          go語言最全優(yōu)化技巧總結(jié),值得收藏!

          如何用函數(shù)式編程思想優(yōu)化業(yè)務(wù)代碼,這就給你安排上!

          拒絕代碼臃腫,這套計(jì)算引擎設(shè)計(jì)方法值得一看!





          瀏覽 105
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  无码一区二区免费 | 99久久久无码国产精精品品不卡 | 在线a黄网站 | 国产高清免费视频 | 内射在线视频免费 |