一、Typescript 的类装饰器和方法装饰器
随着 TypeScript
和 ES6
里引入了类,在一些场景下我们需要额外的特性来支持标注或修改类及其成员。 装饰器(Decorators)
为我们在类的声明及成员上通过元编程语法添加标注提供了一种方式。 装饰器是一项实验性特性,在未来的版本中可能会发生改变。若要启用实验性的装饰器特性,你必须在 tsconfig.json
文件中进行配置开启,代码如下所示:
"experimentalDecorators" : true ,
"emitDecoratorMetadata" : true ,
装饰器是一种特殊类型的声明,它能够被附加到类声明,方法, 访问符,属性或参数上。 装饰器使用 @expression
这种形式,expression
求值后必须为一个函数,它会在运行时被调用,被装饰的声明信息做为参数传入。装饰器工厂就是一个简单的函数,它返回一个表达式,以供装饰器在运行时调用。在 TypeScript
里,当多个装饰器应用在一个声明上时,由上至下依次对装饰器表达式求值,求值的结果会被当作函数,由下至上依次调用。 装饰器本身是一个函数,类装饰器接受的参数是构造函数,装饰器通过 @
符号来使用。类被创建的时候,装饰器就会执行,对类进行修饰,多个类装饰器,从右到左、从下到上 依次执行,先收集的装饰器会后执行。对于工厂模式的类装饰器,函数执行后,这个函数的返回值就会作为装饰器。如下,创建 testDecorator
装饰器,接收的构造函数中,创建 getName
这个方法,通过 @testDecorator
去使用这个构造器。对于多个装饰器,从下到上执行,@testDecorator2
先执行,@testDecorator
后执行,代码如下所示:
function testDecorator ( constructor: any) {
constructor. prototype. getName = ( ) => {
console. log ( "Tom" ) ;
} ;
}
function testDecorator2 ( constructor: any) {
console. log ( "decorator2" ) ;
}
@testDecorator
@testDecorator2
class Test2 { }
const test = new Test2 ( ) ;
( test as any) . getName ( ) ;
function testDecorator3 ( flag: boolean) {
if ( flag) {
return function testDecorator ( constructor: any) {
constructor. prototype. getName = ( ) => {
console. log ( "Tom" ) ;
} ;
} ;
} else {
return function ( constructor: any) { } ;
}
}
@testDecorator3 ( true )
class Test3 { }
装饰器求值,类中不同声明上的装饰器将按以下规定的顺序应用,如下所示:
参数装饰器,然后依次是方法装饰器,访问符装饰器,或属性装饰器应用到每个实例成员 参数装饰器,然后依次是方法装饰器,访问符装饰器,或属性装饰器应用到每个静态成员 参数装饰器应用到构造函数 类装饰器应用到类
类装饰器在类声明之前被声明(紧靠着类声明)。 类装饰器应用于类构造函数,可以用来监视,修改或替换类定义。 类装饰器不能用在声明文件中( .d.ts)
,也不能用在任何外部上下文中(比如declare的类)
。类装饰器表达式会在运行时当作函数被调用,类的构造函数作为其唯一的参数。如果类装饰器返回一个值,它会使用提供的构造函数来替换类的声明。如果你要返回一个新的构造函数,你必须注意处理好原来的原型链。 在运行时的装饰器调用逻辑中 不会为你做这些。 一个构造函数,可以接收很多的参数,合并到一起就是数组,每一个参数都是any
类型,最终的返回值也是 any
类型。 T 这个泛型可以被这个构造函数所实例化出来,类包含这个构造函数。 类的装饰器,也可以对构造函数进行扩展。旧的构造函数先执行,最后再执行装饰器的函数,代码如下所示:
function testDecorator ( ) {
return function < T extends new ( ... args: any[ ] ) => any> ( constructor: T ) {
return class extends constructor {
name = "Tom" ;
getName ( ) {
return this . name;
}
} ;
} ;
}
const Test = testDecorator ( ) (
class {
name: string;
constructor ( name: string) {
this . name = name;
}
}
) ;
const test = new Test ( "Jack" ) ;
console. log ( test. getName ( ) ) ;
方法装饰器声明在一个方法的声明之前(紧靠着方法声明)。 它会被应用到方法的 属性描述符上,可以用来监视,修改或者替换方法定义。 方法装饰器不能用在声明文件( .d.ts
),重载或者任何外部上下文(比如declare
的类)中。 如果方法装饰器返回一个值,它会被用作方法的属性描述符。方法装饰器表达式会在运行时当作函数被调用,传入下列3
个参数,如下所示:
对于静态成员来说是类的构造函数,对于实例成员是类的原型对象 成员的名字 成员的属性描述符
对类中的方法进行装饰器,对普通方法,target
对应的是类的 prototype
。对静态方法,target
对应的是类的构造函数。方法装饰器可以对原型、对 key
值,对 descriptor
都可以进行修改,如下所示:
function getNameDecorator (
target: any,
key: string,
descriptor: PropertyDescriptor
) {
console. log ( target, key) ;
descriptor. writable = true ;
descriptor. value = function ( ) {
return "descriptor" ;
} ;
}
class Test {
name: string;
constructor ( name: string) {
this . name = name;
}
@getNameDecorator
getName ( ) {
return this . name;
}
}
const test = new Test ( "Tom" ) ;
console. log ( test. getName ( ) ) ;
二、Typescript 的访问器装饰器、属性装饰器和参数装饰器
访问器装饰器声明在一个访问器的声明之前(紧靠着访问器声明)。 访问器装饰器应用于访问器的 属性描述符并且可以用来监视,修改或替换一个访问器的定义。 访问器装饰器不能用在声明文件中(.d.ts)
,或者任何外部上下文(比如 declare的类)
里。 TypeScript
不允许同时装饰一个成员的 get
和 set
访问器。取而代之的是,一个成员的所有装饰的必须应用在文档顺序的第一个访问器上。这是因为,在装饰器应用于一个属性描述符时,它联合了get
和set
访问器,而不是分开声明的。 如果访问器装饰器返回一个值,它会被用作方法的属性描述符。访问器装饰器表达式会在运行时当作函数被调用,传入下列 3
个参数,如下所示:
对于静态成员来说是类的构造函数,对于实例成员是类的原型对象 成员的名字 成员的属性描述符
访问器装饰器,在 set
上进行装饰器的修改,代码如下所示:
function visitDecorator (
target: any,
key: string,
descriptor: PropertyDescriptor
) {
descriptor. writable = false ;
}
class Test {
private _name: string;
constructor ( name: string) {
this . _name = name;
}
get name ( ) {
return this . _name;
}
@visitDecorator
set name ( name: string) {
this . _name = name;
}
}
const test = new Test ( "Tom" ) ;
test. name = "zhangsan" ;
console. log ( test. name) ;
属性装饰器声明在一个属性声明之前(紧靠着属性声明)。 属性装饰器不能用在声明文件中(.d.ts)
,或者任何外部上下文(比如 declare的类)
里。属性描述符不会做为参数传入属性装饰器,这与 TypeScript
是如何初始化属性装饰器的有关。 因为目前没有办法在定义一个原型对象的成员时描述一个实例属性,并且没办法监视或修改一个属性的初始化方法。返回值也会被忽略。因此,属性描述符只能用来监视类中是否声明了某个名字的属性。属性装饰器表达式会在运行时当作函数被调用,传入下列 2
个参数,如下所示:
对于静态成员来说是类的构造函数,对于实例成员是类的原型对象 成员的名字
属性装饰器,可以修改属性的 descriptor
的值。性装饰器的写法也是 Decorator
的写法,接收两个参数,分别是原型和属性的名字,可以返回 一个 descriptor
替换掉属性原始的 descriptor
。使用属性装饰器不能直接修改属性上的值,实际上修改的是原型上的属性的值,但是这个属性是直接存储在类声明的实例上的。修改原型上的,并不会对修改实例上的有什么变更,没办法直接修改属性值。如下,修改的并不是实例上的 name
, 而是原型上的 name
,name
放在实例上,代码如下所示:
function nameDecorator ( target: any, key: string) : any {
target[ key] = "Jack" ;
}
class Test {
@nameDecorator
name = "Jack" ;
}
const test = new Test ( ) ;
console. log ( ( test as any) . _proto_. name) ;
参数装饰器声明在一个参数声明之前(紧靠着参数声明)。 参数装饰器应用于类构造函数或方法声明。 参数装饰器不能用在声明文件(.d.ts)
,重载或其它外部上下文(比如 declare的类)
里。参数装饰器的返回值会被忽略。参数装饰器只能用来监视一个方法的参数是否被传入。参数装饰器表达式会在运行时当作函数被调用 ,传入下列 3
个参数,如下:
对于静态成员来说是类的构造函数,对于实例成员是类的原型对象 成员的名字 参数在函数参数列表中的索引
参数装饰器,代码如下所示:
function paramDecorator ( target: any, method: string, paramIndex: number) {
console. log ( target, method, paramIndex) ;
}
class Test {
getInfo ( name: string, @paramDecorator age: number) {
console. log ( name, age) ;
}
}
const test = new Test ( ) ;
test. getInfo ( "Jack" , 30 ) ;
方法装饰器的应用,进行方法的异常捕获,代码如下所示:
const userInfo: any = undefined;
function catchError ( msg: string) {
return function ( target: any, key: string, descriptor: PropertyDescriptor) {
const fn = descriptor. value;
descriptor. value = function ( ) {
try {
fn ( ) ;
} catch ( e ) {
console. log ( msg) ;
}
} ;
} ;
}
class Test {
@catchError ( "userInfo.name 不存在" )
getName ( ) {
return userInfo. name;
}
@catchError ( "userInfo.age 不存在" )
getAge ( ) {
return userInfo. age;
}
@catchError ( "userInfo.gender 不存在" )
getGender ( ) {
return userInfo. gender;
}
}
const test = new Test ( ) ;
test. getName ( ) ;
test. getAge ( ) ;