【認真臉】註解與裝飾器的點點滴滴

對於很多 Java 開發人員來說,註解(Annotation)已經是一個十分熟悉的概念。而在 JavaScript 中(目前仍然是 Stage 2 的 ES Proposal),引入了一個類似的語法——裝飾器(Decorator)。

甚至有很多人在生活中不加區分兩個的概念,把裝飾器也叫做註解。那麼,裝飾器和註解到底有什麼聯繫與區別呢?

概念

  • 註解(Annotation):僅提供附加元數據支持,並不能實現任何操作。需要另外的 Scanner 根據元數據執行相應操作。
  • 裝飾器(Decorator):僅提供定義劫持,能夠對類及其方法的定義並沒有提供任何附加元數據的功能。

雖然語法上很相似,但在不同的語言中可能使用的是不同的概念:

  • 使用註解(Annotation)的語言:AtScript、Java、C#(叫 Attribute)。
  • 使用裝飾器(Decorator)的語言:Python、JavaScript/ECMAScript。

從概念上來說,我們可以很清晰的看出,註解和裝飾器在語義上沒有任何共性!

但是,這個結論並不是那麼符合我們的直覺,一個最直接的例子就是 Angular 2,在曾經結合 AtScript 使用的時候,用的是註解;在改成結合 TypeScript/ES next 使用的時候,用的是裝飾器。雖然叫法變了,但在 API 方面幾乎沒有任何可見的變化,這又是為什麼呢?

實現

這裡以 AtScript 的註解和 ES Stage 2(截至目前)的 Decorator 為例。

在 AtScript 中,我們使用的是真真切切的 Annotation,也就是完完全全的單純附加元數據,例如:

// app.component.asnn@Component({n selector: appn})nclass AppComponent {} n

等價於:

// app.component.jsnnclass AppComponent {} nnAppComponent.annotations = [n new Component({n selector: appn })n]n

也就是簡單地把註解中的類型轉化成實例構造然後掛到被註解的類型上。

而在 ES next 的 Decorator 中,我們也可以使用類似的語法:

// app.component.tsnn@Component({n selector: appn})nclass AppComponent {} n

但卻具有完全不同的語義:

// app.component.jsnnlet AppComponent = Component({n selector: appn})(class AppComponent {})n

這裡的對應結果並不完全精確,僅供表意。

事實上

我們發現,對於註解有一個構造函數調用,對於裝飾器而言有一個普通的函數調用。既然有這個函數調用過程,我們其實可以該函數調用中做任何事情!

或者說的更為直白一些,(AtScript 的)註解和(ES next)的裝飾器在實現上可以相互模擬!

對於裝飾器而言,如果想要實現註解的功能,在原來的類型上掛點什麼東西就好;對於註解而言,如果想要實現裝飾器的功能,把現有類型(或其屬性)按需替換掉就好。當然,後者可能會有一些限制,但主要功能還是能夠模擬出來的。

現在我們可以知道,所有 Angular 2n中使用的 Decorator 都並不是真正作為 Decorator 使用,只是通過 Decorator + Reflect.metadata 的組合來模擬 Annotation 的功能。即附加元數據走的是 Reflect.metadata,該實現和nDecorator 本身並無聯繫。

所以 Angular 2 中其實並沒有通過 Decorator 來實現附加數據,而是在 Decorator 的應用過程中,通過 Reflect.metadata 來附加數據,Decorator 在這裡的意義僅僅是作為語法糖,把函數調用寫的更好看了而已。

另外,雖然我們的用法沒有發生變化,但經過上面的討論我們很容易知道在 Angular 2 中原先配合 AtScript 的 Component 實體的實現和現在配合 ES next 的 Component 實體的實現是完全不一樣的,前者就是 ComponentMetadata,而後者是一個 ComponentMetadataDecoratorFactory。

總結

  • 註解和裝飾器語法上很接近;
  • 註解和裝飾器語義上沒有一丁點關係;
  • Angular 2 曾經在 AtScript 中用的是註解語法;
  • Angular 2 現在在 ES next 中用的是裝飾器語法;
  • 裝飾器沒有提供任何附加數據的功能;
  • Angular 2 雖然用的裝飾器的語法但並沒有使用裝飾器的功能;
  • Angular 2 的附加元數據是通過 Reflect.metadata 實現的,和裝飾器本身並沒有任何關係。

推薦閱讀:

ThinkJS 3.0 如何實現對 TypeScript 的支持
TypeScript 2.1中的類型運算
TypeScript入門
是時候再給TypeScript一次機會了【譯】

TAG:Angular? | TypeScript | ECMAScript |