最近新開了一個Node項目助币,采用TypeScript來開發(fā),在數(shù)據(jù)庫及路由管理方面用了不少的裝飾器燕雁,發(fā)覺這的確是一個好東西诞丽。
裝飾器是一個還處于草案中的特性,目前木有直接支持該語法的環(huán)境拐格,但是可以通過 babel 之類的進(jìn)行轉(zhuǎn)換為舊語法來實現(xiàn)效果僧免,所以在TypeScript中,可以放心的使用@Decorator捏浊。
什么是裝飾器
裝飾器是對類懂衩、函數(shù)、屬性之類的一種裝飾金踪,可以針對其添加一些額外的行為浊洞。
通俗的理解可以認(rèn)為就是在原有代碼外層包裝了一層處理邏輯。
個人認(rèn)為裝飾器是一種解決方案热康,而并非是狹義的@Decorator沛申,后者僅僅是一個語法糖罷了。
裝飾器在身邊的例子隨處可見姐军,一個簡單的例子铁材,水龍頭上邊的起泡器就是一個裝飾器,在裝上以后就會把空氣混入水流中奕锌,摻雜很多泡泡在水里著觉。
但是起泡器安裝與否對水龍頭本身并沒有什么影響,即使拆掉起泡器惊暴,也會照樣工作饼丘,水龍頭的作用在于閥門的控制,至于水中摻不摻雜氣泡則不是水龍頭需要關(guān)心的辽话。
所以肄鸽,對于裝飾器卫病,可以簡單地理解為是非侵入式的行為修改。
為什么要用裝飾器
可能有些時候典徘,我們會對傳入?yún)?shù)的類型判斷蟀苛、對返回值的排序、過濾逮诲,對函數(shù)添加節(jié)流帜平、防抖或其他的功能性代碼,基于多個類的繼承梅鹦,各種各樣的與函數(shù)邏輯本身無關(guān)的裆甩、重復(fù)性的代碼。
函數(shù)中的作用
可以想像一下齐唆,我們有一個工具類嗤栓,提供了一個獲取數(shù)據(jù)的函數(shù):
class Model1 {
getData() {
// 此處省略獲取數(shù)據(jù)的邏輯
return [{
id: 1,
name: 'Niko'
}, {
id: 2,
name: 'Bellic'
}]
}
}
console.log(new Model1().getData()) // [ { id: 1, name: 'Niko'}, { id: 2, name: 'Bellic' } ]
console.log(Model1.prototype.getData()) // [ { id: 1, name: 'Niko'}, { id: 2, name: 'Bellic' } ]
復(fù)制代碼
現(xiàn)在我們想要添加一個功能,記錄該函數(shù)執(zhí)行的耗時蝶念。
因為這個函數(shù)被很多人使用抛腕,在調(diào)用方添加耗時統(tǒng)計邏輯是不可取的,所以我們要在Model1中進(jìn)行修改:
class Model1 {
getData() {
- let start = new Date().valueOf()
- try {
// 此處省略獲取數(shù)據(jù)的邏輯
return [{
id: 1,
name: 'Niko'
}, {
id: 2,
name: 'Bellic'
}] - } finally {
let end = new Date().valueOf()
console.log(`start: ${start} end: ${end} consume: ${end - start}`)
- }
}
}
// start: XXX end: XXX consume: XXX
console.log(new Model1().getData()) // [ { id: 1, name: 'Niko'}, { id: 2, name: 'Bellic' } ]
// start: XXX end: XXX consume: XXX
console.log(Model1.prototype.getData()) // [ { id: 1, name: 'Niko'}, { id: 2, name: 'Bellic' } ]
復(fù)制代碼
這樣在調(diào)用方法后我們就可以在控制臺看到耗時的輸出了媒殉。
但是這樣直接修改原函數(shù)代碼有以下幾個問題:
統(tǒng)計耗時的相關(guān)代碼與函數(shù)本身邏輯并無一點(diǎn)關(guān)系担敌,影響到了對原函數(shù)本身的理解,對函數(shù)結(jié)構(gòu)造成了破壞性的修改
如果后期還有更多類似的函數(shù)需要添加統(tǒng)計耗時的代碼廷蓉,在每個函數(shù)中都添加這樣的代碼顯然是低效的全封,維護(hù)成本太高
所以,為了讓統(tǒng)計耗時的邏輯變得更加靈活桃犬,我們將創(chuàng)建一個新的工具函數(shù)刹悴,用來包裝需要設(shè)置統(tǒng)計耗時的函數(shù)。
通過將Class與目標(biāo)函數(shù)的name傳遞到函數(shù)中攒暇,實現(xiàn)了通用的耗時統(tǒng)計:
function wrap(Model, key) {
// 獲取Class對應(yīng)的原型
let target = Model.prototype
// 獲取函數(shù)對應(yīng)的描述符
let descriptor = Object.getOwnPropertyDescriptor(target, key)
// 生成新的函數(shù)土匀,添加耗時統(tǒng)計邏輯
let log = function (...arg) {
let start = new Date().valueOf()
try {
return descriptor.value.apply(this, arg) // 調(diào)用之前的函數(shù)
} finally {
let end = new Date().valueOf()
console.log(start: ${start} end: ${end} consume: ${end - start}
)
}
}
// 將修改后的函數(shù)重新定義到原型鏈上
Object.defineProperty(target, key, {
...descriptor,
value: log // 覆蓋描述符重的value
})
}
wrap(Model1, 'getData')
wrap(Model2, 'getData')
// start: XXX end: XXX consume: XXX
console.log(new Model1().getData()) // [ { id: 1, name: 'Niko'}, { id: 2, name: 'Bellic' } ]
// start: XXX end: XXX consume: XXX
console.log(Model2.prototype.getData()) // [ { id: 1, name: 'Niko'}, { id: 2, name: 'Bellic' } ]
復(fù)制代碼