装饰器

73 阅读2分钟

装饰器与装饰者模式

装饰者模式是设计模式之一,比较官方的解释如下:

装饰者模式能够在不改变原对象自身的情况下,在程序运行期间给对象动态的添加职责

而装饰器则是装饰者模式的实践

用法

装饰器分为 类装饰器、成员装饰器、参数装饰器。

为什么有类装饰器而没有函数装饰器呢?

由于 function 存在变量提升,所以运行至装饰器时 target 指向 undefined

装饰器本质是高阶函数,通过相关参数和返回值动态改变原来的对象(类)

在 Typescript 中,通过@来使用定义的装饰器,JavaScript 目前还没有正式支持装饰器(ES2024有望加入) 所以这里我们采用 TS 演示

类装饰器

类装饰器接收一个参数(target),也就是 class 本身

作为类的装饰器函数返回值存在两种情况:

  1. void,也就是没有返回值,则不改变原来的类
  2. 新的类,则与原来的类脱离干系,实例是通过新类生成的(不建议这样使用)
// 定义
function classDecorator (target: object) {
}
// 使用
@classDecorator
class Test {
}

成员装饰器

成员装饰器接收三个参数

  • target:
    • 如果是静态成员(static)返回class
    • 如果是实例成员(pubic)返回类的原型(prototype)
  • key:当前属性名
  • descriptor:属性描述符对象

没有返回值

// 定义
function propDecorator (
    target: object
    key: string,
    descriptor: PropertyDescriptor
) {
}
// 使用
class Test {
    @propDecorator
    public propName: string
}

参数装饰器

参数装饰器接收三个参数

  • target:
    • 如果是静态成员(static)返回class
    • 如果是实例成员(pubic)返回类的原型(prototype)
  • methodName:当前方法名
  • index:参数arguments下标

没有返回值

// 定义
function paramDecorator (
    target: object
    methodName: string,
    index: number
) {
}
// 使用
class Test {
    getName(@paramDecorator id: string) {}
}

实现

上面也提到了,装饰器本身是通过高阶函数实现的,所以多个装饰器修饰时,会类似于洋葱一样嵌套函数调用,所以推出@这样的语法糖

以成员装饰器为例,我们通过 tsc 将上面的代码转换成 js,来看看是怎么做的

// 定义
function propDecorator (
    target: object
    key: string,
    descriptor: PropertyDescriptor
) {
}
// 使用
var Test = (function () {
    function Test(){}
    __decorate(
        [propDecorator],
        Test.prototype,
        'propName'
    )
    return Test
})()

__decorate 函数__decorate 是一个辅助函数,它用于应用装饰器。这个函数一般由编译器自动生成,但在纯 JavaScript 环境中,你可以手动定义它。

注!这里的 __decorate函数 第一个参数是数组,证明支持同时应用多个装饰器。