之前整理過《Java注解(批注)的基本原理》,在java里面,,注解(Annotation)是油鹽,對於JavaScript來說,還中世紀歐洲的東方香料
裝飾器和注解
裝飾器和注解之前也搞不清他們的具體理念,覺得都是基於元編程實現,注解就是裝飾模式的一種吧。
-
注解(Annotation):僅提供附加元數據支持,並不能實現任何操作。需要另外的 Scanner 根據元數據執行相應操作。
-
裝飾器(Decorator):僅提供定義劫持,可以對類,類的方法,類的屬性以及類的方法的入參進行修改。不提供元數據的支持。
注解與裝飾器兩者之間的聯系:
通過注解添加元數據,然后在裝飾器中獲取這些元數據,完成對類、類的方法等等的修改,可以在裝飾器中添加元數據的支持,比如可以可以在裝飾器工廠函數以及裝飾器函數中添加元數據支持等。
注解與裝飾器的區別
雖然語法上很相似,但在不同的語言中可能使用的是不同的概念:
-
使用注解(Annotation)的語言:AtScript、Java、C#(叫 Attribute)。
-
使用裝飾器(Decorator)的語言:Python、JavaScript/ECMAScript。
從概念上來說,我們可以很清晰的看出,注解和裝飾器在語義上沒有任何共性!
注解和裝飾器可以互相模擬,不等同。 裝飾器可以天生跑在運行時,注解還要通過反射(拿不到類型本身)
繼承模式是豐富子元素“內涵”的一種重要方式,不管是繼承接口還是子類繼承基類。而裝飾者模式可以在不改變繼承關系的前提下,包裝先有的模塊,使其內涵更加豐富,並不會影響到原來的功能。與繼承相比,更加的靈活。
裝飾器最為強大的功能之一是它能夠反射元數據(reflect metada)
為什么需要在JavaScript中進行反射?
反射用於描述能夠檢查同一系統(或其自身)中的其他代碼的代碼。
JavaScript應用程序越來越大,所以需要一些工具(如控件容器的反轉)和像(運行時類型斷言)這樣的功能來管理這種日益增加的復雜性。
強大的反射API應該允許我們在運行時檢查未知對象並找出有關它的所有內容。我們應該能夠找到像這樣的東西:
-
實體的名稱。
-
實體的類型。
-
哪些接口由實體實現。
-
實體屬性的名稱和類型。
-
實體的構造函數參數的名稱和類型
在JavaScript中,我們可以使用Object.getOwnPropertyDescriptor()或Object.keys()等函數來查找有關實體的一些信息,但我們需要反思來實現更強大的開發工具。
但是,事情即將發生變化,因為TypeScript開始支持一些Reflection功能。但實際上它們只是一些 JavaScript 函數,能夠幫助我們來注釋代碼或者是修改代碼的行為——這種做法我們通常稱為元編程。
TypeScript 裝飾器
裝飾器能夠很好的抽象代碼,它們最適合用來包裝可能會多處復用的邏輯。
五種裝飾器的方法
-
類聲明
-
屬性
-
方法
-
參數
-
accessor
類裝飾器 Class Decorator
類裝飾器使得開發者能夠攔截類的構造方法 constructor。
注意:當我們聲明一個類時,裝飾器就會被調用,而不是等到類實例化的時候。
當你裝飾一個類的時候,裝飾器並不會對該類的子類生效,讓我們來凍結一個類來徹底避免別的程序員不小心忘了這個特性。
@Frozen class IceCream {} function Frozen(constructor: Function) { Object.freeze(constructor); Object.freeze(constructor.prototype); } console.log(Object.isFrozen(IceCream)); // true class FroYo extends IceCream {} // 報錯,類不能被擴展
當裝飾函數直接修飾類的時候,裝飾函數接受唯一的參數constructor,這個參數就是該被修飾類本身。
此外,在修飾類的時候,如果裝飾函數有返回值,該返回值會重新定義這個類,也就是說當裝飾函數有返回值時,其實是生成了一個新類,該新類通過返回值來定義。
方法裝飾器 Method Decorator
方法裝飾器來覆寫一個方法,改變它的執行流程,以及在它執行前后額外運行一些代碼。
下面這個例子會在執行真正的代碼之前彈出一個確認框。如果用戶點擊了取消,方法就會被跳過。注意,這里我們裝飾了一個方法兩次,這兩個裝飾器會從上到下地執行。
function log(target, key, descriptor) {} class P { @log foo() { console.log('Do something'); } }
對於類的函數的裝飾器函數,依次接受的參數為:
-
target:如果修飾的是類的實例函數,那么target就是類的原型。如果修飾的是類的靜態函數,那么target就是類本身。
-
key: 該函數的函數名。
-
descriptor:該函數的描述屬性,比如 configurable、value、enumerable等。
屬性裝飾器 Property Decorator
屬性裝飾器極其有用,因為它可以監聽對象狀態的變化。
為了充分了解接下來這個例子,建議你先熟悉一下 JavaScript 的屬性描述符(PropertyDescriptor)。
function foo(target,name){} class P{ @foo name = 'Jony' }
這里對於類的屬性的裝飾器函數接受兩個參數,
-
第一個參數:
-
對於靜態屬性而言,是類本身
-
對於實例屬性而言,是類的原型,
-
第二個參數:所指屬性的名字。
類函數參數的裝飾器
類函數的參數裝飾器可以修飾類的構建函數中的參數,以及類中其他普通函數中的參數。該裝飾器在類的方法被調用的時候執行。
function foo(target,key,index){} class P{ test(@foo a){ } }
類函數參數的裝飾器函數接受三個參數
-
target: 類本身
-
key:該參數所在的函數的函數名
-
index: 該參數在函數參數列表中的索引值
裝飾器可以起到分離復雜邏輯的功能,且使用上極其簡單方便。與繼承相比,也更加靈活,可以從裝飾類,到裝飾類函數的參數,可以說武裝到了“牙齒”。
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”.
參考列表:
TypeScript中的裝飾器&元數據反射:從新手到專家四 https://zhuanlan.zhihu.com/p/42220487
理解 TypeScript 裝飾器 https://zhuanlan.zhihu.com/p/65764702
【認真臉】注解與裝飾器的點點滴滴 https://zhuanlan.zhihu.com/p/22277764
聊聊Typescript中的設計模式——裝飾器篇(decorators) https://github.com/forthealllight/blog/issues/33
轉載本站文章《從java注解漫談到typescript裝飾器——注解與裝飾器》,
請注明出處:https://www.zhoulujun.cn/html/webfront/ECMAScript/typescript/2020_0721_8528.html