ES7 Decorator 應用小結

許多面向對象的語言都有 裝飾器(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)
複製代碼

因爲在異步編程的時候,asyncawait的異常很難捕獲,若是強行用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')
    }
}
複製代碼

裝飾器加載順序

一個類或者方法能夠嵌套不少個裝飾器,因此搞清楚它們的執行順序也很重要:

  • 有多個參數裝飾器時,從最後一個參數依次向前執行;
  • 方法和方法參數中參數裝飾器先執行;
  • 類裝飾器老是最後執行;
  • 方法和屬性裝飾器,誰在前面誰先執行;
  • 由於參數屬於方法一部分,因此參數會一直牢牢挨着方法執行。

裝飾器的應用

在初衷那裏就已經提到了,試着想象一下,只須要幾個裝飾器就能夠完成先後端基本的性能和日誌監控,是否是頗有意思?

相關文章
相關標籤/搜索