<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ǔ)(上)

          共 5315字,需瀏覽 11分鐘

           ·

          2021-08-28 11:00


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



          一、裝飾器的樣子


          我們先來看看Decorator裝飾器長什么樣子,大家可能沒在項目中用過Decorator裝飾器,但多多少少會看過下面裝飾器的寫法:


          /* Nest.Js cats.controller.ts */import { Controller, Get } from '@nestjs/common';
          @Controller('cats')export class CatsController { @Get() findAll(): string { return 'This action returns all cats'; }}

          摘自《Nest.Js》官方文檔(網(wǎng)址:https://docs.nestjs.cn/8/controllers)


          上述代碼大家可以不著急去理解,主要是讓大家對裝飾器有一個初步了解,后面我們會逐一分析Decorator裝飾器的實現(xiàn)原理以及具體用法。



          二、為什么要理解裝飾器


          (一)淺一點來說,理解才能讀懂VS Code源碼


          Decorator裝飾器是ECMAScript的語言提案,目前還處于stage-2階段(https://github.com/tc39/proposal-decorators),但是借助TypeScript或者Babel,已經(jīng)有大量的優(yōu)秀開源項目深度用上它了,比如:VS Code, Angular,Nest.Js(后端Node.js框架),TypeORM,Mobx(5) 等等。


          舉個例子:

          https://github.com/microsoft/vscode/blob/main/src/vs/workbench/services/editor/browser/codeEditorService.ts#L22




          作為一個有追求的程序員,你可能會問:上面代碼的裝飾器代表什么含義?去掉裝飾器后能不能正常運行?


          如果沒弄懂裝飾器,很難讀懂VS Code這些優(yōu)秀項目源碼的核心思想。所以說你不需要熟練使用裝飾器,但一定要理解裝飾器的用法。



          (二)深一點來說,理解才能弄懂AOP,IoC,DI等優(yōu)秀編程思想


          • AOP即面向切面編程 (Aspect Oriented Programming)

          AOP主要意圖是將日志記錄,性能統(tǒng)計,安全控制,異常處理等代碼從業(yè)務(wù)邏輯代碼中劃分出來,將它們獨立到非指導業(yè)務(wù)邏輯的方法中,進而改變這些行為的時候不影響業(yè)務(wù)邏輯的代碼。


          簡而言之,就是“優(yōu)雅”地把“輔助功能邏輯”從“業(yè)務(wù)邏輯”中分離,解耦出來。


          圖摘自《簡談前端開發(fā)中的AOP(一) -- 前端AOP的實現(xiàn)思路》

          (https://zhuanlan.zhihu.com/p/269504590)


          • IoC即控制反轉(zhuǎn) (Inversion of Control),是解耦的一種設(shè)計理念


          • DI即依賴注入 (Dependency Injection),是IoC的一種具體實現(xiàn)


          使用IoC前:




          使用IoC后:


          圖摘自《兩張圖讓你理解IoC(控制反轉(zhuǎn))》

          (https://learnku.com/laravel/t/3845/the-two-picture-lets-you-understand-ioc-inversion-of-control)


          IoC控制反轉(zhuǎn)的設(shè)計模式可以大幅度地降低了程序的耦合性。而Decorator裝飾器在VS Code的控制反轉(zhuǎn)設(shè)計模式里,其主要作用是實現(xiàn)DI依賴注入的功能和精簡部分重復的寫法。


          由于該步驟實現(xiàn)較為復雜,我們先從簡單的例子為切入點去了解裝飾器的基本原理。



          三、裝飾器的概念區(qū)分


          在理解裝飾器之前,有必要先對裝飾器的3個概念進行區(qū)分。


          (一)Decorator Pattern(裝飾器模式)


          是一種抽象的設(shè)計理念,核心思想是在不修改原有代碼情況下,對功能進行擴展。



          (二)Decorator(裝飾器)


          是一種特殊的裝飾類函數(shù),是一種對裝飾器模式理念的具體實現(xiàn)。



          (三)@Decorator(裝飾器語法)


          是一種便捷的語法糖(寫法),通過@來引用,需要編譯后才能運行。理解了概念之后可以知道:裝飾器的存在就是希望實現(xiàn)裝飾器模式的設(shè)計理念。

          說法1:在不修改原有代碼情況下,對功能進行擴展。也就是對擴展開放,對修改關(guān)閉。


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



          四、裝飾器的實戰(zhàn):記錄函數(shù)耗時


          現(xiàn)在有一個關(guān)羽(GuanYu)類,它有兩個函數(shù)方法:attack(攻擊)和run(奔跑):


          class GuanYu {  attack() {    console.log('揮了一次大刀')  }  run() {    console.log('跑了一段距離')  }}


          而我們都是優(yōu)秀的程序員,時時刻刻都有著經(jīng)營思維(性能優(yōu)化),因此想給關(guān)羽(GuanYu)的函數(shù)方法提前做好準備:


          記錄關(guān)羽的每一次attack(攻擊)和run(奔跑)的執(zhí)行時間,以便于后期做性能優(yōu)化。


          (一)復制粘貼,不用思考一把梭就是干


          拿到需求,不用多想,立刻在函數(shù)前后,添加記錄函數(shù)耗時的邏輯代碼,并復制粘貼到其他地方:


          class GuanYu {  attack() {+   const start = +new Date()    console.log('揮了一次大刀')+   const end = +new Date()+   console.log(`耗時: ${end - start}ms`)  }  run() {+   const start = +new Date()    console.log('跑了一段距離')+   const end = +new Date()+   console.log(`耗時: ${end - start}ms`)   }}


          但是這樣直接修改原函數(shù)代碼有以下幾個問題:


          • 理解成本高

          統(tǒng)計耗時的相關(guān)代碼與函數(shù)本身邏輯并無關(guān)系,對函數(shù)結(jié)構(gòu)造成了破壞性的修改,影響到了對原函數(shù)本身的理解。


          • 維護成本高

          如果后期還有更多類似的函數(shù)需要添加統(tǒng)計耗時的代碼,在每個函數(shù)中都添加這樣的代碼非常低效,也大大提高了維護成本。



          (二)裝飾器模式,不修改原代碼擴展功能


          • 裝飾器前置基礎(chǔ)知識


          在開始用裝飾器實現(xiàn)之前必須掌握以下基礎(chǔ):


          • Object.getOwnPropertyDescriptor()(https://developer.mozilla.org/zhCN/docs/Web/JavaScript/Reference/Global_Objects/Object/getOwnPropertyDescriptor)

          返回指定對象上一個自有屬性對應(yīng)的屬性描述符:


          var a = { b: () => {} }var descriptor = Object.getOwnPropertyDescriptor(a, 'b')console.log(descriptor)/** * { *   configurable: true,  // 可配置的 *   enumerable: true,    // 可枚舉的 *   value: () => {},     // 該屬性對應(yīng)的值(數(shù)值,對象,函數(shù)等) *   writable: true,      // 可寫入的 * } */


          這里要注意一個點是:value可以是JavaScript的任意值,比如函數(shù)方法,正則,日期等。


          • Object.defineProperty()https://developer.mozilla.org/zhCN/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty


          在一個對象上定義或修改一個屬性的描述符:


          const object1 = {};
          Object.defineProperty(object1, 'property1', { value: 'ThisIsNotWritable', writable: false});
          object1.property1 = 'newValue';// throws an error in strict mode
          console.log(object1.property1);// expected output: 'ThisIsNotWritable'


          • 【重點】手寫一個裝飾器函數(shù)


          有了上面的兩個基礎(chǔ)后,我們開始利用裝飾器模式的設(shè)計理念,用純函數(shù)的形式寫一個裝飾器,實現(xiàn)記錄函數(shù)耗時功能。為了讓大家更深刻理解裝飾器的原理,我們先不用@Decorator這個語法糖。


          下面代碼是本文的重點,大家可以放慢閱讀速度,理解后再繼續(xù)往下看:


          // 裝飾器函數(shù)function decoratorLogTime(target, key) {  const targetPrototype = target.prototype  // Step1 備份原來類構(gòu)造器上的屬性描述符 Descriptor  const oldDescriptor = Object.getOwnPropertyDescriptor(targetPrototype, key)
          // Step2 編寫裝飾器函數(shù)業(yè)務(wù)邏輯代碼 const logTime = function (...arg) { // Before 鉤子 let start = +new Date() try { // 執(zhí)行原來函數(shù) return oldDescriptor.value.apply(this, arg) // 調(diào)用之前的函數(shù) } finally { // After 鉤子 let end = +new Date() console.log(`耗時: ${end - start}ms`) } } // Step3 將裝飾器覆蓋原來的屬性描述符的 value Object.defineProperty(targetPrototype, key, { ...oldDescriptor, value: logTime })}

          class GuanYu { attack() { console.log('揮了一次大刀') } run() { console.log('跑了一段距離') }}// Step4 手動執(zhí)行裝飾器函數(shù),裝飾 GuanYu 的 attack 函數(shù)decoratorLogTime(GuanYu, 'attack')// Step4 手動執(zhí)行裝飾器函數(shù),裝飾 GuanYu 的 run 函數(shù)decoratorLogTime(GuanYu, 'run')

          const guanYu = new GuanYu()guanYu.attack()// 揮了一次大刀// 耗時: 0msguanYu.run()// 跑了一段距離// 耗時: 0ms


          以上就是裝飾器的具體實現(xiàn)方法,其核心思路是:


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

          利用Object.getOwnPropertyDescriptor獲取


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

          利用執(zhí)行原函數(shù)前后鉤子,添加耗時統(tǒng)計邏輯


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

          利用Object.defineProperty代理


          • Step4 手動執(zhí)行裝飾器函數(shù),裝飾Class(類)指定屬性

          從而實現(xiàn)在不修改原代碼的前提下,執(zhí)行額外邏輯代碼



           作者簡介


          阮易強(easonruan)

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

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



           推薦閱讀


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

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

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

          保姆級教程: c++游戲服務(wù)器嵌入v8 js引擎





          瀏覽 62
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <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>
                  国产欧美日韩在线 | 影音先锋 成人 | 国产综合婷婷色 | 男女操逼视频网站 | 大香蕉大香蕉视频网 |