許多面向對象的語言都有 裝飾器(Decorator) 函數,用來修改類的行爲。目前,這個方法已經被引入了 ES7,可是不管是主流瀏覽器仍是 Node.js 對它的兼容性都不是特別友好。javascript
所以要在項目中使用Decorator
的話,須要使用 Babel 進行轉譯,或者使用 Javascript 的超集 Typescript 來進行開發。java
若是對這一語法細節還不是很瞭解的話,能夠先進這個傳送門:http://es6.ruanyifeng.com/#docs/decorator ,跟着阮一峯老師一塊兒瞭解一下它的特性。es6
使用 裝飾器 的初衷來自於不想修改原來接口的狀況下,能讓一件事表現得更好。就像:編程
若是要更加抽象地理解,在計算機領域,它就能夠被應用到日誌收集、錯誤捕獲、安全檢查、緩存、調試、持久化等等方面。後端
經常使用的裝飾器通常有 類裝飾器 和 方法裝飾器,固然也會有屬性裝飾器,可是用的很少就很少討論了。瀏覽器
主要應用於類構造函數,其參數是類的構造函數:緩存
function testable(target) {
target.prototype.isTestable = true
}
@testable
class MyTestableClass {}
let obj = new MyTestableClass()
obj.isTestable // true
複製代碼
注意: 這裏的target
參數若是直接給它添加方法,得到的是一個靜態方法,至關於在class
的方法前添加static
關鍵字;若是想添加實例屬性,能夠經過目標類的prototype
對象操做。安全
如今咱們就用 類裝飾器 實現一個捕獲方法執行時間的裝飾器:app
const sleepTimeClass = (timeHandler?: (time?: number) => void) => (target: any) => {
Object.getOwnPropertyNames(target.prototype).forEach(key => {
const func = target.prototype[key]
target.prototype[key] = async (...args: any[]) => {
const startTime = await +new Date()
await func.apply(this, args)
const endTime = await +new Date()
timeHandler && await timeHandler(endTime - startTime)
}
})
return target
}
複製代碼
之因此還在外面包了一層函數,是爲了經過柯里化,讓使用者能夠再進一步處理獲得的執行時間:異步
const sleepTimeClassTimer = sleepTimeClass(time => {
console.log('執行時間', `${time}ms`)
})
@sleepTimeClassTimer
class homepageController {
async get(ctx: any) {
ctx.response.body = await pageService.homeHtml('/page/helloworld', '/page/404')
}
}
複製代碼
這樣,每次class
中的方法執行完以後就會打印出相應的執行時間。
function readonly(target, name, descriptor){
// descriptor對象原來的值以下
// {
// value: specifiedFunction,
// enumerable: false,
// configurable: true,
// writable: true
// }
descriptor.writable = false
return descriptor
}
readonly(Person.prototype, 'name', descriptor)
// 相似於
Object.defineProperty(Person.prototype, 'name', descriptor)
複製代碼
因爲在異步編程的時候,async
和await
的異常很難捕獲,若是強行用try...catch
來搞,捕捉不完不說,代碼看起來還很難看,使用裝飾器就很簡單了:
const asyncMethod = (errorHandler?: (error?: Error) => void) => (...args: any[]) => {
const func = args[2].value
return {
get() {
return (...args: any[]) => {
return Promise.resolve(func.apply(this, args)).catch(error => {
errorHandler && errorHandler(error)
})
}
},
set(newValue: any) {
return newValue
}
}
}
複製代碼
接着使用方法裝飾器:
const errorAsyncMethod = asyncMethod(error => {
console.error('錯誤警告', error)
})
class homepageController {
@errorAsyncMethod async get(ctx: any) {
ctx.response.body = await pageService.homeHtml('/page/helloworld', '/page/404')
}
}
複製代碼
一個類或者方法能夠嵌套不少個裝飾器,因此搞清楚它們的執行順序也很重要:
在初衷那裏就已經提到了,試着想象一下,只須要幾個裝飾器就能夠完成先後端基本的性能和日誌監控,是否是頗有意思?