JavaScript中的裝飾器--Decorator

玉鴦發表於2018-03-21

什麼是Decorator

JavaScript中的裝飾器--Decorator

  修飾模式(Decortaor),是物件導向程式設計領域中,一種動態地往一個類中新增新的行為的設計模式。就功能而言,修飾模式相比生成子類更為靈活,這樣可以給某個物件而不是整個類新增一些功能。

Decorator的作用

  通過使用修飾模式,可以在執行時擴充一個類的功能。原理是:增加一個修飾類包裹原來的類,包裹的方式一般是通過在將原來的物件作為修飾類的建構函式的引數。裝飾類實現新的功能,但是,在不需要用到新功能的地方,它可以直接呼叫原來的類中的方法。修飾類必須和原來的類有相同的介面。
  修飾模式是類繼承的另外一種選擇。類繼承在編譯時候增加行為,而裝飾模式是在執行時增加行為。
  當有幾個相互獨立的功能需要擴充時,這個區別就變得很重要。在有些>物件導向的程式語言中,類不能在執行時被建立,通常在設計的時候也不能預測到有哪幾種功能組合。這就意味著要為每一種組合建立一個新類。相反,修飾模式是面向執行時候的物件例項的,這樣就可以在執行時根據需要進行組合。一個修飾模式的示例是JAVA裡的Java I/O Streams的實現。

  上面兩段是維基百科中對於Decorator裝飾器模式的介紹.簡單來說.Decorator就是一種動態地往一個類中新增新的行為的設計模式,它可以在類執行時,擴充套件一個類的功能.並且去修改類本身的屬性和方法.使其可以在不同類之間更靈活的共用一些屬性和方法.下面就讓我們來看下在ES中Decorator的用法.

Decorator的用法

類本身的修飾

  在ES中Decorator的具體表現形式為:

一個求值結果為函式的表示式,接受目標物件、名稱和裝飾器描述作為引數,可選地返回一個裝飾器描述來安裝到目標物件上。

所以說在ES中Decorator的最終本質就是一個函式,這個函式通過接受目標物件的三個引數: 所裝飾的類的本身所裝飾的類的某個屬性的key值所裝飾的類的某個屬性的描述物件.並通過對這三個引數的操作,已達到為類擴充套件功能的目的.下面然我們來用具體程式碼來演示一下:

@eat
class Person {
  constructor() {}
}

function eat(target, key, descriptor) {
  console.log('吃飯');
  console.log(target);
  console.log(key);
  console.log(descriptor);
  target.prototype.act = '我要吃飯';
}

const jack = new Person();
console.log(jack.act);

// 吃飯
// [Function: Person]
// undefined
// undefined
// 我要吃飯
複製程式碼

  上面是一個最簡單的裝飾器的運用.我們首先宣告一個類Person,然後在宣告一個裝飾器函式eat,在eat中將傳入的三個引數分別列印出來,並將第一個引數target的原型prototype上新增一個屬性act,並賦值為'我要吃飯'.然後將函式eat作為裝飾在Person這個類本身上.最後,構造一個Person的例項jack,並列印jack上的act屬性.

  然後從下面的執行結果中我們可以看出,程式碼中會先列印出'吃飯',然後是引數target,其次是引數key,再然後是引數descriptor.最後才是jackact屬性.這是因為裝飾器對類的行為的改變,是程式碼編譯時發生的,而不是在執行時。這意味著,裝飾器能在編譯階段執行程式碼。也就是說,裝飾器本質就是編譯時執行的函式。

類的屬性的修飾

  看了上面那段程式碼的執行結果,你可能會用這麼一個疑問.Decorator所傳進來的三個引數: targetkeydescriptor.為什麼只有target有值,而keydescriptor則都是undefined了.事實上這是因為你將裝飾器Decorator裝飾在類本身上所導致的.在ES中裝飾器並不僅僅只能裝飾在類本身上,也可以裝飾在類的屬性上.當裝飾在類的屬性上時.keydescriptor也就有了用武之地.請看下面這段程式碼:

class Person {
    constructor() {}

    @test
    name() {console.log('張三');}
}

function test(target, key, descriptor) {
  console.log(target);
  console.log(key);
  console.log(descriptor);
}

const student = new Person();

student.name();

// Person {}
// name
// { value: [Function: name], writable: true, enumerable: false, configurable: true }
// 張三
複製程式碼

  在上面程式碼中我們將test裝飾器裝飾在Person類的name屬性上.然後列印三個傳入的入參.分別得到了我們期望的結果.而通過這三個引數,我們就可以對我們要裝飾的物件進行一些有趣的修改, 如下面這樣:

class Person {

  constructor() {}
    @test
    name() {console.log('張三');}
}

function test(target, key, descriptor) {
  descriptor.value = function () {
    console.log('李四');
  }
}

const student = new Person();

student.name();

// 李四
複製程式碼

  在上面程式碼中,我們給一個類Person的原型上賦值了一個屬性name,其值為一個函式,執行時會列印張三兩個字.然後我們給屬性name裝飾了test這個裝飾器.在test裝飾器中,將傳入進來的descriptor物件上的value賦值為一個函式,執行時列印李四兩個字.最後構造一個例項student,並執行name方法,執行的結果是列印了李四兩個字,這說明通過裝飾器,我們完全可以在不改變一個類本身的請況下對一個類的屬性進行改寫.這使得在不同類中共享同一方法這一操作相當簡單,且優雅.需要共享的使用使用裝飾器,不需要的時候移除裝飾器.完全不用對類的本身進行操作.

  對於裝飾器,如果我們感到,固定傳入的三個引數不夠用的話,我們也可以自行傳入引數只需要像下面這麼寫:


function rename(name) {
  return function(target, key, descriptor) {
    descriptor.value = name;
  }
}

複製程式碼

前面已經說過,在ES中**Decorator就是一個求值結果為函式的表示式,**所以,只要你最後的返回結果是一個函式.都是一個合法的裝飾器.

Decorator的相容性

  目前ES中Decorator還處於提案階段,各大瀏覽器和node,均未公開支援這一特性.如果想要使用,則需要借用babel的一個外掛babel-plugin-transform-decorators才可以.

結束

  上面是我對Decorator的一些見解,希望對大家有所幫助.如果文中任有何不當之處請予以斧正,謝謝

參考資料:

我的個人網址: www.wangyiming19950222.com

相關文章