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')
    }
}
复制代码

装饰器加载顺序

一个类或者方法能够嵌套不少个装饰器,因此搞清楚它们的执行顺序也很重要:

  • 有多个参数装饰器时,从最后一个参数依次向前执行;
  • 方法和方法参数中参数装饰器先执行;
  • 类装饰器老是最后执行;
  • 方法和属性装饰器,谁在前面谁先执行;
  • 由于参数属于方法一部分,因此参数会一直牢牢挨着方法执行。

装饰器的应用

在初衷那里就已经提到了,试着想象一下,只须要几个装饰器就能够完成先后端基本的性能和日志监控,是否是颇有意思?

相关文章
相关标签/搜索