在之前的一篇文章中,讲解过 JavaScript Decorator 的语法,只不过那是新的语法。这篇文章要介绍的是旧的 Decorator 语法。今年我刚入职的公司,项目代码大量用到了 Decorator 语法(Legacy 版本),所以这里打算再介绍一下 JavaScript Decorator:The Legacy Version。
如果对设计模式比较熟悉的话,应该知道 Decorator Pattern:通过封装一个已有的类获得一个装饰后的类,来达到扩展已有类的功能的模式。
JavaScript Decorator 也是同样的目的,可以用 @Decorator 装饰类、类方法、类属性和类方法参数,通过装饰来达到扩展功能的目的。
目前 Decorator 还没有进入 JavaScript 的标准语法,但是 TypeScript 已经支持了 Decorator,目前 Decorator 正处于 TC39 提案的 stage 2 阶段,所以 原生 JavaScript 最终也会包含 Decorator 的语法。
Decorator 就是一个用来改变类的成员(属性、方法)和类本身的普通的 JavaScript 函数(建议使用纯函数)。当你在类成员和类的头部使用
@decoratorFunction
语法时,decoratorFunction
会被传递一些参数调用,可以用来修改类和类成员。
为了能让 tsc 编译 Decorator 语法,需要给 tsc 传递一些参数。
target
:如果 target 设置为 es5 可能会存在一些问题,所以建议将 target 设置为 ESNext
experimentalDecorators
:开启 decorator 语法功能emitDecoratorMetaData
:decorator metadata 是另外一个实验性的功能,这个选项可以开启支持 decorator metadata 功能上述几个选项可以在 tsc 命令后添加:
$ tsc --target 'ESNext' --experimentalDecorators --emitDecoratorMetadata
也可以 tsconfig.json
文件中添加:
{
"compilerOptions": {
"target": "ESNext",
"experimentalDecorators": true,
"emitDecoratorMetadata": true
}
}
想学习 tsc 和 tsconfig.json 的知识?那么可以看看这篇文章:详解 tsconfig.json 文件
从官方文档中摘抄一些例子,进行讲解。
首先,定义一些 decorator,用于装饰 class、method、parameter 和 property。
// 用于装饰 class(类) 的 decorator
export function ClassDecorator(
constructor: (...args: any[]) => any,
) {
console.log(`Decorating ${constructor.name}`);
}
// 用于装饰 method(成员方法) 的 decorator
export function MethodDecorator(
target: any,
propertyKey: string | symbol,
descriptor: PropertyDescriptor,
) {
console.log(
`Decorating method ${propertyKey}` +
` from ${target.constructor.name}`,
);
}
// 用于装饰 parameter(方法的参数) 的 decorator
export function ParameterDecorator(
target: any,
propertyKey: string | symbol,
parameterIndex: number,
) {
console.log(
`Decorating parameter ${propertyKey}` +
` (index ${parameterIndex})` +
` from ${target.constructor.name}`,
);
}
// 用于装饰 property(成员属性) 的 decorator
export function PropertyDecorator(
target: any,
propertyKey: string | symbol,
) {
console.log(
`Decorating property ${propertyKey}` +
` from ${target.constructor.name}`,
);
}
定义了 decorator 后,通过在需要装饰的目标头部(前面)添加 @decoratorFun
语法来装饰目标,例如 @ClassDecorator class Foo {}
。
@ClassDecorator
class Demo {
@PropertyDecorator
public foo: string = "foo";
constructor() {
console.log("Simple class initialized");
this.writeGreeting();
}
@MethodDecorator
public get bar() {
return "bar";
}
@MethodDecorator
public writeGreeting(
@ParameterDecorator public greeting: string = "Hello, world",
) {
console.log(greeting);
}
}
const demo = new Demo();
编译执行上面的代码,会输出以下结果。
Decorating property foo from Demo
Decorating method bar from Demo
Decorating parameter writeGreeting (index 0) from Demo
Decorating method writeGreeting from Demo
Decorating Demo
Simple class initialized
Hello, world
执行顺序如下:
decorator 可以是一个高阶函数工厂,返回一个 decorator 函数。这样我们在使用 decorator 时,可以传递一些参数。
export function Decorator(type: string) {
return (...args: any[]) => {
console.log(type, args);
};
}
使用这个 decorator:
@Decorator("class")
class Demo {
@Decorator("property")
public foo: string = "foo";
constructor() {
console.log("Simple class initialized");
this.writeGreeting();
}
@Decorator("accessor")
public get bar() {
return "bar";
}
@Decorator("method")
public writeGreeting(
@Decorator("parameter") public greeting: string = "Hello, world",
) {
console.log(greeting);
}
}
const demo = new Demo();
编译执行,输出以下内容:
property [ Demo {}, 'foo', undefined ]
accessor [ Demo {},
'bar',
{ get: [Function: get bar],
set: undefined,
enumerable: false,
configurable: true } ]
parameter [ Demo {}, 'writeGreeting', 0 ]
method [ Demo {},
'writeGreeting',
{ value: [Function: writeGreeting],
writable: true,
enumerable: false,
configurable: true } ]
class [ [Function: Demo] ]
Simple class initialized
Hello, world
也可以同时使用多个 decorators 装饰同一个对象。
export function Enumerable(enumerable: boolean = true) {
console.log(
`Creating ${enumerable ? "" : "non-"}` +
`enumerable property factory`,
);
return function decorator(
target: any,
propertyKey: string | symbol,
descriptor: PropertyDescriptor,
) {
console.log(
`Making ${propertyKey}` +
` ${enumerable ? "" : "non-"}enumerable`,
);
descriptor.enumerable = enumerable;
return descriptor;
};
}
class Demo {
@Enumerable(true)
@Enumerable(false)
public resultIsEnumerable() {
// do nothing
}
}
编译执行输出以下内容:
Creating enumerable property factory
Creating non-enumerable property factory
Making resultIsEnumerable non-enumerable
Making resultIsEnumerable enumerable
执行的顺序如下:
Enumerable(true)
执行,返回 decoratorEnumerable(false)
执行,返回 decorator工厂越先执行,工厂返回的 decorator 越后执行。
(完)