说说你对 TypeScript 中装饰器的理解?应用场景?
是什么
装饰器是一种特殊类型的声明, 它能够被附加到类声明, 方法, 访问符, 属性或参数上
是一种在不改变原类和使用继承的情况下, 动态地扩展对象功能
同样的, 本质也不是什么高大上的结构, 就是一个普通的函数, @expression 的形式其实是Object.defineProperty的语法糖
expression 求值后必须也是一个函数, 它会在运行时被调用, 被装饰的声明信息做为参数传入
使用方式
由于typescript是一个实验性特性, 若要使用, 需要在tsconfig.json文件启动, 如下:
{
"compilerOptions": {
"target": "ES5",
"experimentalDecorators": true
}
}typescript装饰器的使用和javascript基本一致
类的装饰器可以装饰:
- 类
- 方法/属性
- 参数
- 访问器
类装饰
例如声明一个函数 addAge 去给 Class 的属性 age 添加年龄.
function addAge(constructor: Function) {
constructor.prototype.age = 18;
}
@addAge
class Person{
name: string;
age!: number;
constructor() {
this.name = 'huihui';
}
}
let person = new Person();
console.log(person.age); // 18上述代码, 实际等同于以下形式:
Person = addAge(function Person() { ... });上述可以看到, 当装饰器作为修饰类的时候, 会把构造器传递进去。 constructor.prototype.age 就是在每一个实例化对象上面添加一个 age 属性
方法/属性装饰
同样, 装饰器可以用于修饰类的方法, 这时候装饰器函数接收的参数变成了:
target: 对象的原型propertyKey: 方法的名称descriptor: 方法的属性描述符
可以看到, 这三个属性实际就是Object.defineProperty的三个参数, 如果是类的属性, 则没有传递第三个参数
如下例子:
// 声明装饰器修饰方法/属性
function method(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
console.log(target);
console.log("prop " + propertyKey);
console.log("desc " + JSON.stringify(descriptor) + "\n\n");
descriptor.writable = false;
};
function property(target: any, propertyKey: string) {
console.log("target", target)
console.log("propertyKey", propertyKey)
}
class Person {
@property
name: string;
constructor() {
this.name = 'huihui';
}
@method
say () {
return 'instance method';
}
@method
static run() {
return 'static method';
}
}
const xmz = new Person();
// 修改实例方法say
xmz.say = function() {
return 'edit'
}参数装饰
接收3个参数, 分别是:
target: 当前对象的原型propertyKey: 参数的名称index: 参数数组中的位置
function logParameter(target: Object, propertyName: string, index: number) {
console.log(target);
console.log(propertyName);
console.log(index);
}
class Employee {
greet(@logParameter message: string): string {
return `hello ${message}`;
}
}
const emp = new Employee();
emp.greet('hello');访问器装饰
使用起来方式与方法装饰一致, 如下:
function modification(target: Object, propertyKey: string, descriptor: PropertyDescriptor) {
console.log(target);
console.log("prop " + propertyKey);
console.log("desc " + JSON.stringify(descriptor) + "\n\n");
};
class Person {
_name: string;
constructor() {
this._name = 'huihui';
}
@modification
get name() {
return this._name
}
}装饰器工厂
如果想要传递参数, 使装饰器变成类似工厂函数, 只需要在装饰器函数内部再函数一个函数即可, 如下:
function addAge(age: number) {
return function(constructor: Function) {
constructor.prototype.age = age
}
}
@addAge(10)
class Person{
name: string;
age!: number;
constructor() {
this.name = 'huihui';
}
}
let person = new Person();执行顺序
当多个装饰器应用于一个声明上, 将由上至下依次对装饰器表达式求值, 求值的结果会被当作函数, 由下至上依次调用, 例如如下:
function f() {
console.log("f(): evaluated");
return function (target, propertyKey: string, descriptor: PropertyDescriptor) {
console.log("f(): called");
}
}
function g() {
console.log("g(): evaluated");
return function (target, propertyKey: string, descriptor: PropertyDescriptor) {
console.log("g(): called");
}
}
class C {
@f()
@g()
method() {}
}
// 输出
f(): evaluated
g(): evaluated
g(): called
f(): called应用场景
可以看到, 使用装饰器存在两个显著的优点:
- 代码可读性变强了, 装饰器命名相当于一个注释
- 在不改变原有代码情况下, 对原来功能进行扩展
后面的使用场景中, 借助装饰器的特性, 除了提高可读性之后, 针对已经存在的类, 可以通过装饰器的特性, 在不改变原有代码情况下, 对原来功能进行扩展