前言
在3月底,js的装饰器提案终于进入了stage3,同时其metadata部分单独拆开仍处于stage2阶段([详见](github.com/tc39/propos…
decorator提案的历史
在[babel-plugin-proposal-decorators](babeljs.io/docs/en/bab…
-
legacy
stage 1阶段的提案,也是目前广为使用的用法,也基本等同于Typescript中开启
experimentalDecorators
的用法 -
2018-09
在2018.9进入stage2阶段后的提案,此时用法已经完全和stage1不一样
-
2021-12
2021.12针对此前的stage2提案又做了一次修改,用法又做了一点小修改
-
stage3
最近正式进入stage3的提案。对比2021-12只是去掉了
metadata
部分,提案本身没有太大的改变。不出意外这也是以后作为标准的装饰器
详细用法
legacy
我们广为使用的用法。即Typescript中tsconfig中配置experimentalDecorators:true
{
"compileOptions": {
"experimentalDecorators": true
}
}
或者@babel/plugin-proposal-decorators
配置
legacy: true
。注意:最新的@babel/plugin-proposal-decorators
已经将该配置迁移到version
字段,即version: legacy
legacy
下的装饰器更具体用法可以参考此前写的一篇文章2020的最后一天,不妨了解下装饰器。
装饰器函数的签名主要如下
-
类装饰器 (Class Decorators) 类装饰器作用于类的构造函数,可用于修改或者替换一个 class 定义。 一个装饰器函数签名如下:
type decorator = (target: Function) => Function | void;
它接收被装饰的 class 作为
target
函数的参数,如果装饰器函数有返回值,则使用这个返回值作为新的 class -
属性装饰器 (Property Decorators) 1、第一个参数。如果装饰的是静态方法,则是这个类 Target 本身;如果装饰的是原型方法,则是类的原型对象 Target.prototype
2、第二个参数。这个属性的名称
type decorator = ( target: Target | Target.prototype, propertyKey: string ) => void;
-
方法装饰器 (Method Decorators) + 访问器装饰器 (Accessor Decorators) 1、第一个参数。如果装饰的是静态方法,则是这个类
Target
本身;如果装饰的是原型方法,则是类的原型对象Target.prototype
2、第二个参数。这个方法的名称
3、第三个参数,这个方法的属性描述符,通过
descriptor.value
可以直接拿到这个方法如果属性装饰器有返回值,这个返回值讲作为这个方法的属性描述符。对象的属性描述符就是调用
Reflect.getOwnPropertyDescriptor(target, propertyKey)
的返回值
type decorator = (
target: Target | Target.prototype,
propertyKey: string,
descriptor: PropertyDescriptor
) => Function | void;
-
参数装饰器 (Parameter Decorators) 1、第一个参数。如果装饰的是静态方法的参数,则是这个类Target本身;如果装饰的是原型方法的参数,则是类的原型对象Target.prototype
2、第二个参数。参数所处的函数名称
3、第三个参数,该参数位于函数参数列表的位置下标(number)
type decorator = ( target: Target | Target.prototype, propertyKey: string, parameterIndex: number ) => void;
stage3
本次进入stage3提案的用法
装饰器函数签名如下:
type Decorator = (value: Input, context: {
kind: string;
name: string | symbol;
access: {
get?(): unknown;
set?(value: unknown): void;
};
isPrivate?: boolean;
isStatic?: boolean;
addInitializer?(initializer: () => void): void;
}) => Output | void;
装饰器函数包含两个入参参数
1、被装饰的值本身
2、被装饰值的上下文信息
- kind :
"class"|"method"|"getter"|"setter"|"field"|"accessor"
。表示装饰器的类型 - name 装饰值的名称
- access 同个该属性读写值
- isStatic 是否静态属性
- isPrivate 是否私有属性
- addInitializer 用于执行一些初始化逻辑
各种不同类型的装饰器如下
-
方法装饰器
type ClassMethodDecorator = (value: Function, context: { kind: "method"; name: string | symbol; access: { get(): unknown }; isStatic: boolean; isPrivate: boolean; addInitializer(initializer: () => void): void; }) => Function | void;
方法装饰器接收被装饰的方法作为第一个参数并可选的返回一个函数。返回的函数将替代原先的函数。方法装饰器可作用于静态方法或者原型方法
-
访问器装饰器
type ClassGetterDecorator = (value: Function, context: { kind: "getter"; name: string | symbol; access: { get(): unknown }; isStatic: boolean; isPrivate: boolean; addInitializer(initializer: () => void): void; }) => Function | void; type ClassSetterDecorator = (value: Function, context: { kind: "setter"; name: string | symbol; access: { set(value: unknown): void }; isStatic: boolean; isPrivate: boolean; addInitializer(initializer: () => void): void; }) => Function | void;
访问器装饰器和方法装饰器类似,接收被装饰器的原始方法,可以返回一个函数替代原始方法
-
属性装饰器
type ClassFieldDecorator = (value: undefined, context: { kind: "field"; name: string | symbol; access: { get(): unknown, set(value: unknown): void }; isStatic: boolean; isPrivate: boolean; }) => (initialValue: unknown) => unknown | void;
和访问器装饰器、方法装饰器区别,属性装饰器的第一个参数为undefined。属性装饰器可以返回一个初始化函数,返回的初始化函数的入参为原始属性值,返回值为替代原始的属性值
-
类装饰器
type ClassDecorator = (value: Function, context: { kind: "class"; name: string | undefined; addInitializer(initializer: () => void): void; }) => Function | void;
类装饰器第一个参数为被装饰的类,可以返回一个新的类去替代原有的类
此外stage3对比legacy提案特有的两个用法
-
类自动访问器(Class Auto-Accessors) 类自动访问器是一种新定义的行为。通过在类属性名前加上
accessor
关键字进行使用class C { accessor x = 1; } // 与常规字段不同,自动访问器在类原型上定义了 getter 和 setter。getter 和 setter 默认获取和设置私有字段上的值 // 等同如下代码 class C { #x = 1; get x() { return this.#x; } set x(val) { this.#x = val; } }
类自动访问器也可以被装饰,装饰器函数签名如下
type ClassAutoAccessorDecorator = ( value: { get: () => unknown; set(value: unknown) => void; }, context: { kind: "accessor"; name: string | symbol; access: { get(): unknown, set(value: unknown): void }; isStatic: boolean; isPrivate: boolean; addInitializer(initializer: () => void): void; } ) => { get?: () => unknown; set?: (value: unknown) => void; initialize?: (initialValue: unknown) => unknown; } | void;
和普通的属性访问器略有区别,装饰器函数第一个参数的入参是一个包含原型上get/set函数的对象。可以通过返回一个包含get/set函数的对象来取代被装饰值的行为。也可以通过
initialize
函数修改初始值 -
addInitializer
初始化逻辑函数 stage3的装饰器函数中context参数入参会有一个addInitializer
函数,它可以接收一个回调,用于执行一些初始化逻辑。一个实际的用法是例如注册web component。function customElement(name) { (value, { addInitializer }) => { addInitializer(function() { customElements.define(name, this); }); } } @customElement('my-element') class MyElement extends HTMLElement { static get observedAttributes() { return ['some', 'attrs']; } } // 等同如下代码 class MyElement { static get observedAttributes() { return ['some', 'attrs']; } } let initializersForMyElement = []; MyElement = customElement('my-element')(MyElement, { kind: "class", name: "MyElement", addInitializer(fn) { initializersForMyElement.push(fn); }, }) ?? MyElement; for (let initializer of initializersForMyElement) { initializer.call(MyElement); }
如果是
legacy
提案,我们是无法通过装饰器一步到位去注册web component的,必须手动调用customElements.define(name, xxx)
2018-09&2021-12
由于这两种用法在实际中很少特别使用这里只作简单差异化介绍
-
2018-09 只接收一个descriptor参数,并返回一个新的descriptor参数作为替代。 例如方法装饰器接收的descriptor如下
{ kind: "method" key: String, Symbol or Private Name, placement: "static", "prototype" or "own", ...Property Descriptor (argument to Object.defineProperty), method: The method itself }
-
2021-12 和stage3几乎完全一样,只是stage3在2021-12的基础上去除了metadata的部分
type Decorator = (value: Input, context: { kind: string; name: string | symbol; access: { get?(): unknown; set?(value: unknown): void; }; isPrivate?: boolean; isStatic?: boolean; addInitializer?(initializer: () => void): void; // 被移除的getMetadata 和 setMetadataa getMetadata(key: symbol); setMetadata(key: symbol, value: unknown); }) => Output | void;
metadata本文就不展开介绍,详情可参考
总结
stage3对比legacy
- 除了上面提到的语法区别。legacy装饰器是用“Target”(由当前被装饰目标决定是类本身还是类的原型)调用的,而在stage3中,不再提供这个
Target
给装饰器函数 - legacy装饰器会提供一个完整的
descriptor
对象,而stage3中只提供被装饰的值以及和它有关的上下文对象。在stage3中修改一个属性的attribute是不可能的,并且 getter 和 setter 不是“合并”而是单独被装饰
stage3对比2018-09
- 上面提到的语法的区别
- 功能上stage3是2018-09的子集