從java註解漫談到typescript裝飾器——註解與裝飾器
之前整理過《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 classIceCream{} functionFrozen(constructor:Function){ Object.freeze(constructor); Object.freeze(constructor.prototype); } console.log(Object.isFrozen(IceCream));//true classFroYoextendsIceCream{}//報錯,類不能被擴充套件
當裝飾函式直接修飾類的時候,裝飾函式接受唯一的引數constructor,這個引數就是該被修飾類本身。
此外,在修飾類的時候,如果裝飾函式有返回值,該返回值會重新定義這個類,也就是說當裝飾函式有返回值時,其實是生成了一個新類,該新類通過返回值來定義。
方法裝飾器 Method Decorator
方法裝飾器來覆寫一個方法,改變它的執行流程,以及在它執行前後額外執行一些程式碼。
下面這個例子會在執行真正的程式碼之前彈出一個確認框。如果使用者點選了取消,方法就會被跳過。注意,這裡我們裝飾了一個方法兩次,這兩個裝飾器會從上到下地執行。
functionlog(target,key,descriptor){} classP{ @log foo(){ console.log('Dosomething'); } }
對於類的函式的裝飾器函式,依次接受的引數為:
-
target:如果修飾的是類的例項函式,那麼target就是類的原型。如果修飾的是類的靜態函式,那麼target就是類本身。
-
key: 該函式的函式名。
-
descriptor:該函式的描述屬性,比如 configurable、value、enumerable等。
屬性裝飾器 Property Decorator
屬性裝飾器極其有用,因為它可以監聽物件狀態的變化。
為了充分了解接下來這個例子,建議你先熟悉一下 JavaScript 的屬性描述符(PropertyDescriptor)。
functionfoo(target,name){} classP{ @foo name='Jony' }
這裡對於類的屬性的裝飾器函式接受兩個引數,
-
第一個引數:
-
對於靜態屬性而言,是類本身
-
對於例項屬性而言,是類的原型,
-
第二個引數:所指屬性的名字。
類函式引數的裝飾器
類函式的引數裝飾器可以修飾類的構建函式中的引數,以及類中其他普通函式中的引數。該裝飾器在類的方法被呼叫的時候執行。
functionfoo(target,key,index){} classP{ test(@fooa){ } }
類函式引數的裝飾器函式接受三個引數
-
target: 類本身
-
key:該引數所在的函式的函式名
-
index: 該引數在函式引數列表中的索引值
裝飾器可以起到分離複雜邏輯的功能,且使用上極其簡單方便。與繼承相比,也更加靈活,可以從裝飾類,到裝飾類函式的引數,可以說武裝到了“牙齒”。
Typescript中的元資料操作
可以通過reflect-metadata包來實現對於元資料的操作。首先我們來看reflect-metadata的使用,首先定義使用元資料的函式:
constformatMetadataKey=Symbol("format"); functionformat(formatString:string){ returnReflect.metadata(formatMetadataKey,formatString); } functiongetFormat(target:any,propertyKey:string){ returnReflect.getMetadata(formatMetadataKey,target,propertyKey); }
這裡的format可以作為裝飾器函式的工廠函式,因為format函式返回的是一個裝飾器函式,上述的方法定義了元資料Sysmbol("format"),用Sysmbol的原因是為了防止元資料中的欄位重複,而format定義了取元資料中相應欄位的功能。
接著我們來在類中使用相應的元資料:
classGreeter{ @format("Hello,%s") name:string; constructor(name:string){ this.name=message; } sayHello(){ letformatString=getFormat(this,"name"); returnformatString.replace("%s",this.name); } } constg=newGreeter("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