JS装饰器(Decorator)
Contents
概念
装饰器是 ES7 的一个新语法,在Typescript已经支持。作用就是**对一些对象进行装饰后返回一个被包装过的对象。**装饰器本质是基于Object.defineProperty 的语法糖,参数也与 Object.defineProperty相同,即:
参数名 | 描述 |
---|---|
target | 目标对象 |
name | 属性名 |
descriptor | 针对该属性的描述符 |
可以被应用在:
- 类
- 类属性
- 类方法
- 类访问器
- 类方法的参数
// 类装饰器
@classDecorator
class Bird {
// 属性装饰器
@propertyDecorator
name: string;
// 方法装饰器
@methodDecorator
fly(
// 参数装饰器
@parameterDecorator
meters: number
) {}
// 访问器装饰器
@accessorDecorator
get egg() {}
}
执行
时机
装饰器只在解释执行时应用一次,例如:
function f(C) {
console.log('apply decorator')
return C
}
@f
class A {}
// output: apply decorator
// 即便没有使用类A,也会打印`apply decorator`
执行顺序
对于属性/方法/访问器装饰器而言,执行顺序取决于声明它们的顺序。
而对于参数装饰器来说执行顺序是相反的, 最后一个参数的装饰器会最先被执行。
当多个装饰器应用于一个声明上,将由上至下依次对装饰器表达式求值,求值的结果会被当作函数,由下至上依次调用.
依次为:
- 从上往下,谁先写谁执行。
- 先实例属性,后静态属性。
- 先参数装饰器再方法装饰器,而且会在一起执行。
- 最后类装饰器。
function f(key: string): any {
console.log("evaluate: ", key);
return function () {
console.log("call: ", key);
};
}
@f("Class Decorator")
class C {
@f("Static Property")
static prop?: number;
@f("Static Method")
static method(@f("Static Method Parameter") foo) { }
constructor(@f("Constructor Parameter") foo) { }
@f("Instance Method Outer ")
@f("Instance Method Inner ")
method(@f("Instance Method Parameter") foo) { }
@f("Instance Property")
prop?: number;
}
// evaluate: Instance Method Outer
// evaluate: Instance Method Inner
// evaluate: Instance Method Parameter
// call: Instance Method Parameter
// call: Instance Method Inner
// call: Instance Method Outer
// evaluate: Instance Property
// call: Instance Property
// evaluate: Static Property
// call: Static Property
// evaluate: Static Method
// evaluate: Static Method Parameter
// call: Static Method Parameter
// call: Static Method
// evaluate: Class Decorator
// evaluate: Constructor Parameter
// call: Constructor Parameter
// call: Class Decorator
用法
类装饰器
类型
type ClassDecorator = <TFunction extends Function>
(target: TFunction) => TFunction | void;
参数
参数名 | 描述 |
---|---|
target | 类的构造器 |
返回
如果类装饰器返回了一个值,她将会被用来代替原有的类构造器的声明。
应用场景
类装饰器适合用于继承一个现有类并添加一些属性和方法。
type Consturctor = { new (...args: any[]): any };
function toString<T extends Consturctor>(BaseClass: T) {
return class extends BaseClass {
toString() {
return JSON.stringify(this);
}
};
}
@toString
class C {
public foo = "foo";
public num = 24;
}
console.log(new C().toString())
// -> {"foo":"foo","num":24}
类属性装饰器
参数
参数名 | 描述 |
---|---|
target | 对于静态成员来说是类的构造器,对于实例成员来说是类的原型链。 |
name | 属性名 |
返回
返回值会被忽略
应用场景
- 收集信息
- 给类添加额外的方法和属性
function observable(target: any, key: string): void {
let val = target[key];
Reflect.defineProperty(target, key, {
get() {
console.log('拦截到正在获取属性:' + key);
return val
},
set(next) {
console.log(`prev:${val},next:${next}`);
val = next
}
})
}
class C {
@observable
foo = -1; // prev: undefined, next: -1
@observable
bar = "bar"; // prev: undefined, next: bar
}
const c = new C();
c.foo = 100; // -> prev: -1, next: 100
c.foo = -3.14; // -> prev: 100, next: -3.14
c.bar = "baz"; // -> prev: bar, next: baz
c.bar = "sing"; // -> prev: baz, next: sing
console.log(c.bar); // -> 拦截到正在获取属性:bar
console.log(c.foo); // -> 拦截到正在获取属性:foo
console.log(c) // 不会输出,只有target[key]的情况下,defineProperty才会拦截到
类方法/访问器装饰器
参数
参数名 | 描述 |
---|---|
target | 对于静态成员来说是类的构造器,对于实例成员来说是类的原型链。 |
propertyKey | 属性名称 |
descriptor | 属性的描述器。 |
访问器装饰器唯一的区别在于描述器:
方法装饰器的描述器的key为:
value
writable
enumerable
configurable
访问器装饰器的描述器的key为:
get
set
enumerable
configurable
返回值
如果返回了值,它会被用于替代属性的描述器。
应用场景
方法装饰器不同于属性装饰器的地方在于descriptor
参数。 通过这个参数可以修改方法原本的实现,添加一些共用逻辑。
例如:
- 增加消除繁琐的try/catch块,装饰器内统一输出函数日志。
- 校验参数或返回值类型
以打印输入与输出举例:
function logger(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
const original = descriptor.value;
descriptor.value = function (...args) {
console.log('params: ', ...args);
const result = original.call(this, ...args);
console.log('result: ', result);
return result;
}
}
class C {
@logger
add(x: number, y: number) {
return x + y;
}
}
const c = new C();
c.add(1, 2);
// -> params: 1, 2
// -> result: 3
类参数装饰器
类型
type ParameterDecorator = (
target: Object,
propertyKey: string | symbol,
parameterIndex: number
) => void;
参数
参数名 | 描述 |
---|---|
target | 对于静态成员来说是类的构造器,对于实例成员来说是类的原型链。 |
name | 属性的名称(注意是方法的名称,而不是参数的名称)。 |
index | 参数在方法中所处的位置的下标。 |
返回
返回的值将会被忽略。
应用场景
只能做一些参数的记录,不能更改参数相关的任何内容,(除非配合reflect-metadata
),一般需要配合其他的装饰器使用。例如配合方法装饰器进行运行时的参数检查。
装饰器工厂
如果想要传递参数,使装饰器变成类似工厂函数,只需要在装饰器函数内部再函数一个函数即可,如下:
function addAge(name: string) {
return function (constructor: Function) {
constructor.prototype.name = name;
}
}
@addAge('bhwa233')
class Person {
name: string;
}
let person = new Person()
console.log(person.name); //bhwa233
参考链接: