<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>

          Javascript裝飾器原理

          共 9149字,需瀏覽 19分鐘

           ·

          2021-01-15 08:52

          一個(gè)以@開頭的描述性詞語。英語的decorator動(dòng)詞是decorate,裝飾的意思。其中詞根dek(dec發(fā)音)原始印歐語系中意思是“接受”。即,原來的某個(gè)事物接受一些新東西(而變得更好)。
          從另外一個(gè)角度描述,裝飾器主要是在被裝飾對(duì)象的外部起作用,而非入侵其內(nèi)部發(fā)生什么改變。裝飾器模式同時(shí)也是一種開發(fā)模式,其地位雖然弱于MVC、IoC等,但不失為一種優(yōu)秀的模式。
          JavaScript的裝飾器可能是借鑒自Python也或許是Java。較為明顯的不同的是大部分語言的裝飾器必須是一行行分開,而js的裝飾器可以在一行中。

          裝飾器存在的意義

          會(huì)偷懶的程序員,才是優(yōu)秀的程序員。

          舉個(gè)例子:我拿著員工卡進(jìn)入公司總部大樓。因?yàn)槊總€(gè)員工所屬的部門、級(jí)別不同,并不能進(jìn)入大樓的任何房間。每個(gè)房間都有一扇門;那么,公司需要安排每個(gè)辦公室里至少一個(gè)人關(guān)于驗(yàn)證來訪者的工作:

          1. 先登記來訪者

          2. 驗(yàn)證是否有權(quán)限進(jìn)入,如果沒有則要求其離開

          3. 記錄其離開時(shí)間

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

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

          function A101(who){  record(who,new Date(),'enter');  if (!permission(who)) {    record(who,new Date(),'no permission')    return void;  }  // 繼續(xù)執(zhí)行  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; } // 繼續(xù)執(zhí)行 doSomeWork(); record(who,new Date(),'leave')}
          // ...

          有經(jīng)驗(yàn)的大家肯定第一時(shí)間想到了,把那些重復(fù)語句封裝為一個(gè)方法,并統(tǒng)一調(diào)用。是的,這樣可以解決大部分問題,但是還不夠“優(yōu)雅”。同時(shí)還有另外一個(gè)問題,如果“房間”特別多,又或者只有大樓奇數(shù)號(hào)房間要驗(yàn)證偶數(shù)不驗(yàn)證,那豈不是很“變態(tài)”?如果使用裝飾器模式來做,代碼會(huì)如下面這樣的:

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

          verify是驗(yàn)證的裝飾器,而其本質(zhì)就是一組函數(shù)。

          JavaScript裝飾器

          正如先前的那個(gè)例子,裝飾器其實(shí)本身就是一個(gè)函數(shù),它在執(zhí)行被裝飾的對(duì)象之前先被執(zhí)行。

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

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

          • 字段

          • 方法

          • 參數(shù)

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

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

          創(chuàng)建一個(gè).babelrc配置文件,如下:

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

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

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

          類裝飾器

          創(chuàng)建一個(gè)使用裝飾器的JS程序decorate-class.js

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

          以上是最最簡(jiǎn)單的裝飾器程序,我們利用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行就是在類生成過程中,調(diào)用函數(shù)形態(tài)的裝飾器,并將構(gòu)造函數(shù)(類本身)送入其中。同樣揭示了裝飾器的第一個(gè)參數(shù)是類的構(gòu)造函數(shù)的由來。

          方法 (method)裝飾器

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

          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=========");}

          然后轉(zhuǎn)換代碼,可以發(fā)現(xiàn),這次代碼量突然增大了很多。排除掉_classCallCheck、_defineProperties和_createClass三個(gè)函數(shù),關(guān)注_applyDecoratedDescriptor函數(shù):

          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;}

          它在生成構(gòu)造函數(shù)之后,執(zhí)行了這個(gè)函數(shù),特別注意,這個(gè)裝飾器函數(shù)是以數(shù)組形式的參數(shù)傳遞的。然后到上述代碼的17~22行,將裝飾器逐個(gè)應(yīng)用,其中對(duì)裝飾器的調(diào)用就在第21行。

          它發(fā)送了3個(gè)參數(shù),target指類本身。property指方法名(或者屬性名),desc是可能被先前裝飾器被處理過的descriptor,如果是第一次循環(huán)或只有一個(gè)裝飾器,那么就是方法或?qū)傩员旧淼膁escriptor。

          存取器(accessor)裝飾

          JS關(guān)于類的定義中,支持get和set關(guān)鍵字針對(duì)設(shè)置某個(gè)字段的讀寫操作邏輯,裝飾器也同樣支持這類方法的操作。

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

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

          屬性裝飾器

          繼續(xù)修改源代碼:

          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=========");}

          轉(zhuǎn)換后的代碼,還是與上述屬性、存取器的代碼非常接近。但除了_applyDecoratedDescriptor外,還多了一個(gè)_initializerDefineProperty函數(shù)。這個(gè)函數(shù)在生成構(gòu)造函數(shù)時(shí),將聲明的各種字段綁定給對(duì)象。

          參數(shù)裝飾器

          參數(shù)裝飾器的使用位置較之前集中裝飾器略有不同,它被使用在行內(nèi)。

          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=========");}

          轉(zhuǎn)換后的代碼區(qū)別就比較明顯了,babel并沒有對(duì)其生成一個(gè)特定的函數(shù)對(duì)其進(jìn)行特有的操作,而只在創(chuàng)建完類(構(gòu)造函數(shù))以及相關(guān)屬性、方法后直接調(diào)用了開發(fā)者自己編寫的裝飾器函數(shù):

          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;}();

          裝飾器應(yīng)用

          使用參數(shù)——閉包

          以上所有的案例,裝飾器本身均沒有使用任何參數(shù)。然實(shí)際應(yīng)用中,經(jīng)常會(huì)需要有特定的參數(shù)需求。我們?cè)倩氐揭婚_頭的例子中verify(who),其中需要傳入一個(gè)身份變量。哪又怎么做?我們少許改變一下類裝飾器的代碼:

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

          轉(zhuǎn)換后得到

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

          請(qǐng)注意第4第5行,它先執(zhí)行了裝飾器,然后再用返回值將類(構(gòu)造函數(shù))送入。相對(duì)應(yīng)的,我們就應(yīng)該將構(gòu)造函數(shù)寫成下面這樣:

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

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

          裝飾器包裹方法

          到此,我們已經(jīng)可以將裝飾器參數(shù)與目標(biāo)對(duì)象結(jié)合起來,進(jìn)行一些邏輯類的操作。那么再回到文章的開頭的例子中:需求中要先驗(yàn)證來訪者權(quán)限,然后記錄,最后在來訪者離開時(shí)再做一次記錄。此時(shí)需要監(jiān)管對(duì)象方法被調(diào)用的整個(gè)過程。

          請(qǐng)大家留意那個(gè)方法裝飾器的descriptor,我們可以利用這個(gè)對(duì)象來“重寫”這個(gè)方法。

          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行定義一個(gè)新的方法,20~25行,記錄、驗(yàn)證和記錄離開的動(dòng)作。

          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

          裝飾順序

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

          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

          還可以通過閱讀轉(zhuǎn)換后的源代碼得到執(zhí)行順序:

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

          2. 參數(shù)裝飾器(在生成構(gòu)造函數(shù)最里層)

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

          總結(jié)

          裝飾器是一種優(yōu)雅的開發(fā)模式,極大的方便了開發(fā)者編碼過程,同時(shí)提升了代碼的可讀性。我們?cè)谑褂醚b飾器開發(fā)時(shí),還是非常有必要了解其運(yùn)行機(jī)理。

          本文完?


          瀏覽 55
          點(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>
                  亚洲最新中文字幕 | www.91九色 | 国产精品秘 国产A级 | 日皮视频在线播放 | 自拍超碰|