Typescript的装饰器
装饰器是一种特殊类型的声明,它能够被附加到类声明、方法、属性或参数上,可以修改类的行为。
通俗的讲,装饰器就是一个方法,可以注入到类、方法、属性参数上来扩展类、属性、方法、参数的功能。
常见的装饰器有:类装饰器、属性装饰器、方法装饰器、参数装饰器
装饰器的写法:普通装饰器(无法传参)、装饰器工厂(可传参)。
对修饰器的实验支持是一项将在将来版本中更改的功能。
普通类装饰器定义
// 定义一个function,作为装饰器
// 里面的params参数,其实就是调用该装饰器的类
function logClass(params:any) {
console.log(params);
// 使用该装饰器的类,会增加一个apiUrl的属性
params.prototype.apiUrl = 'xxxx';
// 使用该装饰器的类,会增加一个run方法
params.prototype.run = function(){
console.log('run方法运行了.');
}
}
// 使用装饰器
@logClass
class HttpClient {
constructor() {
}
getData() {
}
}
// 测试
let a:any = new HttpClient();
console.log(a.apiUrl);
a.run();
定义装饰器工厂
// 定义装饰器工厂,params即为调用时传入的参数,target即调用的类本身
function logClass(params:string) {
return function(target:any) {
target.prototype.apiUri = params;
}
}
// 此处必须要传入参数,否则会报错
@logClass('http://xxxx')
class HttpClient {
constructor() {
}
getData() {
}
}
let a:any = new HttpClient();
console.log(a.apiUri);
类装饰器重写类的构造方法和其他方法
类装饰器表达式会在运行时当做函数被调用,类的构造函数作为唯一的参数。
如果类装饰器返回一个值,它会使用提供的构造函数来替换类的声明。
function logClass(target:any) {
console.log(target);
return class extends target {
// 在类构造方法后运行,修改apiUrl的值
apiUrl = '我是修改后的apiUrl';
// 重写getData方法
getData() {
console.log('修改后的getData方法');
console.log(this.apiUrl);
}
}
}
@logClass
class HttpClient {
public apiUrl:string | undefined;
constructor() {
this.apiUrl = '构造方法中的apiUrl';
}
getData() {
console.log(this.apiUrl);
}
}
let client = new HttpClient();
client.getData();
属性装饰器
属性装饰器表达式会在运行时当做函数被调用,传入下列2个参数:
1、对于静态成员来说是类的构造函数,对于实例成员是类的原型对象。
2、成员的名字
// 定义属性装饰器
// params:传入的值
// target:类的原型对象,即HttpClient.prototype
// attr:属性名称
function logProperty(params:any) {
return function(target:any, attr:any) {
console.log(params);
console.log(target);
console.log(attr);
// 修改apiUrl属性的值
target.attr = "https://yyyyyyy";
}
}
class HttpClient {
@logProperty("http://xxxxxxx")
public apiUrl:string | undefined;
constructor() {
this.apiUrl = '构造方法中的apiUrl';
}
getData() {
console.log(this.apiUrl);
}
}
方法装饰器
方法装饰器会在运行时传入下列三个参数:
1、对于静态成员来说,是类的构造函数;对于实例成员,是类的原型对象;
2、成员的名字;
3、成员的属性描述符;
// 定义方法装饰器
// params: 传入的参数
// target:如果方法是static的,target为类的构造函数;如果方法是实例化的,target为类的原型对象;
// methodName:方法名称
// describe:方法描述;describe.value就是方法本身
function logMethod(params:any) {
return function(target:any, methodName:any, describe:any) {
console.log(params);
console.log(target);
console.log(methodName);
console.log(describe);
// 修改或扩展类的属性
target.apiUrl = 'http://xxxxxx';
// 修改或扩展类的方法
target.run = function() {
console.log('run 方法运行了.');
}
// 修改方法装饰器装饰的方法:将传入的参数转换为string类型
let preMethod = describe.value; // 保存原来的方法
// 替换掉原来的方法
describe.value = function(...args:any[]) {
console.log('我是修改后的describe方法');
args = args.map((value:any) => String(value));
console.log(args);
// 调用原来的方法
preMethod.apply(this, args);
}
}
}
class HttpClient {
public apiUrl:string | undefined;
constructor() {
this.apiUrl = '构造方法中的apiUrl';
}
@logMethod("aaa")
getData(...args:any[]) {
// console.log(this.apiUrl);
console.log(args); // 此处输出的args也都变成了字符串
console.log('我是原来的getData方法');
}
}
let client = new HttpClient();
client.getData(1,2,'c');
方法参数装饰器
参数装饰器表达式会在运行时当做函数被调用,可以使用参数装饰器为类的原型增加一些数据元素,传入下列3个参数:
- 对于静态成员来说是类的构造函数,对于实例成员来说是类的原型对象;
- 方法的名称;
- 参数在函数参数列表中的索引。
// 定义方法参数装饰器
// params为调用该装饰器传入的参数
// target:静态成员是类的构造函数,实例成员是类的原型对象;
// methodName:方法名
// index:属性的位置,从0开始
function logParam(params:any) {
return function(target:any, methodName:any, index:any) {
console.log(params);
console.log(target);
console.log(methodName);
console.log(index);
}
}
class HttpClient {
public apiUrl:string | undefined;
constructor() {
this.apiUrl = '构造方法中的apiUrl';
}
// 使用方法参数装饰器
getData(@logParam('aaa') param:any) {
}
}
let a = new HttpClient();
a.getData('xxx');
装饰器执行顺序
从下向上、从内向外执行
// 参数加了问号变成可选参数,就可以使用@logClass()进行调用
function logClass1(params?:any) {
return function(target:any) {
console.log('类装饰器1: '+ params);
}
}
function logClass2(params?:any) {
return function(target:any) {
console.log('类装饰器2: '+ params);
}
}
function logAttribute(params?:any) {
return function(target:any, attributeName:any) {
console.log('属性装饰器: '+ params;
}
}
function logMethod(params?:any) {
return function(target:any, methodName:any, desc:any) {
console.log('方法装饰器: '+ params);
}
}
function logParams(params?:any) {
return function(target:any, methodName:any, index:any) {
console.log('方法参数装饰器: '+ params);
}
}
// 一个类上可以同时加上多个装饰器
@logClass1('test1')
@logClass1('test2')
@logClass2('class2')
class HttpClient {
@logAttribute('apiUrl')
public apiUrl:string | undefined;
constructor() {
this.apiUrl = '构造方法中的apiUrl';
}
@logMethod('getData')
getData(@logParams('getDataParam') param:any) {
}
@logMethod('setData')
setData(@logParams('setDataParam1') param1:any, @logParams('setDataParam2') param2:any) {
}
}
let a = new HttpClient();
a.getData('xxx');
a.setData('xxxx','yyyyy');
运行结果:
属性装饰器: apiUrl
方法参数装饰器: getDataParam
方法装饰器: getData
方法参数装饰器: setDataParam2
方法参数装饰器: setDataParam1
方法装饰器: setData
类装饰器2: class2
类装饰器1: test2
类装饰器1: test1