TypeScript:装饰器
一、什么是装饰器
装饰器是一种特殊类型的声明,它能够被附加到类声明、方法、属性或者参数上,
装饰期语法:
装饰器使用 @expression 这种形式,以@开头,expression求值后必须为一个函数,它会在运行时被调用,被装饰的声明信息做为参数传入
function logController(target: any) { //target:运行时,被装饰的声明的信息Person的构造函数
console.log('log')
}
//@logController: @ + expression
//运行时被调用,被装饰的声明信息做为参数传入
@logController class Person {
name: string = ''
age: number = 0
constructor(name: string, age: number) { this.name = name this.age = age } } const p = new Person('张三', 18) // log
开启装饰器:
装饰器目前还是一项实验性特性,若要启用实验性的装饰器特性,必须tsconfig.json里启用experimentalDecorators编译器选项
// tsconfig.json { "compilerOptions": { "experimentalDecorators": true // 开启装饰器 } }
常见装饰器:
类装饰器、属性装饰器、方法装饰器、参数装饰器
装饰器的写法:
分为普通装饰器(无法传参)和 装饰器工厂(可以传参)
二、装饰器的写法
- 普通装饰器
interface Person { name: string age: string } function enhancer(target: any) { target.prototype.name = '金色小芝麻' target.prototype.age = '18' } @enhancer class Person { //@enhancer 普通装饰器 constructor(){} print(){ console.log(this.name); //金色小芝麻 console.log(this.age); //18 } } const person = new Person() person.print()
-
装饰器工厂
interface Person { name: string age: number } // 利用函数柯里化解决传参问题, 向装饰器传入一些参数,也可以叫 参数注解 function enhancer(name: string) { return function enhancer(target: any) { target.prototype.name = name // 这个 name 就是装饰器的元数据,外界传递进来的参数 target.prototype.age = 18 } } //在使用装饰器的时候, 为其指定元数据 @enhancer('小芝麻') class Person { constructor() { } }
三、装饰器的分类
1. 类装饰器
类装饰器在类声明之前声明(紧靠着类声明),用来监视、修改或者替换类定义
类装饰器不能用在声明文件中(
.d.ts),也不能用在任何外部上下文中(比如declare的类)。
类装饰器表达式会在运行时当作函数被调用,类的构造函数作为其唯一的参数。
如果类装饰器返回一个值,它会使用提供的构造函数来替换类的声明。
类装饰器的参数有且只有一个:
target:类的构造函数。
interface Person { name: string age: string } function enhancer(target: any) { target.prototype.name = '金色小芝麻' target.prototype.age = '18' } @enhancer class Person { constructor() { } } let p = new Person() console.log(p.age) // 18
2. 属性装饰器
属性装饰器用来装饰属性
属性装饰器表达式会在运行时当做函数被调用,传入下列两个参数
- 第一个参数(target): 对于静态成员来说是类的构造函数,对于实例成员是类的原型对象
- 第二个参数(propertyKey): 是属性的名称
function enhancer(target:any, propertyKey: string) { console.log(target); //{} console.log("key: "+propertyKey); //key: name } class Person { @enhancer name: string; constructor() { this.name = '金色小芝麻' } }
3. 方法装饰器
方法装饰器用来装饰方法
- 第一个参数(target): 对于静态成员来说是类的构造函数,对于实例成员是类的原型对象
- 第二个参数(propertyKey): 是方法的名称
- 第三个参数(descriptor): 是方法的描述 修饰方法
function enhancer(target: any, propertyKey: string, descriptor: PropertyDescriptor) { // target 如果装饰的是个普通属性的话,那么这个 target 指向类的原型 Person.prototype console.log(target); // Person { getName: [Function] } console.log("key " + propertyKey); // key getName console.log("desc " + JSON.stringify(descriptor)); // {"writable":true,"enumerable":true,"configurable":true} }; class Person { name: string; constructor() { this.name = '金色小芝麻'; } @enhancer getName() { return 'getName'; } } const user = new Person(); user.getName = function () { return '金色小芝麻' } console.log(user.getName()); // '金色小芝麻'
修饰静态方法
// 声明装饰器修饰静态方法 function enhancer(target: any, propertyKey: string, descriptor: PropertyDescriptor) { // target 装饰的是一个类的属性static,那么这个 target 指向类的定义 console.log(target); // [Function: Person] { getAge: [Function] } console.log("key " + propertyKey); // key getAge console.log("desc " + JSON.stringify(descriptor)); // {"writable":true,"enumerable":true,"configurable":true} }; class Person { age: number = 18; constructor() { } @enhancer static getAge() { return 'static getAge'; } } const user = new Person(); Person.getAge = function () { return '你好啊!' } console.log(Person.getAge()) // 你好啊!
4. 参数装饰器
参数装饰器用来装饰参数
- 第一个参数(target): 对于静态成员来说是类的构造函数,对于实例成员是类的原型对象
- 第二个参数(propertyKey): 成员的名字
- 第三个参数(paramIndex): 参数在函数参数列表中的索引
function enhancer(target: any, propertyKey: string, parameterIndex: number) { console.log(target); // Person { getName: [Function] } console.log("key " + propertyKey); // key getName console.log("index " + parameterIndex); // index 0 }; class Person { name: string; constructor() { this.name = '你好啊!'; } getName(@enhancer name: string) { return name } } const user = new Person(); user.name = '金色小芝麻' console.log(user.name) // '金色小芝麻'
5. 访问器装饰器
访问器装饰器作业用于访问器上,接收3个参数:
第一个参数(target): 对于静态成员来说是类的构造函数,对于实例成员是类的原型对象。第二个参数(propertyKey): 方法名。第三个参数(descriptor): 属性描述符。
const getSetDecorator = (target: any, propertyKey: string, descriptor: PropertyDescriptor) => { console.log(target, propertyKey, descriptor) } class Person { private _name: string = '' private _age: number = 0 constructor(name: string, age: number) { this._name = name this._age = age } @getSetDecorator get name() { return this._name } set name(name: string) { this._name = name } } const p = new Person('张三', 18)
TypeScript不允许同时装饰一个成员的get和set访问器,所以上面的set访问器不能够再添加装饰器,否则会报错。
6. 装饰器执行顺序
- 属性方法先执行,谁先写 先执行谁
- 方法的时候, 先参数在方法,而且一定会在一起
- 最后是类
- 如果同类型,先执行离类近的
四、实战例子
请求loading
在平时的业务开发中,我们往往会在请求接口数据时添加loading:
import { Loading } from 'element-ui'
export default {
methods: {
async fetchData() {
const loading = Loading.service()
try {
await api.getList()
} finally {
loading.close() // 关闭loading
}
}
}
}
通过装饰器实现后的样子:
export default { methods: { @loading() async fetchData() { await api.getList() } } }
装饰器实现代码:
import { Loading } from 'element-ui'
/**
* 装饰器:loading
*/
export const loading = (text = '加载中') => {
return (target, name, descriptor) => {
const fn = descriptor.value
descriptor.value = async (...rest) => {
const loading = Loading.service({
text
})
try {
return await fn.apply(this, rest)
} finally {
loading.close()
}
}
}
}
确认弹窗
在进行删除操作时我们一般会弹出确认弹窗,确认弹窗的代码一般也都很固定,因此我们也可以写一个装饰器来简化这个过程。
import { MessageBox } from 'element-ui'
/**
* 装饰器:确认弹窗
*/
export const confirm = (message) => {
return (target, name, descriptor) => {
const fn = descriptor.value
descriptor.value = async (...rest) => {
await MessageBox.confirm(message)
fn.apply(this, rest)
}
}
}
// 使用装饰器 export default { methods: { @confirm('确认删除吗?') async onDelete() { await api.deleteUser() } } }
以上两个例子只是装饰器应用于实际开发中的冰山一角,对装饰器感兴趣的朋友可以多深入了解并实践,实现更复杂的应用场景。
五、装饰器的原理
我们以下列 类装饰器 为例:
interface Person { name: string age: string } function enhancer(target: any) { target.xx = 'Person'; target.prototype.name = '金色小芝麻' target.prototype.age = '18' } @enhancer class Person { constructor() { } } let p = new Person()
编译结果为:

我们先把装饰器的实现这部分代码拿出来稍微整理一下代码如下:
var __decorate = // 当前上下文是否有 __decorate 这个函数,如果有就返回这个函数,如果没有就定义一个 //( this && this.__decorate 的作用 :为了避免重复声明) (this && this.__decorate) || function (decorators, target, key, desc) { /** * decorators: 装饰器数组, * target: 构造函数, * key: 方法名称, * desc: 方法描述 */ var c = arguments.length, // c: 当前参数的个数,此例中我们只传了两个参数,所以 c = 2 r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, // c < 3 所以 r = target(构造函数) d; // Reflect 详解:https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Reflect // 如果系统支持反射,则直接使用Reflect.decorate(decorators,target, key, desc)方法。 if (typeof Reflect === "object" && typeof Reflect.decorate === "function") { r = Reflect.decorate(decorators, target, key, desc); } else {// 否则自行定义实现装饰器机制的代码。 for (var i = decorators.length - 1; i >= 0; i--) { if (d = decorators[i]) { // 给 d 赋值为 装饰器数组中的每一项 并且 不为 undefined // c < 3 执行 d(r) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; } } } return c > 3 && r && Object.defineProperty(target, key, r), r; // return c > 3 && r && Object.defineProperty(target, key, r), r; // 等价于 => // if(c > 3 && r) { // Object.defineProperty(target, key, r) // } // return r }; var __metadata = (this && this.__metadata) || function (k, v) { if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); }; function enhancer(target) { // target 接收的实参是: r (是 构造函数 Person) target.xx = 'Person'; // 给 Person 增加属性 target.prototype.name = '金色小芝麻'; // 给 Person 的原型 增加 name 属性 target.prototype.age = '18'; // 给 Person 的原型 增加 age 属性 } let Person = class Person { constructor() { } }; Person = __decorate( // 传入两个实参,1、当前类使用的装饰器数组, 2、当前构造函数 [enhancer, __metadata("design:paramtypes", [])], Person ); let p = new Person();
执行步骤
1、定义一个 __decorate 函数(实现装饰器)
2、元数据我们暂时不考虑
3、定义一个 enhancer 函数
4、定义一个 Person 的 构造函数
5、给 Person 重新赋值
__decorate([ enhancer, __metadata("design:paramtypes", [])], Person);

浙公网安备 33010602011771号