爲何個人裝飾器沒別人的強?

裝飾器(Decorator)是一種設計模式,容許向一個對象添加功能,可是又不改變其內部結構。裝飾器只是ES7中提案,目前處於Stage 2階段,可是不久的未來就會變成規範。它主要用來修飾類以及類屬性。前端

本文總共分爲五個部分:react

  • 修飾類
  • 修飾類屬性
  • 三個參數
  • 在React的應用
  • Babel編譯

修飾類

@isPerson
class Person {}

function isPerson(target) {
  target.prototype.isPerson = true;
}

const person1 = new Person();
console.log(person1.isPerson); // true
複製代碼

上面有一個Person類,咱們寫了一個isPerson裝飾器修飾它,最終咱們實例化Person時,實例上多了isPerson屬性。git

@isPerson(true)
class Person {}

function isPerson(bol) {
  return function(target) {
    target.prototype.isPerson = true;
  }
}
複製代碼

一樣咱們也能夠給裝飾器isPerson添加參數,而裝飾器內容就會多一層結構,return對一個對象。因此裝飾器是支持傳參和不傳參的。es6

本質上能夠把Person看做一個方法,而實際上裝飾器就是將方法當參數傳入。在Babel編譯我會講解裝飾器的本質。github

isPerson(true)(function Person() {})
複製代碼

修飾類屬性

class Person {
  firstName = 'Peter';
  lastName = 'Cheng';

  @readonly
  realName() { return `${this.firstName} ${this.lastName}` };
}

function readonly(target, name, descriptor) {
  descriptor.writable = false;
}

const person2 = new Person();
console.log(person2.realName = 1);
複製代碼

同時給類PersonrealName方法添加了readonly裝飾器,當輸出實例的realName屬性時,程序會報錯,Uncaught TypeError: Cannot assign to read only property 'realName' of object '#<Person>'。注意,這裏實際上修飾的是類方法,裝飾器目前不能修飾類裏面的變量,好比firstNamelastName設計模式

三個參數

  • target

若是修飾類,那target就是目標自己,第一個例子中就是Person類。若是你修飾的是類方法,那target就是類實例。數組

  • name

類名或者類方法名稱,一樣第一個例子,打印出來就是Personbash

  • descriptor

屬性的描述對象。它具備以下幾個屬性,valueenumerableconfigurablewritable。value是修飾對象自己,而其餘值和Object.defineProperty的屬性同樣,控制值的行爲。babel

{
   value: ƒ realName(),
   enumerable: false,
   configurable: true,
   writable: false
 };
複製代碼

在React的應用

裝飾器在React中的應用咱們隨處看見,好比Redux,自定義HOC等,其實這些都是高階函數的應用。app

下面咱們實現一個打點裝飾器,當咱們觸發postClick方法時,會輸出一個打點的log,最終會輸出postClick 2。咱們經過攔截value,並重寫value,將參數id打印了出來。

class App extends Component {
  @analytic()
  postClick(id = 1) {}
}

function analytic(args) {
  return function decorator(target, name, descriptor) {
    if (typeof descriptor === 'undefined') {
      throw new Error('@analytic decorator can only be applied to class methods');
    }

    const value = descriptor.value;
    function newValue(...args) {
      console.log(`${name} ${args}`);
      return value.bind(this)(args);
    };

    
    return {
      ...descriptor,
      value: newValue
    };
  }
}

const app = new App();
app.postClick(2);
複製代碼

Babel編譯

咱們選擇修飾類方法的例子,看一下最簡單的裝飾器若是編譯成ES5代碼會是怎麼樣。能夠用Babel官方的網址 babeljs.io/repl,看一下第一個例子中代碼被編譯成什麼樣子。

class Person {
  firstName = 'Peter';
  lastName = 'Cheng';

  @readonly
  realName() { return `${this.firstName} ${this.lastName}` };
}

function readonly(target, name, descriptor) {
  descriptor.writable = false;
}

const person2 = new Person();
console.log(person2.realName = 1);
複製代碼

編譯以後

function _decorate(decorators, factory, superClass, mixins) {}

// 省略中間一大推

var Person = _decorate(null, function (_initialize) {
  var Person = function Person() {
    _classCallCheck(this, Person);

    _initialize(this);
  };

  return {
    F: Person,
    d: [{
      kind: "field",
      key: "firstName",
      value: function value() {
        return 'Peter';
      }
    }, {
      kind: "field",
      key: "lastName",
      value: function value() {
        return 'Cheng';
      }
    }, {
      kind: "method",
      decorators: [readonly],
      key: "realName",
      value: function realName() {
        return "".concat(this.firstName, " ").concat(this.lastName);
      }
    }]
  };
});

function readonly(target, name, descriptor) {
  descriptor.writable = false;
}

var person2 = new Person();
console.log(person2.realName = 1);
複製代碼

其實主要看Person對象有那些變化,Babel將類編譯成了ES5的function,而且外面套一層裝飾器,可是裝飾器最終仍是賦值給Person變量。內部Person對象最終返回一個對象,而key爲realName的對象有一個decorators,它是一個數組。咱們看看decorators作了什麼。其實就是遍歷數組,將參數最終映射到Object.defineProperty,操做對象的可寫入等屬性。

結束語

經過本文咱們知道了裝飾器是什麼,而且用來作什麼,以及實質是什麼。最近看了一部電影《斯隆女士》,裏面就有一個片斷闡述專業性的重要性,做爲前端,首先須要掌握的就是ES相關的知識。終於整理完裝飾器的知識了,我最近正在用Node + Flutter作一個App,最終計劃是發佈上線,敬請期待。

我用create-react-app生成了一個項目,能夠直接使用裝飾器。github.com/carrollcai/…

參考:

es6.ruanyifeng.com/#docs/decor…


寫做時間: 20190922

相關文章
相關標籤/搜索