什么是装饰器
装饰器是一种特殊类型的声明,它能够被附加到类声明,方法, 访问符,属性或参数上。 装饰器使用 @expression这种形式,expression求值后必须为一个函数,它会在运行时被调用,被装饰的声明信息做为参数传入。
通俗的理解可以认为就是在原有代码外层包装了一层处理逻辑。 个人认为装饰器是一种解决方案,而并非是狭义的@Decorator,后者仅仅是一个语法糖罢了。
在TypeScript中装饰器还属于实验性语法,你必须在命令行或tsconfig.json里启用
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
总结
- 装饰器本身是一个函数
- 装饰器对类,方法,属性等等都可以进行装饰
- 装饰器通过@符号进行引用
装饰器执行时机
修饰器对类的行为的改变,是代码编译时发生的(不是TypeScript编译,而是js在执行机中编译阶段),而不是在运行时。这意味着,修饰器能在编译阶段运行代码。也就是说,修饰器本质就是编译时执行的函数。
在Node.js环境中模块一加载时就会执行
装饰器类型
方法装饰器
原型方法装饰器
// target 是 Test 的原型 类似于 Test.prototype
// propertyKey 是 方法的名字 这里面是 getName
// descriptor 是 属性描述符
// 属性描述符是对JavaScript属性的描述,包括:value、writable、enumerable、configurable,除value其他默认为true
function method(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
console.log(target);
console.log("prop " + propertyKey);
console.log("desc " + JSON.stringify(descriptor) + "\n\n");
};
function method2(flag) {
// 这里面可以做一些逻辑处理 判断 flag 做一些处理判断
return function(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
console.log(target);
console.log("prop " + propertyKey);
console.log("desc " + JSON.stringify(descriptor) + "\n\n");
};
};
class Test {
// 原型方法 是挂载到 Test.prototype 上的
@method
@method2(true)
getName() {
return 'name';
}
}
执行结果
// @method getName 输出
Test { getName: [Function] }
prop getName
desc {"writable":true,"enumerable":true,"configurable":true}
// @method2 getName 输出
Test { getName: [Function] }
prop getName
desc {"writable":true,"enumerable":true,"configurable":true}
静态方法装饰器
// target 是 Test 类,可以访问到 Test 类上的静态属性和静态方法
// propertyKey 是 方法的名字 这里面是 getAge
// descriptor 是 属性描述符
// 属性描述符是对JavaScript属性的描述,包括:value、writable、enumerable、configurable,除value其他默认为true
function method(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
console.log(target);
console.log("prop " + propertyKey);
console.log("desc " + JSON.stringify(descriptor) + "\n\n");
};
class Test {
@method
static getAge() {
return 20;
}
}
执行结果
{ [Function: Test] getAge: [Function] }
prop getAge
desc {"writable":true,"enumerable":true,"configurable":true}
属性装饰器
普通属性装饰器
// target 是 Test 的原型 类似于 Test.prototype
// propertyKey 是 属性的名字 这里面是 age
function attr(target: any, propertyKey: string) {
console.log(target);
console.log("prop " + propertyKey);
};
class Test {
@attr
age = 20;
}
执行结果
Test {}
prop age
静态属性装饰器
// target 是 Test 类,可以访问到 Test 类上的静态属性和静态方法
// propertyKey 是 属性的名字 这里面是 age
function attr(target: any, propertyKey: string) {
console.log(target);
console.log("prop " + propertyKey);
};
class Test {
@attr
static age = 30;
}
执行结果
{ [Function: Test] age: 30 }
prop age
方法参数装饰器
// target 这个参数根据方法是原型方法和静态方法有区分,如上类同
// 原型方法 => target 是 Test 的原型 类似于 Test.prototype
// 静态方法 => target 是 Test 类,可以访问到 Test 类上的静态属性和静态方法
// propertyKey 是 方法的名字 这里面是 getName 和 getAge
// propertyIndex 是方法里面参数的下标值,如 装饰器在第一个参数 propertyIndex 为0 在第二个参数 propertyIndex 为 1
function methodParams(target: any, propertyKey: string, propertyIndex: number) {
console.log(target);
console.log("prop " + propertyKey);
console.log("index " + propertyIndex);
};
class Test {
getName(@methodParams name: string, name2: string): string {
return name;
}
getName2(name: string, @methodParams name2: string): string {
return name;
}
static getAge(@methodParams name: string): string {
return name;
}
}
执行结果
Test { getName: [Function], getName2: [Function] }
prop getName
index 0 // 这个地方 index 值 为0
Test { getName: [Function], getName2: [Function] }
prop getName2
index 1 // 这个地方 index 值 为1
{ [Function: Test] getAge: [Function] }
prop getAge
index 0
访问器装饰器
// target 是 Test 的原型 类似于 Test.prototype
// propertyKey 是 方法的名字 这里面是 getName
// descriptor 是 属性描述符
// 属性描述符是对JavaScript属性的描述,包括:value、writable、enumerable、configurable,除value其他默认为true
function decorat(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
console.log(target);
console.log("prop " + propertyKey);
console.log("descriptor " + JSON.stringify(descriptor));
};
class Test {
_name = 'li';
// 访问器 name 属性 是在 Test.prototype 上的
@decorat
get name() {
return this._name;
}
set name(name) {
this._name = name;
}
}
执行结果
Test { name: [Getter/Setter] }
prop name
descriptor {"enumerable":true,"configurable":true}
注意:访问器装饰器 只能在
get或者set上使用,不能同时挂载装饰器,就算是挂载不同的装饰器也不行
类的装饰器
// 装饰器就是一个方法,所以定义一个方法,就是定义一个装饰器
// 类装饰器的方法的入参 constructor 是调用这个装饰器的类的构造函数
function TestDecorator(constructor: any) {
console.log(constructor)
constructor.prototype.getName = function() {
console.log('name');
}
}
// 装饰器的使用方法是 @[定义的装饰器]
@TestDecorator
class Test {}
const test = new Test();
// 波浪线提示错误 --> 类型“Test”上不存在属性“getName”。
test.getName(); // 不能正常执行
// 类型断言 去掉波浪线报错
(test as any).getName(); // 可以正常执行 控制台输出 name
类上面可以修饰多个装饰器
@TestDecorator
@TestDecorator1
class Test {}
调用顺序是 TestDecorator1 --> TestDecorator
装饰器可以工厂模式返回一个方法
// flag 可以用来判断 返回不同的装饰器
function TestDecorator2(flag: boolean) {
if(flag) {
return function(constructor: any) {
constructor.prototype.getName = function() {
console.log('name');
}
}
} else {
return function(constructor: any) {}
}
}
//
@TestDecorator2(false)
class Test {}
const test = new Test();
// 类型断言
(test as any).getName()
看到这发现上面的代码有几个问题,constructor 定义为 any 类型,调用 getName 方法,必须要用类型断言
function TestDecorator() {
// (...args: any[]) => any 这是一个函数 返回 any 类型
// 函数接受很多参数,合并到args 数组中去,参数是any类型
// new (...args: any[]) => any 是构造函数, T 继承了构造函数,那 constructor 就是也构造函数
return function <T extends new (...args: any[]) => any>(constructor: T) {
//
return class extends constructor {
getName() {
console.log('name');
}
}
}
}
// TestDecorator() 返回了装饰器,把 class {} 作为入参传递进去。 constructor 就是 class {}
//
const Test = TestDecorator()(
class {
}
)
const test2 = new Test2();
// 能访问到 getName 了
test2.getName();
案例实践
// 统一捕获异常装饰器
function dector(name: string) {
return function(target: any, key: string, descriptor: PropertyDescriptor) {
// 获取原方法
const fn = descriptor.value;
// 重新定义方法,把原方法包裹在 try catch 中
descriptor.value = function() {
try {
fn();
} catch(e) {
console.log(name + ' 方法 发生异常了');
}
}
}
}
const userInfo: any = undefined;
class Test {
@dector('getName')
getName() {
return userInfo.name;
}
@dector('getAge')
getAge() {
return userInfo.age;
}
// 如果不加装饰器,必须要在每个方法里面捕获异常,有了装饰器就很简洁,方便
// getHobby() {
// try {
// return userInfo.hobby
// } catch (e) {
// console.log('getHobby 方法 发生异常了')
// }
// }
}
const test = new Test();
test.getName();
test.getAge();
执行结果
getName 方法 发生异常了
getAge 方法 发生异常了