类装饰器修饰的是定义的类本身,我们可以为它添加属性、方法。类装饰器类型签名如下所示,第一个参数 value 就是被修饰的类。
类型签名如下所示:
type ClassDecorator =(value: Function, context:{
kind:"class";
name: string | undefined;
addInitializer(initializer:() void): void;})=> Function | void;
1.
2.
3.
4.
5.
为类扩展一个新方法
例如,再不改变原始代码的情况下,借助外部组件使用类装饰器为 Person 类扩展一个方法 run。
function addRunMethod(value,{ kind, name }){
if (kind ==="class"){
return class extends value {
constructor(...args){
super(...args);}
run(){
console.log(`Add a run method for ${this.name}`);}}}}
@addRunMethod
class Person {
constructor(name){
this.name= name
}}
const p1 = new Person('Tom')
p1.run()
const p2 = new Person('Jack')
p2.run()
下面定义了一个 Person 类,有一个 run(跑步) 方法,如果已经吃过饭了,他就有更多的力气,跑的就会快些,否则就会跑的慢些。sleep 方法是为了辅助做些延迟测试。
const sleep = ms new Promise(resolve setTimeout(() resolve(1), ms));
class Person {
async run(isEat){
const start =Date.now();
if (isEat){
await sleep(1000*1);
console.log(`execution time: ${Date.now()- start}`);
return `I can run fast.`;}
await sleep(1000*5);
console.log(`execution time: ${Date.now()- start}`);
return `I can't run any faster.`; }}const p = new Person();console.log(await p.run(true))
const sleep = ms new Promise(resolve setTimeout(() resolve(1), ms));
function executionTime(value, context){
const { kind, name }= context;
if (kind ==='method'){
return async function (...args){// 返回一个新方法,代替被装饰的方法
const start =Date.now();
const result = await value.apply(this, args);
console.log(`CALL method(${name}) execution time: ${Date.now()- start}`);
return result;};}}
class Person {
@executionTime
async run(isEat){
if (isEat){
await sleep(1000*1);//1 minute
return `I can run fast.`;}
await sleep(1000*5);//5 minute
return `I can't run any faster.`; }}const p = new Person();console.log(await p.run(true))
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
注意,类的构造函数不支持装饰器,尽管构造函数看起来像方法,但实际上不是方法。
示例:实现 @bind 解决 this 问题
下面代码直接执行 p.run() 是没问题的,但是将 p.run 提取为一个方法再执行,此时函数内的 this 执行就发生变化了,会被指向为 undefined,单独运行 run() 方法后就会出现 TypeError: Cannot read properties of undefined (reading '#name') 错误。
class Person {
#name
constructor(name){
this.#name= name;}
async run(){
console.log(`My name is ${this.#name}`);}}
const p = new Person('Tom');
const run = p.run;
run()
例如,在这里我们可以声明一个 @bind 装饰器在 addInitializer() 方法触发时将类方法绑定到类实例,解决 this 绑定问题。
function bind(value, context){
const { kind, name, addInitializer }= context;
if (kind ==='method'){
addInitializer(function (){
this[name]= value.bind(this);});}}
class Person {
...
@bind
async run(){
console.log(`My name is ${this.#name}`);}}
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
示例:实现 @deprecated 提醒废弃 API
对于某些在今后版本会被移除的 API,可以通过定义一个 @deprecated 的装饰器,用于 API 将要废弃的消息提醒。
const DEFAULT_MSG ='This function will be removed in future versions.';
function deprecated(value, context){
const { kind, name }= context;
if (kind ==='method'){
return function (...args){
console.warn(`DEPRECATION ${name}: ${DEFAULT_MSG}`);
const result = value.apply(this, args);
return result;};}}
class Person {
@deprecated
hello(){
console.log(`Hello world!`);}}
const p = new Person()
p.hello()
function logged(value, context){
const { kind, name }= context;
if (kind ==='method'|| kind ==='getter'|| kind ==='setter'){
return function (...args){// 返回一个新方法,代替被装饰的方法
console.log(`starting ${name} with arguments ${args.join(", ")}`);
const result = value.apply(this, args);
console.log(`ending ${name}`);
return result;};}}
class Person {
#name
constructor(name){
this.#name= name;}
@logged
get name(){
return this.#name;}
@logged
set name(name){
this.#name= name;}}
const p = new Person('Tom')
p.name='Jack'
p.name
const UNINITIALIZED = Symbol('UNINITIALIZED');
function readOnly(value, context){
const { get,set}= value;
const { name, kind }= context;
if (kind ==='accessor'){
return {
init(initialValue){
return initialValue || UNINITIALIZED;},
get(){
const value = get.call(this);
if (value === UNINITIALIZED){
throw new TypeError(
`Accessor ${name} hasn’t been initialized yet`
);}
return value;},set(newValue){
const oldValue = get.call(this);
if (oldValue !== UNINITIALIZED){
throw new TypeError(
`Accessor ${name} can only be set once`
);}set.call(this, newValue);},};}}
class Person {
@readOnly
accessor name ='Tom';}
const p = new Person()
console.log(p.name);// p.name='Jack'// TypeError: Accessor name can only be set once
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
类字段装饰器
类字段装饰器接收到的第一个参数 value 为 undefined,我们不能更改字段的名称。通过返回函数,我们可以接收字段的初始值并返回一个新的初始值。
function multiples(mutiple){
return (value,{ kind, name })=>{
if (kind ==='field'){
return initialValue initialValue * mutiple;}}}
class Person {
@multiples(3)count=2;}
const p = new Person();
console.log(p.count);//6
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
示例:依赖注入示例
Logger 是我们的基础类,Person 类似于我们的业务类。以下代码耦合的一点是 Person 与 Logger 产生了直接依赖,对于 Person 类只需要使用 Logger 实例,不需要把 Logger 实例化也放到 Person 类中。接下来我们要解决的是如何将两个依赖模块之间的初始化信息解耦。
class Logger {
info(...args){
console.log(...args);}}
class Person {
logger = new Logger();
run(){
this.logger.info('Hi!','I am running.')}}
const p = new Person();
p.run();