之前整理過(guò)《Java注解(批注)的基本原理》钮惠,在java里面运授,帜羊,注解(Annotation)是油鹽凭涂,對(duì)于JavaScript來(lái)說(shuō)断傲,還中世紀(jì)歐洲的東方香料
裝飾器和注解
裝飾器和注解之前也搞不清他們的具體理念败徊,覺(jué)得都是基于元編程實(shí)現(xiàn)辟拷,注解就是裝飾模式的一種吧撞羽。
注解(Annotation):僅提供附加元數(shù)據(jù)支持,并不能實(shí)現(xiàn)任何操作衫冻。需要另外的 Scanner 根據(jù)元數(shù)據(jù)執(zhí)行相應(yīng)操作诀紊。
裝飾器(Decorator):僅提供定義劫持,可以對(duì)類(lèi)隅俘,類(lèi)的方法邻奠,類(lèi)的屬性以及類(lèi)的方法的入?yún)⑦M(jìn)行修改笤喳。不提供元數(shù)據(jù)的支持。
注解與裝飾器兩者之間的聯(lián)系:
通過(guò)注解添加元數(shù)據(jù)碌宴,然后在裝飾器中獲取這些元數(shù)據(jù)杀狡,完成對(duì)類(lèi)、類(lèi)的方法等等的修改贰镣,可以在裝飾器中添加元數(shù)據(jù)的支持呜象,比如可以可以在裝飾器工廠(chǎng)函數(shù)以及裝飾器函數(shù)中添加元數(shù)據(jù)支持等。
注解與裝飾器的區(qū)別
雖然語(yǔ)法上很相似碑隆,但在不同的語(yǔ)言中可能使用的是不同的概念:
使用注解(Annotation)的語(yǔ)言:AtScript恭陡、Java、C#(叫 Attribute)上煤。
使用裝飾器(Decorator)的語(yǔ)言:Python休玩、JavaScript/ECMAScript。
從概念上來(lái)說(shuō)劫狠,我們可以很清晰的看出拴疤,注解和裝飾器在語(yǔ)義上沒(méi)有任何共性!
注解和裝飾器可以互相模擬嘉熊,不等同遥赚。 裝飾器可以天生跑在運(yùn)行時(shí),注解還要通過(guò)反射(拿不到類(lèi)型本身)
繼承模式是豐富子元素“內(nèi)涵”的一種重要方式阐肤,不管是繼承接口還是子類(lèi)繼承基類(lèi)凫佛。而裝飾者模式可以在不改變繼承關(guān)系的前提下,包裝先有的模塊孕惜,使其內(nèi)涵更加豐富愧薛,并不會(huì)影響到原來(lái)的功能。與繼承相比衫画,更加的靈活毫炉。
裝飾器最為強(qiáng)大的功能之一是它能夠反射元數(shù)據(jù)(reflect metada)
為什么需要在JavaScript中進(jìn)行反射?
反射用于描述能夠檢查同一系統(tǒng)(或其自身)中的其他代碼的代碼削罩。
JavaScript應(yīng)用程序越來(lái)越大瞄勾,所以需要一些工具(如控件容器的反轉(zhuǎn))和像(運(yùn)行時(shí)類(lèi)型斷言)這樣的功能來(lái)管理這種日益增加的復(fù)雜性。
強(qiáng)大的反射API應(yīng)該允許我們?cè)谶\(yùn)行時(shí)檢查未知對(duì)象并找出有關(guān)它的所有內(nèi)容弥激。我們應(yīng)該能夠找到像這樣的東西:
實(shí)體的名稱(chēng)进陡。
實(shí)體的類(lèi)型。
哪些接口由實(shí)體實(shí)現(xiàn)微服。
實(shí)體屬性的名稱(chēng)和類(lèi)型趾疚。
實(shí)體的構(gòu)造函數(shù)參數(shù)的名稱(chēng)和類(lèi)型
在JavaScript中,我們可以使用Object.getOwnPropertyDescriptor()或Object.keys()等函數(shù)來(lái)查找有關(guān)實(shí)體的一些信息,但我們需要反思來(lái)實(shí)現(xiàn)更強(qiáng)大的開(kāi)發(fā)工具糙麦。
但是辛孵,事情即將發(fā)生變化,因?yàn)門(mén)ypeScript開(kāi)始支持一些Reflection功能赡磅。但實(shí)際上它們只是一些 JavaScript 函數(shù)魄缚,能夠幫助我們來(lái)注釋代碼或者是修改代碼的行為——這種做法我們通常稱(chēng)為元編程。
TypeScript?裝飾器
裝飾器能夠很好的抽象代碼仆邓,它們最適合用來(lái)包裝可能會(huì)多處復(fù)用的邏輯鲜滩。
五種裝飾器的方法
類(lèi)聲明
屬性
方法
參數(shù)
accessor
類(lèi)裝飾器 Class Decorator
類(lèi)裝飾器使得開(kāi)發(fā)者能夠攔截類(lèi)的構(gòu)造方法 constructor。
注意:當(dāng)我們聲明一個(gè)類(lèi)時(shí)节值,裝飾器就會(huì)被調(diào)用徙硅,而不是等到類(lèi)實(shí)例化的時(shí)候。
當(dāng)你裝飾一個(gè)類(lèi)的時(shí)候搞疗,裝飾器并不會(huì)對(duì)該類(lèi)的子類(lèi)生效嗓蘑,讓我們來(lái)凍結(jié)一個(gè)類(lèi)來(lái)徹底避免別的程序員不小心忘了這個(gè)特性。
@Frozen
class?IceCream?{}
function?Frozen(constructor:?Function)?{
??Object.freeze(constructor);
??Object.freeze(constructor.prototype);
}
console.log(Object.isFrozen(IceCream));?//?true
class?FroYo?extends?IceCream?{}?//?報(bào)錯(cuò)匿乃,類(lèi)不能被擴(kuò)展
當(dāng)裝飾函數(shù)直接修飾類(lèi)的時(shí)候桩皿,裝飾函數(shù)接受唯一的參數(shù)constructor,這個(gè)參數(shù)就是該被修飾類(lèi)本身幢炸。
此外泄隔,在修飾類(lèi)的時(shí)候,如果裝飾函數(shù)有返回值宛徊,該返回值會(huì)重新定義這個(gè)類(lèi)佛嬉,也就是說(shuō)當(dāng)裝飾函數(shù)有返回值時(shí),其實(shí)是生成了一個(gè)新類(lèi)闸天,該新類(lèi)通過(guò)返回值來(lái)定義暖呕。
方法裝飾器 Method Decorator
方法裝飾器來(lái)覆寫(xiě)一個(gè)方法,改變它的執(zhí)行流程苞氮,以及在它執(zhí)行前后額外運(yùn)行一些代碼湾揽。
下面這個(gè)例子會(huì)在執(zhí)行真正的代碼之前彈出一個(gè)確認(rèn)框。如果用戶(hù)點(diǎn)擊了取消笼吟,方法就會(huì)被跳過(guò)库物。注意,這里我們裝飾了一個(gè)方法兩次贷帮,這兩個(gè)裝飾器會(huì)從上到下地執(zhí)行艳狐。
function?log(target,?key,?descriptor)?{}
class?P?{
????@log
????foo()?{
??????console.log('Do?something');
????}
}
對(duì)于類(lèi)的函數(shù)的裝飾器函數(shù),依次接受的參數(shù)為:
target:如果修飾的是類(lèi)的實(shí)例函數(shù)皿桑,那么target就是類(lèi)的原型。如果修飾的是類(lèi)的靜態(tài)函數(shù),那么target就是類(lèi)本身诲侮。
key: 該函數(shù)的函數(shù)名镀虐。
descriptor:該函數(shù)的描述屬性,比如 configurable沟绪、value刮便、enumerable等。
屬性裝飾器 Property Decorator
屬性裝飾器極其有用绽慈,因?yàn)樗?b>可以監(jiān)聽(tīng)對(duì)象狀態(tài)的變化恨旱。
為了充分了解接下來(lái)這個(gè)例子,建議你先熟悉一下 JavaScript 的屬性描述符(PropertyDescriptor)坝疼。
function?foo(target,name){}
class?P{
???@foo
???name?=?'Jony'
}
這里對(duì)于類(lèi)的屬性的裝飾器函數(shù)接受兩個(gè)參數(shù)搜贤,
第一個(gè)參數(shù):
對(duì)于靜態(tài)屬性而言,是類(lèi)本身
對(duì)于實(shí)例屬性而言钝凶,是類(lèi)的原型仪芒,
第二個(gè)參數(shù):所指屬性的名字。
類(lèi)函數(shù)參數(shù)的裝飾器
類(lèi)函數(shù)的參數(shù)裝飾器可以修飾類(lèi)的構(gòu)建函數(shù)中的參數(shù)耕陷,以及類(lèi)中其他普通函數(shù)中的參數(shù)掂名。該裝飾器在類(lèi)的方法被調(diào)用的時(shí)候執(zhí)行。
function?foo(target,key,index){}
class?P{
???test(@foo?a){
???}
}
類(lèi)函數(shù)參數(shù)的裝飾器函數(shù)接受三個(gè)參數(shù)
target: 類(lèi)本身
key:該參數(shù)所在的函數(shù)的函數(shù)名
index: 該參數(shù)在函數(shù)參數(shù)列表中的索引值
裝飾器可以起到分離復(fù)雜邏輯的功能哟沫,且使用上極其簡(jiǎn)單方便饺蔑。與繼承相比,也更加靈活嗜诀,可以從裝飾類(lèi)猾警,到裝飾類(lèi)函數(shù)的參數(shù),可以說(shuō)武裝到了“牙齒”裹虫。
Typescript中的元數(shù)據(jù)操作
可以通過(guò)reflect-metadata包來(lái)實(shí)現(xiàn)對(duì)于元數(shù)據(jù)的操作肿嘲。首先我們來(lái)看reflect-metadata的使用,首先定義使用元數(shù)據(jù)的函數(shù):
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可以作為裝飾器函數(shù)的工廠(chǎng)函數(shù)筑公,因?yàn)閒ormat函數(shù)返回的是一個(gè)裝飾器函數(shù)雳窟,上述的方法定義了元數(shù)據(jù)Sysmbol("format"),用Sysmbol的原因是為了防止元數(shù)據(jù)中的字段重復(fù),而format定義了取元數(shù)據(jù)中相應(yīng)字段的功能匣屡。
接著我們來(lái)在類(lèi)中使用相應(yīng)的元數(shù)據(jù):
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());
在上述中封救,我們?cè)趎ame屬性的裝飾器工廠(chǎng)函數(shù),執(zhí)行@format("Hello, %s")捣作,返回一個(gè)裝飾器函數(shù)誉结,且該裝飾器函數(shù)修飾了Greeter類(lèi)的name屬性,將“name”屬性的值寫(xiě)入為"Hello, %s"券躁。
然后再sayHello方法中惩坑,通過(guò)getFormat(this,"name")取到formatString為“Hello,%s”.
參考列表:
TypeScript中的裝飾器&元數(shù)據(jù)反射:從新手到專(zhuān)家四?https://zhuanlan.zhihu.com/p/42220487
理解 TypeScript 裝飾器?https://zhuanlan.zhihu.com/p/65764702
【認(rèn)真臉】注解與裝飾器的點(diǎn)點(diǎn)滴滴https://zhuanlan.zhihu.com/p/22277764
聊聊Typescript中的設(shè)計(jì)模式——裝飾器篇(decorators)?https://github.com/forthealllight/blog/issues/33
轉(zhuǎn)載本站文章《從java注解漫談到typescript裝飾器——注解與裝飾器》,
請(qǐng)注明出處:https://www.zhoulujun.cn/html/webfront/ECMAScript/typescript/2020_0721_8528.html