聊聊Typescript中的設計模式——裝飾器篇(decorators)


  隨着Typescript的普及,在KOA2和nestjs等nodejs框架中常常看到相似於java spring中註解的寫法。本文從裝飾模式出發,聊聊Typescipt中的裝飾器和註解。javascript

  • 什麼是裝飾者模式
  • Typescript中的裝飾器
  • Typescript中的註解
  • 總結

原文地址在:github.com/fortheallli…java

歡迎starnode

1、什麼是裝飾者模式

  最近在看nestjs等支持Typescript的node框架,常常看到這樣一種寫法:git

import { Controller, Get } from '@nestjs/common';

@Controller('cats')

export class CatsController {
  @Get()
  findAll() {
    return 'This action returns all cats';
  }
}
複製代碼

  上述代碼定義了一個處理url爲「/cats」的控制器,該控制器對於url爲「/cats」的get方法執行findAll()函數,返回相應的字符串。angularjs

  在上述的代碼中,用@Controller('cats')修飾CatsController類,經過@Get來修飾類中的findAll方法,這就是典型的裝飾者模式。經過@Controller('cats')和@Get修飾後的類CatsController,簡單來講,就是擁有了豐富的「內涵」。github

下面看看具體裝飾者模式的定義:web

咱們知道繼承模式是豐富子元素「內涵」的一種重要方式,無論是繼承接口仍是子類繼承基類。而裝飾者模式能夠在不改變繼承關係的前提下,包裝先有的模塊,使其內涵更加豐富,並不會影響到原來的功能。與繼承相比,更加的靈活。spring

javascript中的裝飾器處於建議徵集的第二階段,經過babel和Typescrit均可以實現裝飾器的語法。編程

2、Typescript中的裝飾器

Typescript中的裝飾器與類相關,分別能夠修飾類的實例函數和靜態函數、類自己、類的屬性、類中函數的參數以及類的set/get存取器,下面來意義介紹。babel

(1)、類方法的裝飾器

下面來介紹一下用裝飾器來修飾函數,首先來看一個例子:

let temple;
function log(target, key, descriptor) {
  console.log(`${key} was called!`);
  temple = target;
}
class P {
    @log
    foo() {
      console.log('Do something');
    }
}

const p = new P()
p.foo()
console.log(P.prototype === temple) //true
複製代碼

上述是實例方法foo中咱們用log函數修飾,log函數接受三個參數,經過P.prototype === temple(target)能夠判斷,在類的實例函數的裝飾器函數第一個參數爲類的原型,第二個參數爲函數名自己,第三個參數爲該函數的描述屬性。

具體總結以下,對於類的函數的裝飾器函數,依次接受的參數爲:

  • target:若是修飾的是類的實例函數,那麼target就是類的原型。若是修飾的是類的靜態函數,那麼target就是類自己。
  • key: 該函數的函數名。
  • descriptor:該函數的描述屬性,好比 configurable、value、enumerable等。

從上述的例子中咱們能夠看到,用裝飾器來修飾相應的類的函數十分方便:

@log
foo() {
  ...
}
複製代碼

(2)、類的裝飾器

裝飾函數也能夠直接修飾類:

let temple
function foo(target){
   console.log(target);
   temple = target
}
@foo
class P{
   constructor(){
     
   }
}

const p = new P();
temple === P //true
複製代碼

當裝飾函數直接修飾類的時候,裝飾函數接受惟一的參數,這個參數就是該被修飾類自己。上述的例子中,輸出的target就是類P的自己。

此外,在修飾類的時候,若是裝飾函數有返回值,該返回值會從新定義這個類,也就是說當裝飾函數有返回值時,實際上是生成了一個新類,該新類經過返回值來定義。

舉例來講:

function foo(target){
   return class extends target{
      name = 'Jony';
      sayHello(){
         console.log("Hello "+ this.name)
      }
   }
}
@foo
class P{
   constructor(){
     
   }
}

const p = new P();
p.sayHello(); // 會輸出Hello Jony
複製代碼

上面的例子能夠看到,當裝飾函數foo有返回值時,實際上P類已經被返回值所表明的新類所代替,所以P的實例p擁有sayHello方法。

(3)、類的屬性的裝飾器

下面咱們來看類的屬性的裝飾器,裝飾函數修飾類的屬性時,在類實例化的時候調用屬性的裝飾函數,舉例來講:

function foo(target,name){
   console.log("target is",target);
   console.log("name is",name)
}
class P{
   @foo
   name = 'Jony'
}
const p = new P();
//會依次輸出 target is f P()  name is Jony
複製代碼

這裏對於類的屬性的裝飾器函數接受兩個參數,對於靜態屬性而言,第一個參數是類自己,對於實例屬性而言,第一個參數是類的原型,第二個參數是指屬性的名字。

(4)、類函數參數的裝飾器

接着來看類函數參數的裝飾器,類函數的參數裝飾器能夠修飾類的構建函數中的參數,以及類中其餘普通函數中的參數。該裝飾器在類的方法被調用的時候執行,下面來看實例:

function foo(target,key,index){
   console.log("target is",target);
   console.log("key is",key);
   console.log("index is",index)
}
class P{
   test(@foo a){
   }
}
const p = new P();
p.test("Hello Jony")
// 依次輸出 f P() , test , 0 
複製代碼

類函數參數的裝飾器函數接受三個參數,依次爲類自己,類中該被修飾的函數自己,以及被修飾的參數在參數列表中的索引值。上述的例子中,會依次輸出 f P() 、test和0。再次明確一下修飾函數參數的裝飾器函數中的參數含義:

  • target: 類自己
  • key:該參數所在的函數的函數名
  • index: 該參數在函數參數列表中的索引值

從上面的Typescrit中在基類中經常使用的裝飾器後,咱們發現:

裝飾器能夠起到分離複雜邏輯的功能,且使用上極其簡單方便。與繼承相比,也更加靈活,能夠從裝飾類,到裝飾類函數的參數,能夠說武裝到了「牙齒」。

3、Typescript中的註解

在瞭解了Typescrit中的裝飾器以後,接着咱們來看Typescrit中的註解。

什麼是註解,所謂註解的定義就是:

爲相應的類附加元數據支持

所謂元數據能夠簡單的解釋,就是修飾數據的數據,好比一我的有name,age等數據屬性,那麼name和age這些字段就是爲了修飾數據的數據,能夠簡單的稱爲元數據。

元數據簡單來講就是能夠修飾某些數據的字段。下面給出裝飾器和註解的解釋和區別:

  • 裝飾器:定義劫持,能夠對類,類的方法,類的屬性以及類的方法的入參進行修改。不提供元數據的支持。
  • 註解:僅提供元數據的支持。

二者之間的聯繫:

經過註解添加元數據,而後在裝飾器中獲取這些元數據,完成對類、類的方法等等的修改,能夠在裝飾器中添加元數據的支持,好比能夠能夠在裝飾器工廠函數以及裝飾器函數中添加元數據支持等

(1)、Typescript中的元數據操做

能夠經過reflect-metadata包來實現對於元數據的操做。首先咱們來看reflect-metadata的使用,首先定義使用元數據的函數:

const formatMetadataKey = Symbol("format");

function format(formatString: string) {
    return Reflect.metadata(formatMetadataKey, formatString);
}

function getFormat(target: any, propertyKey: string) {
    return Reflect.getMetadata(formatMetadataKey, target, propertyKey);
}
複製代碼

這裏的format能夠做爲裝飾器函數的工廠函數,由於format函數返回的是一個裝飾器函數,上述的方法定義了元數據Sysmbol("format"),用Sysmbol的緣由是爲了防止元數據中的字段重複,而format定義了取元數據中相應字段的功能。

接着咱們來在類中使用相應的元數據:

class Greeter {
    @format("Hello, %s")
    name: string;

    constructor(name: string) {
        this.name = message;
    }
    sayHello() {
        let formatString = getFormat(this, "name");
        return formatString.replace("%s", this.name);
    }
}

const g = new Greeter("Jony");
console.log(g.sayHello());
複製代碼

在上述中,咱們在name屬性的裝飾器工廠函數,執行@format("Hello, %s"),返回一個裝飾器函數,且該裝飾器函數修飾了Greeter類的name屬性,將「name」屬性的值寫入爲"Hello, %s"。

而後再sayHello方法中,經過getFormat(this,"name")取到formatString爲「Hello,%s」.

4、總結

經過裝飾器,能夠方便的修飾類,以及類的方法,類的屬性等,相比於繼承而言更加靈活,此外,經過註解的方法,能夠在Typescript中引入元數據,實現元編程等。特別是在angularjs、nestjs中,大量使用了註解,特別是nestjs構建了相似於java springMVC式的web框架。

相關文章
相關標籤/搜索