- 这里就不介绍装饰器了, 感兴趣的可以去看 JS装饰器是什么
- 本文我们介绍一下 装饰器 Stage3 的用法
- 如果想要了解旧语法, 可以看 JS装饰器(Decorators)用法-Stage1(旧)
- 本文的所有示例都可以通过 Babel在线工具 进行编译后直接运行
装饰器 API
type Decorator = (value: Input, context: {
kind: string;
name: string | symbol;
access: {
get?(): unknown;
set?(value: unknown): void;
};
private?: boolean;
static?: boolean;
addInitializer?(initializer: () => void): void;
}) => Output | void;
当调用 decorator
时,它们会收到两个参数:
value
: 所要装饰的值, 当装饰类属性时为undefined
context
: 上下文对象,包含有关要装饰的值的信息kind
:表示装饰类型,可能的取值有class
、method
、getter
、setter
、field
、accessor
。name
:值的名称(被装饰的值的名称)access
: 访问这个值的方法,即存值器(set)和取值器(get)。static
: 是否是static
(静态) 类元素。仅适用于类元素。private
: 和static
类似, 是否为private
(私有) 类元素addInitializer
: 添加额外的初始化逻辑。适用于所有按类操作的装饰器
装饰器语法
类(class)装饰器
API 定义
可以看到比最开始的 装饰器 API 少了一些字段, 是因为具体的装饰器有些字段是不需要的
type ClassDecorator = (value: Function, context: {
kind: "class";
name: string | undefined;
addInitializer(initializer: () => void): void;
}) => Function | void;
用法
@logged
class C {}
// or (等价写法)
class C {}
C = logged(C, {
kind: "class",
name: "C",
}) ?? C;
示例
function logged(value, { kind, name }) {
return class extends value {
constructor(...args) {
super(...args);
console.log(`constructing an instance of ${name} with arguments ${args.join(", ")}`);
}
}
}
@logged
class C {}
new C(1);
// constructing an instance of C with arguments 1
类方法装饰器
API 定义
type ClassMethodDecorator = (value: Function, context: {
kind: "method";
name: string | symbol;
access: { get(): unknown };
static: boolean;
private: boolean;
addInitializer(initializer: () => void): void;
}) => Function | void;
用法
class C {
@logged
m(arg) {}
}
// or (等价写法)
class C {
m(arg) {}
}
C.prototype.m = logged(C.prototype.m, {
kind: "method",
name: "m",
static: false,
private: false,
}) ?? C.prototype.m;
示例
function logged(value, { kind, name }) {
if (kind === "method") {
return function (...args) {
console.log(`starting ${name} with arguments ${args.join(", ")}`);
const ret = value.call(this, ...args);
console.log(`ending ${name}`);
return ret;
};
}
}
class C {
@logged
m(arg) {}
}
new C().m(1);
// starting m with arguments 1
// ending m
存取器(set/get)装饰器
API 定义
// get
type ClassGetterDecorator = (value: Function, context: {
kind: "getter";
name: string | symbol;
access: { get(): unknown };
static: boolean;
private: boolean;
addInitializer(initializer: () => void): void;
}) => Function | void;
// set
type ClassSetterDecorator = (value: Function, context: {
kind: "setter";
name: string | symbol;
access: { set(value: unknown): void };
static: boolean;
private: boolean;
addInitializer(initializer: () => void): void;
}) => Function | void;
- 可以选择返回一个新的 getter/setter 函数来替换它
- 如果返回任何其他类型的值(
undefined
除外),则会引发错误。
用法
class C {
@foo
get x() {}
set x(val) {}
}
在以上示例中,@foo
仅应用于get x()
。set x()
未装饰
// 等价写法
class C {
get x() {}
set x(val) {}
}
let { get } = Object.getOwnPropertyDescriptor(C.prototype, "x");
get = foo(get, {
kind: "getter",
name: "x",
static: false,
private: false,
}) ?? get;
Object.defineProperty(C.prototype, "x", { get });
示例
我们对 @logged
装饰器稍加修改,就可以用在存取装饰器上
function logged(value, { kind, name }) {
if (kind === "method" || kind === "getter" || kind === "setter") {
return function (...args) {
console.log(`starting ${name} with arguments ${args.join(", ")}`);
const ret = value.call(this, ...args);
console.log(`ending ${name}`);
return ret;
};
}
}
class C {
@logged
set x(arg) {}
}
new C().x = 1
// starting x with arguments 1
// ending x
类属性装饰器
API 定义
type ClassFieldDecorator = (value: undefined, context: {
kind: "field";
name: string | symbol;
access: { get(): unknown, set(value: unknown): void };
static: boolean;
private: boolean;
}) => (initialValue: unknown) => unknown | void;
属性装饰器的第一个参数是undefined
,即不输入值。
用法
class C {
@logged x = 1;
}
// or (等价写)
let initializeX = logged(undefined, {
kind: "field",
name: "x",
static: false,
private: false,
}) ?? (initialValue) => initialValue;
class C {
x = initializeX.call(this, 1);
}
示例
function logged(value, { kind, name }) {
if (kind === "field") {
return function (initialValue) {
console.log(`initializing ${name} with value ${initialValue}`);
return initialValue;
};
}
}
class C {
@logged x = 1;
}
new C();
// initializing x with value 1
addInitializer 初始化方法
addInitializer
该方法除类字段之外都可以使用, 可以调用此方法完成一些初始化操作
- 类装饰器:在类完全定义之后以及分配类静态字段之后运行。
- 方法装饰器:在类构造期间运行,在属性初始化之前。
- 静态方法装饰器:在类定义期间运行,早于静态属性定义,但晚于类方法的定义。
addInitializer 的执行时机测试
function test(type) {
return function test(value, { addInitializer }) {
console.log(`run: ${type}-test`);
addInitializer(function() {
console.log(`run: ${type}-initializer`);
});
}
}
@test('class')
class MyClass {
@test('static_method')
static staticLog() {
console.log('run: static log');
}
@test('class_method')
classLog() {
console.log('run: class log');
}
}
console.log('run: new before');
const myClass = new MyClass();
console.log('run: new after');
// run: static_method-test
// run: class_method-test
// run: class-test
// run: static_method-initializer
// run: class_initializer
// run: new before
// run: class_method-initializer
// run: new after
accessor 命令
类装饰器引入了一个新命令accessor
, 通过在类字段前面添加关键字的方式使用
class C {
accessor x = 1;
}
它是一种简写形式,相当于声明属性x
是私有属性#x
的存取接口。上面的代码等同于下面的代码。
class C {
#x = 1;
get x() {
return this.#x;
}
set x(val) {
this.#x = val;
}
}
accessor
可以被装饰,装饰器具有以下特性(签名)
type ClassAutoAccessorDecorator = (
value: {
get: () => unknown;
set(value: unknown) => void;
},
context: {
kind: "accessor";
name: string | symbol;
access: { get(): unknown, set(value: unknown): void };
static: boolean;
private: boolean;
addInitializer(initializer: () => void): void;
}
) => {
get?: () => unknown;
set?: (value: unknown) => void;
init?: (initialValue: unknown) => unknown;
} | void;
下面我们来一个简单的示例
function logged(value, { kind, name }) {
let { get, set } = value;
return {
get() {
console.log(`getting ${name}`);
return get.call(this);
},
set(val) {
console.log(`setting ${name} to ${val}`);
return set.call(this, val);
},
init(initialValue) {
console.log(`initializing ${name} with value ${initialValue}`);
return initialValue;
}
};
}
class C {
@logged accessor x = 1;
}
const c = new C();
// initializing x with value 1
c.x;
// getting x
c.x = 123;
// setting x to 123