什么是装饰器
装饰器翻译自单词 decorators
,在 JavaScript 中是一个能修改现有功能的函数。
这里的功能包含类或者类的方法。如果是修改类,称为类装饰器;修改类的方法,则是方法装饰器。
在 JavaScript 中使用装饰器
JavaScript 装饰器目前在 提案 阶段,可以通过 babel 或者 typescript 使用
在 babel 中使用
需要安装插件 @babel/plugin-proposal-decorators
yarn add @babel/plugin-proposal-decorators --dev
在配置文件 babel.config.json
中使用插件
{
"plugins": ["@babel/plugin-syntax-decorators"]
}
在 TypeScript 中使用
在 TypeScript
中是实验性支持的特性,使用时需要开启 experimentalDecorators
编译选项。开启方式:
- 命令行编译
tsc --target ES5 --experimentalDecorators
- 在配置文件
tsconfig.json
进行设置
{
"compilerOptions": {
"target": "ES5",
"experimentalDecorators": true
}
}
装饰类
在装饰类时,需要在类的声明上方使用 @
符号加上装饰器函数名。比如这里 annotation
函数就是一个类装饰器:
@annotation
class MyClass {}
function annotation(target) {
target.annotated = true
}
以上代码使用 babel
会被编译为
var _class
let MyClass = annotation(_class = class MyClass {}) || _class
function annotation(target) {
target.annotated = true
}
所以类装饰器的行为用代码表示就是
@decorator
class A {}
// 等同于
class A {}
A = decorator(A) || A
类装饰器是一个对类进行处理的函数,函数的第一个参数,是装饰的目标类:
function annotation(target) {
// ...
}
如果需要在使用时传入额外的参数,需要在外层封装一个函数:
@isTestable(true)
class MyClass {}
function isTestable(value) {
return function decorator(target) {
target.isTestable = value
}
}
装饰类的方法
装饰类的方法时,需要把装饰器放在方法上面:
class Cat {
@readonly
name() {}
}
function readonly(target, key, descriptor) {
descriptor.enumerable = value
return descriptor
}
类方法装饰器一共接收 3 个参数:
- 第 1 个参数
target
是类的原型对象 - 第 2 个参数
key
是装饰方法的名称 - 第 3 个参数
descriptor
是该方法的描述对象
当需要传入参数时,同样需要在外层封装一个函数:
class C {
@enumerable(false)
method() {}
}
function enumerable(value) {
return function(target, key, descriptor) {
descriptor.enumerable = value
return descriptor
}
}
同时使用多个装饰器
多个装饰器可以一起使用:
function first() {
console.log('first(): factory evaluated')
return function(target, key, descriptor) {
console.log('first(): called')
}
}
function second() {
console.log('second(): factory evaluated')
return function(target, key, descriptor) {
console.log('second(): called')
}
}
class ExampleClass {
@first()
@second()
method() {}
}
以上代码输出:
first(): factory evaluated
second(): factory evaluated
second(): called
first(): called
需要注意执行顺序,装饰器声明的执行顺序是从上往下,而调用时的执行顺序是从下往上。
使用场景
通过装饰器可以在不修改原有类的情况下扩展其功能,使用场景很广泛。以下举例说明:
- log:通过封装 log 装饰器,可以方便地记录 log
class Math {
@log
add(a, b) {
return a + b
}
}
function log(target, name, descriptor) {
var oldValue = descriptor.value
descriptor.value = function() {
console.log(`Calling ${name} with`, arguments)
return oldValue.apply(this, arguments)
}
return descriptor
}
TypeORM
是一个使用TypeScript
开发的 ORM,其中使用装饰器进行model
声明:
import { Entity, PrimaryGeneratedColumn, Column } from 'typeorm'
@Entity()
export class User {
@PrimaryGeneratedColumn()
id: number;
@Column()
firstName: string;
@Column()
lastName: string;
@Column()
age: number;
}
Nest
是一个使用TypeScript
开发的 Node.js 框架,其中使用装饰器声明路由
import { Controller, Get } from '@nestjs/common'
@Controller('cats')
export class CatsController {
@Get()
findAll(): string {
return 'This action returns all cats'
}
}