TS 装饰器

Decorator是ES7的一个新语法,目前仍处于提案中,nestjsArkTS都用到了此语法,在之前学习nestjs的时候,就对此语法有所了解,考虑到ArkTS也用到了此语法,所以就打算复习记录一下。

1. 什么是装饰器

装饰器是一种特殊类型的声明,它能够被附加到类声明,属性,方法或参数上,可以修改类的行为。装饰器以@开头,紧接着是一个表达式,这个表达式会在运行时被调用,运行结果会被装饰器使用。

使用装饰器,需要在tsconfig.json中开启experimentalDecorators配置项:

{ "compilerOptions": { "experimentalDecorators": true } }

2. 装饰器的分类

装饰器分为:类装饰器属性装饰器方法装饰器参数装饰器,它们的声明如下:

declare type ClassDecorator = <TFunction extends Function>(target: TFunction) => TFunction | void declare type PropertyDecorator = (target: Object, propertyKey: string | symbol) => void declare type MethodDecorator = <T>(target: Object, propertyKey: string | symbol, descriptor: TypedPropertyDescriptor<T>) => TypedPropertyDescriptor<T> | void declare type ParameterDecorator = (target: Object, propertyKey: string | symbol | undefined, parameterIndex: number) => void

两种装饰器写法:

  • 普通装饰器(无法传参)
  • 装饰器工厂(可传参),即返回一个函数,该函数会被当作装饰器使用

2.1 类装饰器

类装饰器在类声明之前被声明(紧靠着类声明)。类装饰器应用于类构造函数,可以用来监视,修改或替换类定义。该表达式会在运行时当作函数被调用,类的构造函数作为其唯一的参数。

主要作用:

普通装饰器

function logClass(target: any) { console.log(target) // 动态扩展属性 target.prototype.apiUrl = 'https://www.baidu.com' // 动态扩展方法 target.prototype.logUrl = function () { console.log(this.apiUrl) } } @logClass class HttpClient { constructor() {} } const http = new HttpClient() http.logUrl()

输出结果:

[Function: HttpClient] https://www.baidu.com

装饰器工厂

function logClass(params: string) { return function (target: any) { console.log(target) // 动态扩展属性 target.prototype.apiUrl = params // 动态扩展方法 target.prototype.run = function () { console.log(this.apiUrl) } } } @logClass('https://www.baidu.com') class HttpClient { constructor() {} } const http = new HttpClient() http.run()

输出结果:

[Function: HttpClient] https://www.baidu.com

装饰器重载构造函数

function logClass(target: any) { console.log(target) return class extends target { apiUrl: any = '我是修改后的数据' getData() { console.log(this.apiUrl) } } } @logClass class HttpClient { public apiUrl: string | undefined constructor() { this.apiUrl = '我是构造函数里面的apiUrl' } getData() { console.log(this.apiUrl) } } const http = new HttpClient() http.getData()

输出结果:

[Function: HttpClient] 我是修改后的数据

2.2 属性装饰器

属性装饰器声明在一个属性声明之前(紧靠着属性声明)。属性装饰器表达式会在运行时当作函数被调用,传入下列2个参数:

  • 对于静态成员来说是类的构造函数,对于实例成员是类的原型对象。
  • 成员的名字。

普通装饰器

function logProperty(target: any, attr: any) { console.log(target) console.log(attr) target[attr] = 'https://www.baidu.com' } class HttpClient { @logProperty public url: string | undefined constructor() {} } const http = new HttpClient() console.log(http.url)

输出结果:

[Function: HttpClient] url https://www.baidu.com

装饰器工厂

function logProperty(params: string) { return function (target: any, attr: any) { console.log(target) console.log(attr) target[attr] = params } } class HttpClient { @logProperty('https://www.baidu.com') public url: string | undefined constructor() {} } const http = new HttpClient() console.log(http.url)

输出结果同上。

2.3 方法装饰器

方法装饰器声明在一个方法声明之前(紧靠着方法声明)。它会被应用到方法的属性描述符上,可以用来监视,修改或者替换方法定义。方法装饰器表达式会在运行时当作函数被调用,传入下列3个参数:

  • 对于静态成员来说是类的构造函数,对于实例成员是类的原型对象。
  • 成员的名字。
  • 成员的属性描述符,可写writable,可枚举enumerable,可配置configurable

普通装饰器

function logMethod(target: any, methodName: string, desc: PropertyDescriptor) { console.log(target) console.log(methodName) console.log(desc) // 修改装饰器方法:把装饰器方法里面传入的所有参数改为string类型 const oMethod = desc.value desc.value = function (...args: any[]) { args = args.map(item => String(item)) console.log(args) oMethod.apply(this, args) } } class HttpClient { public url: string | undefined constructor() {} @logMethod public getData(...args: any[]) { console.log(args) console.log('我是getData里面的方法') } } const http = new HttpClient() http.getData(123, 'xxx')

输出结果:

[Function: HttpClient] getData { value: [Function: getData], writable: true, enumerable: false, configurable: true } [ '123', 'xxx' ] 我是getData里面的方法

装饰器工厂

function logMethod(params: string) { return function (target: any, methodName: string, desc: PropertyDescriptor) { console.log(target) console.log(methodName) console.log(desc) target.url = params const oMethod = desc.value // 修改装饰器方法:把装饰器方法里面传入的所有参数改为string类型 desc.value = function (...args: any[]) { args = args.map(item => String(item)) console.log(args) oMethod.apply(this, args) } } } class HttpClient { public url: string | undefined @logMethod('https://www.baidu.com') public getData(...args: any[]) { console.log(args) console.log('我是getData里面的方法') } public logUrl() { console.log(this.url) } } const http = new HttpClient() http.getData(123, 'hello') http.logUrl()

输出结果:

[Function: HttpClient] getData { value: [Function], writable: true, enumerable: false, configurable: true } [ '123', 'hello' ] [ '123', 'hello' ] 我是getData里面的方法 https://www.baidu.com

2.4 参数装饰器

参数装饰器声明在一个参数声明之前(紧靠着参数声明)。参数装饰器应用于类构造函数或方法声明。参数装饰器表达式会在运行时当作函数被调用,传入下列3个参数:

  • 对于静态成员来说是类的构造函数,对于实例成员是类的原型对象。
  • 成员的名字。
  • 参数在函数参数列表中的索引

普通装饰器

function logParams(target: any, methodName: string, paramsIndex: number) { console.log(target) console.log(methodName) console.log(paramsIndex) } class HttpClient { constructor() {} public getFn(@logParams url: string) {} }

输出结果:

[Function: HttpClient] getFn 0

装饰器工厂

function logParams(params: string) { return function (target: any, methodName: string, paramsIndex: number) { console.log(target) console.log(methodName) console.log(paramsIndex) target.apiUrl = params } } class HttpClient { public apiUrl: string | undefined constructor() {} public getFn(@logParams('https://www.baidu.com') url: string) { console.log(this.apiUrl) } } const http = new HttpClient() http.getFn('')

输出结果:

[Function: HttpClient] getFn 0 https://www.baidu.com

3. 装饰器的执行顺序

function logClass1(params: any) { console.log('logClass1') } function logClass2(params: any) { console.log('logClass2') } function logAttribute1(params: any, params2: any) { console.log('logAttribute1') } function logAttribute2(params: any, params2: any) { console.log('logAttribute2') } function logMethod1(params: any, params2: any, params3: any) { console.log('logMethod1') } function logMethod2(params: any, params2: any, params3: any) { console.log('logMethod2') } function logParams1(params: any, params2: any, params3: any) { console.log('logParams1') } function logParams2(params: any, params2: any, params3: any) { console.log('logParams2') } @logClass1 @logClass2 class HttpClient { @logAttribute1 @logAttribute2 public url: string | undefined constructor() {} @logMethod1 @logMethod2 public getFn(@logParams1 url: string, @logParams2 params: any) { console.log('getFn') } }

上面示例中,装饰器的执行顺序是:

logAttribute2 logAttribute1 logParams2 logParams1 logMethod2 logMethod1 logClass2 logClass1

4. 装饰器文档

TS 装饰器:https://www.tslang.cn/docs/handbook/decorators.html