TS系列篇|装饰器(@)
"不畏惧,不将就,未来的日子好好努力"——大家好!我是小芝麻😄
装饰器是一种特殊类型的声明,它能够被附加到类声明
、方法
、属性
或者参数
上,
- 语法:装饰器使用
@expression
这种形式,expression
求值后必须为一个函数,它会在运行时被调用,被装饰的声明信息做为参数传入 - 若要启用实验性的装饰器特性,必须
tsconfig.json
里启用experimentalDecorators
编译器选项 - 常见的装饰器有:
类装饰器
、属性装饰器
、方法装饰器
、参数装饰器
- 装饰器的写法: 分为
普通装饰器(无法传参)
和装饰器工厂(可以传参)
1、装饰器的写法
1.1 普通装饰器
interface Person {
name: string
age: string
}
function enhancer(target: any) {
target.prototype.name = '金色小芝麻'
target.prototype.age = '18'
}
@enhancer // 普通装饰器
class Person {
constructor() { }
}
1.2 装饰器工厂
interface Person {
name: string
age: number
}
// 利用函数柯里化解决传参问题, 向装饰器传入一些参数,也可以叫 参数注解
function enhancer(name: string) {
return function enhancer(target: any) {
// 这个 name 就是装饰器的元数据,外界传递进来的参数
target.prototype.name = name
target.prototype.age = 18
}
}
@enhancer('小芝麻') // 在使用装饰器的时候, 为其指定元数据
class Person {
constructor() {}
}
2、装饰器的分类
2.1 类装饰器
类装饰器在类声明之前声明(紧靠着类声明),用来
监视
、修改
或者替换
类定义
- 类装饰器不能用在声明文件中(
.d.ts
),也不能用在任何外部上下文中(比如declare
的类)。 - 类装饰器表达式会在运行时当作函数被调用,类的构造函数作为其唯一的参数。
- 如果类装饰器返回一个值,它会使用提供的构造函数来替换类的声明。
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()
console.log(Person.name); // Person
console.log(p.age) // 18
2.2 属性装饰器
- 属性装饰器用来装饰属性
- 属性装饰器表达式会在运行时当做函数被调用,传入下列两个参数
- 第一个参数: 对于静态成员来说是类的构造函数,对于实例成员是类的原型对象
- 第二个参数: 是属性的名称
function enhancer(target: any, propertyKey: string) {
console.log(target); // Person {}
console.log("key " + propertyKey); // key name
};
class Person {
@enhancer
name: string;
constructor() {
this.name = '金色小芝麻';
}
}
const user = new Person();
user.name = '你好啊!'
console.log(user.name) // 你好啊!
2.3 方法装饰器
- 方法装饰器用来装饰方法
- 第一个参数: 对于静态成员来说是类的构造函数,对于实例成员是类的原型对象
- 第二个参数: 是方法的名称
- 第三个参数: 是方法的描述 修饰方法
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()) // 你好啊!
2.4 参数装饰器
- 参数装饰器用来装饰参数
- 第一个参数: 对于静态成员来说是类的构造函数,对于实例成员是类的原型对象
- 第二个参数: 成员的名字
- 第三个参数: 参数在函数参数列表中的索引
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) // '金色小芝麻'
2.5 装饰器执行顺序
- 属性方法先执行,谁先写 先执行谁
- 方法的时候, 先参数在方法,而且一定会在一起
- 最后是类
- 如果同类型,先执行离类近的
3、装饰器的原理
我们以下列 类装饰器 为例:
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();
2.1 执行步骤
- 1、定义一个
__decorate
函数(实现装饰器) - 2、元数据我们暂时不考虑
- 3、定义一个
enhancer
函数 - 4、定义一个
Person
的 构造函数 - 5、给
Person
重新赋值- 5.1 执行:
__decorate([ enhancer, __metadata("design:paramtypes", [])], Person);
- 5.1 执行:
参考文献
[1]. TypeScript中文网
[2]. TypeScript 入门教程
转载自:https://juejin.cn/post/7006483808832716813