寫在最前:本文轉(zhuǎn)自掘金
前言
我們平常開發(fā)中或多或少的聽說(shuō)使用過(guò)裝飾器,也切身感受到它帶給我們的遍歷削罩。本文將聚焦ts的裝飾器欣舵,去探討什么是裝飾器荆永,如何使用憋他。
裝飾器的演變
- 2015-3-24
stage1 階段释牺,也是目前廣為使用的用法,也基本等同ts開啟了experimentalDecorators的用法微服。 - 2018-09
進(jìn)入到stage2階段趾疚,用法和stage1很大不同 - 2012-12
針對(duì)stage2天進(jìn)行了一次修改。 - 2022-03
正是進(jìn)入stage3以蕴,去掉了matedata部分糙麦,使用方式?jīng)]有太大變化。
js裝飾器和ts裝飾器
js原生目前不支持裝飾器丛肮,只能通過(guò)babel體驗(yàn)裝飾器這個(gè)新特性赡磅。
裝飾器是一種特殊類型的聲明,它能夠被附加到類聲明宝与,方法焚廊,訪問(wèn)符,屬性或參數(shù)上习劫。裝飾器使用@expression這種形式咆瘟,expression求職后必須為一個(gè)函數(shù),它會(huì)再運(yùn)行時(shí)被調(diào)用榜聂,被裝飾的聲明信息作為參數(shù)傳入搞疗。
由于裝飾器目前還是實(shí)驗(yàn)中的特定嗓蘑,在js中處于stage-3階段须肆。在ts中已經(jīng)作為一項(xiàng)實(shí)驗(yàn)性予以支持。開啟裝飾器需要在tsconfig.json
文件中啟用 experimentalDecorators
編譯器選項(xiàng)桩皿。
裝飾器的使用
類裝飾器
類裝飾器是我們最常使用到的豌汇,它的通常作用是,為該類擴(kuò)展功能
- 類裝飾器有且只有一個(gè)泄隔,參數(shù)為類的構(gòu)造函數(shù)constructor
- 如果類裝飾器返回一個(gè)值拒贱,它會(huì)使用提供的構(gòu)造函數(shù)來(lái)替換類的聲明
設(shè)想有這樣一個(gè)場(chǎng)景。目前有一個(gè)Tank類,有一個(gè)Plane類逻澳,有一個(gè)Animal類闸天。這三個(gè)類都需要一個(gè)公共的方法來(lái)獲取他們所在的位置。我們第一可能想到使用繼承來(lái)實(shí)現(xiàn)斜做。
class BaseClass {
getPosition() {
return {
x: 100,
y: 200,
z: 300,
}
}
}
class Tank extends BaseClass{}
class Plane extends BaseClass {}
class Animal extends BaseClass {}
這樣三個(gè)類都可以調(diào)用getPosition
方法來(lái)獲取各自的位置了苞氮。到目前為止看起來(lái)沒(méi)有什么問(wèn)題。
現(xiàn)在又有新需求瓤逼,Tank類和Plane類需要一個(gè)新的方法addPetrol
來(lái)給坦克和飛機(jī)加油笼吟。而動(dòng)物不需要加油。此時(shí)這種寫法好像不能繼續(xù)進(jìn)行下去了霸旗。而js目前沒(méi)有直接語(yǔ)法提供多繼承的功能贷帮,我們的繼承方向好像行不通了。這個(gè)時(shí)候類裝飾器可以很完美的實(shí)現(xiàn)這樣的功能诱告。
裝飾器功能之一——能力擴(kuò)展
我們把getPosition
和addPertrol
都抽象成一個(gè)單獨(dú)的功能撵枢,它們得作用是給宿主擴(kuò)展對(duì)應(yīng)的功能。
const getPositionDecorator: ClassDecorator = (constructor: Function) => {
constructor.prototype.getPosition = () => {
return [100, 200]
}
}
const addPetrolDecorator: ClassDecorator = (constructor: Function) => {
constructor.prototype.addPetrol = () => {
// do something
console.log(`${constructor.name}進(jìn)行加油`);
}
}
@addPetrolDecorator
@getPositionDecorator
class Tank {}
@addPetrolDecorator
@getPositionDecorator
class Plane {}
@getPositionDecorator
class Animal {}
這樣的話精居,假如日后我們有其他的需求诲侮,都可以對(duì)他進(jìn)行能力擴(kuò)展,讓其具有加油的能力箱蟆。
注意沟绪,多個(gè)裝飾器疊加的時(shí)候,執(zhí)行順序?yàn)殡x被裝飾對(duì)象越近的裝飾器越先執(zhí)行空猜。
裝飾器功能之二——重載構(gòu)造函數(shù)
在類裝飾器中如果返回一個(gè)值绽慈,它會(huì)使用提供的構(gòu)造函數(shù)來(lái)替換類的聲明。
function classDecorator<T extends {new(...args:any[]):{}}>(constructor:T) {
return class extends constructor {
newProperty = "new property";
hello = "override";
}
}
@classDecorator
class Greeter {
property = "property";
hello: string;
constructor(m: string) {
this.hello = m;
}
}
方法裝飾器
方法裝飾器接收三個(gè)參數(shù):
- 對(duì)于靜態(tài)方法辈毯,第一個(gè)參數(shù)為類的構(gòu)造函數(shù)坝疼。對(duì)于實(shí)例方法,為類的原型對(duì)象
- 第二個(gè)參數(shù)為方法名谆沃。
- 第三個(gè)參數(shù)為方法描述符钝凶。
- 方法裝飾器可以有返回值,返回值會(huì)作為方法的屬性描述符
裝飾器功能之一——能力增強(qiáng)
我們代碼編寫時(shí)唁影,經(jīng)常會(huì)做一些錯(cuò)誤catch
耕陷,使用裝飾器對(duì)每個(gè)方法進(jìn)行增加,使它們自動(dòng)獲取catch錯(cuò)誤的能力~
const ErrorDecorator: MethodDecorator = (target: Object, propertyKey: string | symbol, descriptor: PropertyDescriptor) => {
const sourceMethod = descriptor.value;
descriptor.value = async function (...args: any) {
try {
await sourceMethod.apply(this, args);
} catch (error) {
console.error('捕獲到了錯(cuò)誤');
// do something
}
}
}
class MusicSystem {
getMusicById(name: string): Promise<{name: string, singer: string}> {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (Math.round(Math.random())) {
resolve({name: '鳳凰傳奇', singer: '玲花|曾毅'});
} else {
reject()
}
}, 1000);
})
}
@ErrorDecorator
async play(name: string) {
const music = await this.getMusicById(name);
// ... do something
console.log(`在曲庫(kù)中找到了名為${music.name}的音樂(lè)据沈,由${music.singer}進(jìn)行演唱哟沫,敬請(qǐng)欣賞。`);
}
@ErrorDecorator
async deleteByName(name: string) {
const music = await this.getMusicById(name);
// ... do something
console.log(`${music.name}音樂(lè)刪除成功锌介!`);
}
}
const musicSystem = new MusicSystem();
musicSystem.play('鳳凰傳奇');
musicSystem.deleteByName('鳳凰傳奇');
細(xì)心的同學(xué)可以發(fā)現(xiàn)了嗜诀,我們?cè)诜椒ㄑb飾器中無(wú)法捕獲到實(shí)際的錯(cuò)誤猾警,比如精準(zhǔn)報(bào)錯(cuò)哪首歌沒(méi)找到。很遺憾隆敢,目前裝飾器的原生能力发皿,是無(wú)法獲取到我們調(diào)用時(shí)候傳入的具體參數(shù)的。因?yàn)檠b飾器實(shí)在編譯階段執(zhí)行的拂蝎。但是雳窟,我們可以通過(guò)其他方式實(shí)現(xiàn)這樣的功能,這就是大名鼎鼎的 metadata
匣屡。我們會(huì)在文章的末尾提到它封救。
裝飾器功能之一——descriptor修改
通過(guò)修改descriptor,我們可以實(shí)現(xiàn)對(duì)方法進(jìn)行重新描述捣作。比如設(shè)置方法禁止修改誉结,禁止刪除等。
const DescriptorDecorator: MethodDecorator = (target: Object, propertyKey: string | symbol, descriptor: PropertyDescriptor) : object => {
return {
value: () => {
console.log('eat方法被替換')
},
writable: true,
enumerable: true,
configurable: true,
};
}
class Pig {
name = 'peiqi';
@DescriptorDecorator
eat() {
}
}
同樣的券躁,也可以直接對(duì)descriptor進(jìn)行修改
descriptor.value = () => {console.log('eat方法被替換')};
descriptor.writable = true;
descriptor.enumerable = true;
descriptor.configurable = true;
方法裝飾器的使用方式很多惩坑,大多數(shù)的使用方式是對(duì)descriptor的value屬性進(jìn)行替換,攔截等實(shí)現(xiàn)功能也拜。
【下邊的三個(gè)裝飾器類型以舒,相對(duì)來(lái)說(shuō)使用比較少,有興趣的小伙伴可以查看原文】