久久精品精选,精品九九视频,www久久只有这里有精品,亚洲熟女乱色综合一区
    分享

    Javascript裝飾器原理

     新進小設計 2021-08-28

    一個以@開頭的描述性詞語。英語的decorator動詞是decorate,裝飾的意思。其中詞根dek(dec發音)原始印歐語系中意思是“接受”。即,原來的某個事物接受一些新東西(而變得更好)。從另外一個角度描述,裝飾器主要是在被裝飾對象的外部起作用,而非入侵其內部發生什么改變。裝飾器模式同時也是一種開發模式,其地位雖然弱于MVC、IoC等,但不失為一種優秀的模式。

    JavaScript的裝飾器可能是借鑒自Python也或許是Java。較為明顯的不同的是大部分語言的裝飾器必須是一行行分開,而JS的裝飾器可以在一行中。

    裝飾器存在的意義

    會偷懶的程序員,才是優秀的程序員。

    舉個例子:我拿著員工卡進入公司總部大樓。因為每個員工所屬的部門、級別不同,并不能進入大樓的任何房間。每個房間都有一扇門;那么,公司需要安排每個辦公室里至少一個人關于驗證來訪者的工作:

    1. 先登記來訪者

    2. 驗證是否有權限進入,如果沒有則要求其離開

    3. 記錄其離開時間

    還有一個選擇方式,就是安裝電子門鎖,門鎖只是將員工卡的信息傳輸給機房,由特定的程序驗證。

    前者暫且稱之為笨模式,代碼如下:

    function A101(who){
      record(who,new Date(),'enter');
      if (!permission(who)) {
        record(who,new Date(),'no permission')
        return void;
      }
      // 繼續執行
      doSomeWork();
      record(who,new Date(),'leave')
    }
    
    function A102(who){
    record(who,new Date(),'enter');
      if (!permission(who)) {
        record(who,new Date(),'no permission')
        return void;
      }
      // 繼續執行
      doSomeWork();
      record(who,new Date(),'leave')
    }
    
    // ...

    有經驗的大家肯定第一時間想到了,把那些重復語句封裝為一個方法,并統一調用。是的,這樣可以解決大部分問題,但是還不夠“優雅”。同時還有另外一個問題,如果“房間”特別多,又或者只有大樓奇數號房間要驗證偶數不驗證,那豈不是很“變態”?如果使用裝飾器模式來做,代碼會如下面這樣的:

    @verify(who)
    class Building {
      @verify(who)
      A101(){/*...*/}
      @verify(who)
      A102(){/*...*/}
      //...
    }

    verify是驗證的裝飾器,而其本質就是一組函數。

    JavaScript裝飾器

    正如先前的那個例子,裝飾器其實本身就是一個函數,它在執行被裝飾的對象之前先被執行。

    在JavaScript中,裝飾器的類型有:

    • 存取方法(屬性的get和set)

    • 字段

    • 方法

    • 參數

    由于目前裝飾器概念還處于提案階段,不是一個正式可用的JS功能,所以想要使用這個功能,不得不借助翻譯器工具,例如Babel工具或者TypeScript編譯JS代碼轉后才能被執行。我們需要先搭建運行環境,配置一些參數。(以下過程,假設已經正確安裝了NodeJS開發環境以及包管理工具)

    cd project && npm init
    npm i -D @babel/cli @babel/core @babel/plugin-proposal-class-properties @babel/plugin-proposal-decorators @babel/preset-env babel-plugin-parameter-decorator

    創建一個.babelrc配置文件,如下:

    {
      "presets": ["@babel/preset-env"],
      "plugins": [
        ["@babel/plugin-proposal-decorators", { "legacy": true }],
        ["@babel/plugin-proposal-class-properties", { "loose": true }],
        "babel-plugin-parameter-decorator"
      ]
    }

    利用下面的轉換命令,我們可以得到ES5的轉換程序:

    npx babel source.js --out-file target.js

    類裝飾器

    創建一個使用裝飾器的JS程序decorate-class.js

    @classDecorator
    class Building {
      constructor() {
        this.name = "company";
      }
    }
    
    const building = new Building();
    
    function classDecorator(target) {
      console.log("target", target);
    }

    以上是最最簡單的裝飾器程序,我們利用babel將其“翻譯”為ES5的程序,然后再美化一下后得到如下程序

    "use strict";
    
    var _class;
    
    function _classCallCheck(instance, Constructor) {
      if (!(instance instanceof Constructor)) {
        throw new TypeError("Cannot call a class as a function");
      }
    }
    
    var Building =
      classDecorator(
        (_class = function Building() {
          _classCallCheck(this, Building);
    
          this.name = "company";
        })
      ) || _class;
    
    var building = new Building();
    
    function classDecorator(target) {
      console.log("target", target);
    }

    第12行就是在類生成過程中,調用函數形態的裝飾器,并將構造函數(類本身)送入其中。同樣揭示了裝飾器的第一個參數是類的構造函數的由來。

    方法 (method)裝飾器

    稍微修改一下代碼,依舊是盡量保持最簡單:

    class Building {
      constructor() {
        this.name = "company";
      }
      @methodDecorator
      openDoor() {
        console.log("The door being open");
      }
    }
    
    const building = new Building();
    
    function methodDecorator(target, property, descriptor) {
      console.log("target", target);
      if (property) {
        console.log("property", property);
      }
      if (descriptor) {
        console.log("descriptor", descriptor);
      }
      console.log("=====end of decorator=========");
    }

    然后轉換代碼,可以發現,這次代碼量突然增大了很多。排除掉_classCallCheck_defineProperties_createClass三個函數,關注_applyDecoratedDescriptor函數:

    function _applyDecoratedDescriptor(
      target,
      property,
      decorators,
      descriptor,
      context
    ) {
      var desc = {};
      Object.keys(descriptor).forEach(function (key) {
        desc[key] = descriptor[key];
      });
      desc.enumerable = !!desc.enumerable;
      desc.configurable = !!desc.configurable;
      if ("value" in desc || desc.initializer) {
        desc.writable = true;
      }
      desc = decorators
        .slice()
        .reverse()
        .reduce(function (desc, decorator) {
          return decorator(target, property, desc) || desc;
        }, desc);
      if (context && desc.initializer !== void 0) {
        desc.value = desc.initializer ? desc.initializer.call(context) : void 0;
        desc.initializer = undefined;
      }
      if (desc.initializer === void 0) {
        Object.defineProperty(target, property, desc);
        desc = null;
      }
      return desc;
    }

    它在生成構造函數之后,執行了這個函數,特別注意,這個裝飾器函數是以數組形式的參數傳遞的。然后到上述代碼的17~22行,將裝飾器逐個應用,其中對裝飾器的調用就在第21行。它發送了3個參數,target指類本身。property指方法名(或者屬性名),desc是可能被先前裝飾器被處理過的descriptor,如果是第一次循環或只有一個裝飾器,那么就是方法或屬性本身的descriptor。

    存取器(accessor)裝飾

    JS關于類的定義中,支持get和set關鍵字針對設置某個字段的讀寫操作邏輯,裝飾器也同樣支持這類方法的操作。

    class Building {
      constructor() {
        this.name = "company";
      }
      @propertyDecorator
      get roomNumber() {
        return this._roomNumber;
      }
    
      _roomNumber = "";
      openDoor() {
        console.log("The door being open");
      }
    }

    有心的讀者可能已經發現了,存取器裝飾的代碼與上面的方法裝飾代碼非常接近。關于屬性 get和set方法,其本身也是一種方法的特殊形態。所以他們之間的代碼就非常接近了。

    屬性裝飾器

    繼續修改源代碼:

    class Building {
      constructor() {
        this.name = "company";
      }
      @propertyDecorator
      roomNumber = "";
    }
    
    const building = new Building();
    
    function propertyDecorator(target, property, descriptor) {
      console.log("target", target);
      if (property) {
        console.log("property", property);
      }
      if (descriptor) {
        console.log("descriptor", descriptor);
      }
      console.log("=====end of decorator=========");
    }

    轉換后的代碼,還是與上述屬性、存取器的代碼非常接近。但除了_applyDecoratedDescriptor外,還多了一個_initializerDefineProperty函數。這個函數在生成構造函數時,將聲明的各種字段綁定給對象。

    參數裝飾器

    參數裝飾器的使用位置較之前集中裝飾器略有不同,它被使用在行內。

    class Building {
      constructor() {
        this.name = "company";
      }
      openDoor(@parameterDecorator num, @parameterDecorator zoz) {
        console.log(`${num} door being open`);
      }
    }
    
    const building = new Building();
    
    function parameterDecorator(target, property, key) {
      console.log("target", target);
      if (property) {
        console.log("property", property);
      }
      if (key) {
        console.log("key", key);
      }
      console.log("=====end of decorator=========");
    }

    轉換后的代碼區別就比較明顯了,babel并沒有對其生成一個特定的函數對其進行特有的操作,而只在創建完類(構造函數)以及相關屬性、方法后直接調用了開發者自己編寫的裝飾器函數:

    var Building = /*#__PURE__*/function () {
      function Building() {
        _classCallCheck(this, Building);
    
        this.name = "company";
      }
    
      _createClass(Building, [{
        key: "openDoor",
        value: function openDoor(num, zoz) {
          console.log("".concat(num, " door being open"));
        }
      }]);
    
      parameterDecorator(Building.prototype, "openDoor", 1);
      parameterDecorator(Building.prototype, "openDoor", 0);
      return Building;
    }();

    裝飾器應用

    使用參數——閉包

    以上所有的案例,裝飾器本身均沒有使用任何參數。然實際應用中,經常會需要有特定的參數需求。我們再回到一開頭的例子中verify(who),其中需要傳入一個身份變量。哪又怎么做?我們少許改變一下類裝飾器的代碼:

    const who = "Django";
    @classDecorator(who)
    class Building {
      constructor() {
        this.name = "company";
      }
    }

    轉換后得到

    // ...
    var who = "Django";
    var Building =
      ((_dec = classDecorator(who)),
      _dec(
        (_class = function Building() {
          _classCallCheck(this, Building);
    
          this.name = "company";
        })
      ) || _class);
    // ...

    請注意第4第5行,它先執行了裝飾器,然后再用返回值將類(構造函數)送入。相對應的,我們就應該將構造函數寫成下面這樣:

    function classDecorator(people) {
      console.log(`hi~ ${people}`);
      return function (target) {
        console.log("target", target);
      };
    }

    同樣的,方法、存取器、屬性和參數裝飾器均是如此。

    裝飾器包裹方法

    到此,我們已經可以將裝飾器參數與目標對象結合起來,進行一些邏輯類的操作。那么再回到文章的開頭的例子中:需求中要先驗證來訪者權限,然后記錄,最后在來訪者離開時再做一次記錄。此時需要監管對象方法被調用的整個過程。

    請大家留意那個方法裝飾器的descriptor,我們可以利用這個對象來“重寫”這個方法。

    class Building {
      constructor() {
        this.name = "company";
      }
    
      @methodDecorator("Gate")
      openDoor(firstName, lastName) {
        return `The door will be open, when ${firstName} ${lastName} is walking into the ${this.name}.`;
      }
    }
    
    let building = new Building();
    console.log(building.openDoor("django", "xiang"));
    
    function methodDecorator(door) {
      return function (target, property, descriptor) {
        let fn = descriptor.value;
        descriptor.value = function (...args) {
          let [firstName, lastName] = args;
          console.log(`log: ${firstName}, who are comming.`);
          // verify(firstName,lastName)
          let result = Reflect.apply(fn, this, [firstName, lastName]);
          console.log(`log: ${result}`);
          console.log(`log: ${firstName}, who are leaving.`);
          return result;
        };
        return descriptor;
      };
    }

    代碼第17行,將原方法暫存;18行定義一個新的方法,20~25行,記錄、驗證和記錄離開的動作。

    log: Django, who are comming.
    log: The door will be open, when Django Xiang is walking in to the company.
    log: Django, who are leaving.
    The door will be open, when Django Xiang is walking in to the company

    裝飾順序

    通過閱讀轉換后的代碼,我們知道裝飾器工作的時刻是在類被實例化之前,在生成之中完成裝飾函數的動作。那么,如果不同類型的多個裝飾器同時作用,其過程是怎樣的?我們將先前的案例全部整合到一起看看:

    const who = "Django";
    @classDecorator(who)
    class Building {
      constructor() {
        this.name = "company";
      }
    
      @propertyDecorator
      roomNumber = "";
    
      @methodDecorator
      openDoor(@parameterDecorator num) {
        console.log(`${num} door being open`);
      }
    
      @accessorDecorator
      get roomNumber() {
        return this._roomNumber;
      }
    }
    
    const building = new Building();
    
    function classDecorator(people) {
      console.log(`class decorator`);
      return function (target) {
        console.log("target", target);
      };
    }
    
    function methodDecorator(target, property, descriptor) {
      console.log("method decorator");
    }
    
    function accessorDecorator(target, property, descriptor) {
      console.log("accessor decorator");
    }
    
    function propertyDecorator(target, property, descriptor) {
      console.log("property decoator");
    }
    
    function parameterDecorator(target, property, key) {
      console.log("parameter decorator");
    }
    1. class decorator

    2. parameter decorator

    3. property decoator

    4. method decorator

    5. accessor decorator

    還可以通過閱讀轉換后的源代碼得到執行順序:

    1. 類裝飾器(在最外層)

    2. 參數裝飾器(在生成構造函數最里層)

    3. 按照出現的先后順序的:屬性、方法和存取器

    總結

    裝飾器是一種優雅的開發模式,極大的方便了開發者編碼過程,同時提升了代碼的可讀性。我們在使用裝飾器開發時,還是非常有必要了解其運行機理。

    本文由博客群發一文多發等運營工具平臺 OpenWrite 發布

      本站是提供個人知識管理的網絡存儲空間,所有內容均由用戶發布,不代表本站觀點。請注意甄別內容中的聯系方式、誘導購買等信息,謹防詐騙。如發現有害或侵權內容,請點擊一鍵舉報。
      轉藏 分享 獻花(0

      0條評論

      發表

      請遵守用戶 評論公約

      類似文章 更多

      主站蜘蛛池模板: 亚洲乱理伦片在线观看中字| 国产福利在线观看免费第一福利 | 日本中文字幕有码在线视频| 亚洲欧美日韩愉拍自拍美利坚| 麻花传媒剧国产MV免费播放| 推油少妇久久99久久99久久| 漂亮人妻中文字幕丝袜| 九九久久精品国产| 久青草国产在视频在线观看| 亚洲综合无码精品一区二区三区| 日本一区二区不卡精品| 东北女人毛多水多牲交视频| 日韩精品国产中文字幕| 日本A级视频在线播放| 狠狠亚洲色一日本高清色| 三上悠亚久久精品| 国产永久免费高清在线观看| 激情综合婷婷色五月蜜桃| 亚洲色一色噜一噜噜噜| 人人妻人人澡人人爽欧美精品| 野外做受三级视频| 国产精品IGAO视频网网址| 国产亚洲综合欧美视频| 制服 丝袜 亚洲 中文 综合| 亚洲AV永久无码天堂网一线| 久久精品国产再热青青青| 国产偷国产偷亚洲清高| 精品一区二区三区在线成人| 亚洲av免费成人在线| 国产精品国产精品国产专区不卡 | 18禁无遮挡啪啪无码网站破解版| 九九久久精品国产| 亚洲伊人五月丁香激情| 99精品国产中文字幕| 日韩AV片无码一区二区不卡电影| 亚洲高清国产拍精品5G| 精品人妻日韩中文字幕| 国产精品国产自线拍免费软件| 在线天堂最新版资源| 97久久超碰亚洲视觉盛宴| 一区二区三区国产不卡|