decorator 學習小結

  • 2019 年 12 月 4 日
  • 筆記

decorator 學習小結

1. decorator 是什麼

decorator 是裝飾者,是 ES7 的語法。

decorator 本質是一個 wrapper,可以動態增強【類】,【實例方法】的能力

被裝飾者對於裝飾者是毫無感知的

讀者如果感興趣,可以深入學習【裝飾者模式】

要注意的是,在 ES7 中,decorator 只是一個語法糖而已,和 class 這些一樣

可以參考 babel 轉換之後的 ES5 程式碼

2. decorator 的用法

2.1. 作用在類方法上

decorator 可以在定義類的時候,作用在類方法上,見下面的例子:

function readonly(target, key, descriptor) {      descriptor.writable = false;      return descriptor;  }    class People {      @readonly      sayName() {          console.log(this.name);      }  }    var p = new People();  p.sayName = 'hello';

執行上面的程式碼,會報錯,如下:

decorator 本質是一個 wrapper 函數,當 decorator 作用在類方法的時候

它的簽名是function(target, key, descriptor)

  • target: 類原型,也就是 People.prototype
  • key: 屬性名,也就是sayName
  • descriptor: 屬性的描述符

可以發現,decorator 的簽名和 Object.defineProperty 的簽名是一樣的

本質上來說,作用在類方法的 decorator 就是對 Object.defineProperty 的 wrapper

2.2. 作用在類上

decorator 可以在定義類的時候,作用在類上面,對類進行增強

function nonTestable(target) {      target.canTest = false;  }    @nonTestable  class People {}    console.log(People.canTest); // false

當 decorator 作用在類上時,只會傳入一個參數,就是類本身

2.3. decorator 也可以是工廠函數

當同一個 decorator 在作用在不同目標上面時,有不同的表現時,可以把 decorator 定義為一個工廠函數,詳見下面的例子:

function addName(name) {      return function(target) {          target.myName = name;      };  }    @addName('people')  class People {      static say() {          console.log(People.myName);      }  }    @addName('dog')  class Dog {      static say() {          console.log(Dog.myName);      }  }    People.say(); // people  Dog.say(); // dog

3. 如何使用 decorator?

既然是 ES7 的新語法,那麼如何使用 decorator?

目前需要使用 babel 來進行轉換才能使用,具體方法如下:

.babelrc文件如下:

{    "presets": ["es2015", "stage-1"],    "plugins": [      "babel-plugin-transform-decorators-legacy"    ]  }

然後需要安裝插件:

npm i babel-cli babel-preset-es2015 babel-preset-stage-1 babel-plugin-transform-decorators

安裝完成之後,就可以使用 babel 來轉換帶 decorator 的程式碼了:

babel index.js > index.es5.js

4. 應用 decorator

實際上,decorator 有非常廣泛的運用場景,這裡我簡單舉兩個例子

4.1. 利用 decorator 實現 mixin

廢話少說,直接舉例子:

function nameMixin(target) {      target.prototype.setName = function(name) {          this.name = name;          return this;      };        target.prototype.sayName = function() {          console.log(this.name);      };  }    function ageMixin(target) {      target.prototype.setAge = function(age) {          this.age = age;          return this;      };        target.prototype.sayAge = function() {          console.log(this.age);      };  }    @nameMixin  @ageMixin  class People {}    var p = new People();  p.setName('peter').sayName(); // peter  p.setAge(18).sayAge(); // 18

可以對比一下以前的 mixin 使用方式:

class People extends mixin(ageMixin, nameMixin) {}  // or  class People {}  mixin(People, ageMixin, nameMixin);

可以看到,使用 decorator 之後,程式碼簡單明了了許多

4.2. 利用 decorator 實現 AOP

function aop(before, after) {      return function(target, key, descriptor) {          const method = descriptor.value;          descriptor.value = (...args) => {              let ret;                before && before(...args);              ret = method.apply(target, args);              if (after) {                  ret = after(ret);              }                return ret;          };      };  }    function beforeTest1(opt) {      opt.name = opt.name + ', haha';  }    function beforeTest2(...args) {}    function afterTest2(ret) {      console.log('haha, add 10!');      return ret + 10;  }    class Test {      @aop(beforeTest1)      static test1(opt) {          console.log(`hello, ${opt.name}`);      }        @aop(beforeTest2, afterTest2)      static test2(...args) {          return args.reduce((a, b) => a + b);      }  }    /**   * out:   *   * hello, peter, haha   * haha, add 10!   * 16   *   */  Test.test1({      name: 'peter'  });  console.log(Test.test2(1, 2, 3));

這裡只是實現了一個簡單的 aop 例子

大家可以看到,使用了 decorator 之後,程式碼非常清晰

5. 小結

decorator 是 ES7 的新語法,本質上來說,它就是一個語法糖,但是缺非常有用

任何裝飾者模式的程式碼都可以通過 decorator 來實現

使用了 decorator 之後,程式碼會變得清晰明了

相信不久的將來,ES7 語法將會流行,但是在這之前,你仍然可以通過 babel 來使用 decorator

快來享受 decorator 帶來的簡潔吧!