装饰器(Decorator)

105 阅读2分钟

最近在学习Nest框架时,发现大量使用了装饰器。所以,我们需要掌握修饰器的基本用法才能更好的理解Nest的写法。

装饰器(Decorator) 是一种特殊的函数,用于修改类、方法、属性或参数的行为。

类装饰器

类装饰器可以动态修改类的行为。

@withCreatedTime
class A {
    name: string
    
    constructor(name: string) {
        this.name = name
    }
}

function withCreatedTime(target: Function) {
    target.prototype.createdAt = new Date().getTime()
}

const a = new A('A')
console.log((a as any).createdAt) // 有效的时间戳

装饰器@withCreatedTime会给类A的实例添加createdAt属性。

类方法装饰器

用于对类的方法进行拦截,如添加日志、权限校验等。

注意:需要启用experimentalDecorators选项。

class A {
    name: string
    
    constructor(name: string) {
        this.name = name
    }
    
    @log
    sayName() {
        console.log('My name is ${this.name}')
    }
}

function log(target: Object, propertyKey: string, descriptor: PropertyDescriptor): PropertyDescriptor {
    const originMethod = descriptor.value
    
    descriptor.value = function(...args: any[]) {
        console.log(`A.prototype.${propertyKey} to be called!`)
        
        return originMethod.apply(this, args)
    }
    
    return descriptor
}

const a = new A('A')
a.sayName()

// 输出:
// A.prototype.sayName to be called!
// My name is A

装饰器@log拦截sayName方法,在执行前输出日志信息。

注意:修饰器不能用于函数。

类属性装饰器

类方法装饰器,这里就不重复叙述了。

参数装饰器

用于拦截方法的参数,可以自动填充默认值等。

注意:需要启用experimentalDecorators选项和emitDecoratorMetadata选项。

class A {
    name: string
    
    constructor(name: string) {
        this.name = name
    }
    
    @applyDefaultParams
    echo(@defaultParams('localhost') host?: string, @defaultParams('Hello') message?: string) {
        console.log(`${host} say: ${message}`)
    } 
}

function defaultParams(value: any) {
    return function (target: Object, propertyKey: string, parameterIndex: number) {
    const existingParams = Reflect.getOwnMetadata('defaultParams', target, propertyKey) || []

    existingParams[parameterIndex] = value

    Reflect.defineMetadata('defaultParams', existingParams, target, propertyKey)
  }
}

function applyDefaultParams(target: Object, propertyKey: string, descriptor: PropertyDescriptor) {
    const originalMethod = descriptor.value

    descriptor.value = function (...args: any[]) {
        const defaultParams: any[] = Reflect.getOwnMetadata('defaultParams', target, propertyKey) || []

        args = defaultParams.map((value, index) => (args[index] === undefined ? value : args[index]))

        return originalMethod.apply(this, args)
    }

    return descriptor
}

const a = new A('A')
a.echo()
a.echo('127.0.0.1')
a.echo('0.0.0.0', 'Hi')

// 输出:
// localhost say: Hello
// 127.0.0.1 say: Hello
// 0.0.0.0 say: Hi

装饰器@applyDefaultParams拦截方法,在执行前获取默认参数后传递给原方法;装饰器@defaultParams拦截方法参数,存储默认参数值,便于方法装饰器获取。

注意:

  1. 此示例需要引入reflect-metadata库,用于装饰器相关的元数据存取。
  2. 不能在参数装饰器里面直接通过target[propertyKey]修改方法的行为,因为它实际上并没有修改到descriptor.value的值。