【不止前端】JavaScript中的装饰器简介

什么是装饰器

在 JavaScript 中,装饰器其实就是一个函数,它可以改变或修饰类、类的方法、属性、参数等的行为。装饰器以 @ 符号开始,紧跟着是装饰器函数的名字。

需要注意的是,目前 JavaScript 的装饰器还处于提案阶段,因此不能在所有的环境下使用。但我们可以通过 Babel 或 TypeScript 这类工具来支持它。

如何使用装饰器

以下是一个简单的装饰器示例:

1
2
3
4
5
6
7
8
9
10
// 定义一个装饰器
function log(target) {
console.log(`${target} has been decorated`);
}

// 使用装饰器
@log
class ExampleClass {
//...
}

在这个例子中,log 是一个装饰器函数,它在 ExampleClass 被定义时会被调用,并且它的参数 target 就是被修饰的 ExampleClass

装饰器的种类

JavaScript 中主要有三种类型的装饰器:

  1. 类装饰器:应用于类构造函数,用于监视,修改或替换类定义。传入的参数是类的构造函数。
  2. 方法装饰器:应用于类的方法,用于监视,修改或替换方法定义。传入的参数分别是类的原型对象、方法的名字和方法的属性描述对象。
  3. 属性装饰器:应用于类的属性,可以观察和修改类的属性定义。

一、类装饰器

类装饰器声明在一个类之前(紧靠着类声明)。类装饰器应用于类构造器,能够用来监视,修改或替换类定义。

举个例子,我们定义一个简单的类装饰器 @sealed,它会禁止一个类被继承:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function sealed(constructor: Function) {
Object.seal(constructor);
Object.seal(constructor.prototype);
}

@sealed
class Greeter {
greeting: string;
constructor(message: string) {
this.greeting = message;
}
greet() {
return "Hello, " + this.greeting;
}
}

二、方法装饰器

方法装饰器声明在一个方法声明之前(紧靠着方法声明),被应用到方法的 属性描述符上,可以用来监视,修改或替换方法定义。

举个例子,我们可以定义一个 @enumerable 装饰器,来控制一个方法是否可以被遍历:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
function enumerable(value: boolean) {
return function(
target: any,
propertyKey: string,
descriptor: PropertyDescriptor
) {
descriptor.enumerable = value;
};
}

class Greeter {
greeting: string;
constructor(message: string) {
this.greeting = message;
}

@enumerable(false)
greet() {
return "Hello, " + this.greeting;
}
}

三、属性装饰器

属性装饰器声明在一个属性声明之前(紧靠着属性声明)。属性装饰器不能用在声明文件中,或在任何外部上下文(如 declare 的类)中。

举个例子,我们可以定义一个 @format 装饰器,将一个属性的值转换为特定的格式:

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
function format(formatString: string) {
return function(target: any, propertyKey: string) {
let value = this[propertyKey];

Object.defineProperty(target, propertyKey, {
get: function() {
return value;
},
set: function(newVal) {
value = formatString.replace('%s', newVal);
},
enumerable: true,
configurable: true
});
};
}

class Greeter {
@format('Hello, %s')
greeting: string;

constructor(message: string) {
this.greeting = message;
}
}

在上述代码中,@format('Hello, %s') 会将 greeting 属性的值设为 ‘Hello, greeting’。