主题
TS学习系列03 ---TypeScript装饰器
一、装饰器是什么?
装饰器是一种特殊类型的声明,本质上就是一个方法,可以注入到类、方法、属性、参数上,扩展其功能;
简单总结:
- 它是一个表达式
- 该表达式被执行后,返回一个函数
- 函数的入参分别为 target、name 和 descriptor
- 执行该函数后,可能返回 descriptor 对象,用于配置 target 对象
二、装饰器分类及作用
常见的装饰器:
- 类装饰器 ClassDecorator
- 属性装饰器 PropertyDecorator
- 方法装饰器 MethodDecorator
- 参数装饰器 ParameterDecorator
装饰器在写法上有:
- 普通装饰器(无法传参)、
- 装饰器工厂(可传参)
作用
- 装饰器的目的就是为了给代码添加新的功能,随着程序的功能越来越多,需要给某一个小块添加上功能,这时可以使用装饰器,对类进行方法的新增操作,装饰器与ES6 中的面向对象的继承式有一定的区别的,但是本质上,他们都是在操作原型对象,通过给原型对象 prototype 添加一些方法和属性,来扩展类的功能
三、装饰器的定义及使用
定义:
- 装饰器的定义,装饰器的本身,他是一个函数,会在运行的时候被调用,被装饰的类或函数或其他,会作为参数传递给装饰器函数,当作形参。
使用
- 定义完成装饰器,通过 @ + 装饰器名称,将其书写在要装饰的类的声明上面,这样的含义就是,当前装饰器下面的类,会一参数的形式,传递给装饰器函数
四、ts中启用装饰器
若要启用实验性的装饰器特性,你必须在命令行或 tsconfig.json 里启用 experimentalDecorators 编译器选项:
命令行:
ts
tsc --target ES5 --experimentalDecorators
tsconfig.json:
ts
{
"compilerOptions": {
"target": "ES5",
"experimentalDecorators": true
}
}
五、详细介绍
1、类装饰器ClassDecorator
ts类型:
ClassDecorator
类装饰器在类声明之前被声明,应用于类构造函数,可以监视、修改、替换类的定义,传入一个参数 装饰器的本身,是一个函数,会在运行的时候被调用,被装饰的类,会作为参数传递给装饰器函数,当作形参
类装饰器接收一个构造函数作为参数,参数的类型是一个函数
ts
// 定义一个名叫Greeter的类装饰器 Greeter接收的参数target就是被装饰的类Greeting
function Greeter(target: Function): void {
target.prototype.greet = function (): void {
console.log("Hello ClassDecorator!");
};
}
@Greeter
class Greeting {
constructor() {
// 内部实现
}
}
let myGreeting = new Greeting();
(myGreeting as any).greet(); // console output: 'Hello Semlinker!';
class装饰器传参
同样是上面的例子,每次执行类实例上的greet()方法都是输出“Hello ClassDecorator!”,要想动态输出其他的,可以通过传参的形式来实现。
ts
// 定义装饰器(接收一个参数,类型是string)
function Greeter(greeting: string) {
// 装饰器函数返回一个函数,接收要被装饰的class作为参数target
return function (target: Function) {
target.prototype.greet = function (): void {
console.log(greeting);// 动态的将床过来的参数打印出来
};
};
}
@Greeter("Hello TS!") // 将要打印的参数以参数的形式传给装饰器
class Greeting {
constructor() {
// 内部实现
}
}
let myGreeting = new Greeting();
(myGreeting as any).greet(); // 输出: 'Hello TS!';
2、属性装饰器PropertyDecorator
ts类型:
PropertyDecorator
属性装饰器顾名思义,用来装饰类的属性。它接收两个参数:
- target: Object - 被装饰的类
- propertyKey: string | symbol - 被装饰类的属性名
ts
// 定义一个属性装饰器
const nameDecorator:PropertyDecorator = (target: Object, propertyKey: string | symbol) => {
// 对被装饰的属性进行劫持
Object.defineProperty(target,propertyKey, {
set (v) {
console.log(v);
}
})
}
// 定义class
class User {
// 对str属性使用定义好的属性装饰器
@nameDecorator
public str: string;
constructor(str: string) {
this.str = str // 给属性赋值时候会触发装饰器内的set方法
}
}
new User('小明') // 小明
3、方法装饰器
方法装饰器顾名思义,用来装饰类的方法。它接收三个参数:
- target: Object - 被装饰的类
- propertyKey: string | symbol - 方法名
- descriptor: TypePropertyDescript - 属性描述符
这里需要注意的事项就是,在装饰器里,使用的技巧就是先将原有的值进行存储一下,在去使用,确保它使用的是类中的方法中的值
ts
/**
* 装饰器函数
* @param target - 被装饰的类
* @param propertyKey - 方法名
* @param descriptor - 属性描述符
*/
function log(target: Object, propertyKey: string, descriptor: PropertyDescriptor) {
let originalMethod = descriptor.value; //被装饰的方法体,先存一份
console.log(originalMethod === target[propertyKey]); // true
descriptor.value = function (...args: any[]) {
console.log("装饰器函数: 调用前 " + propertyKey); // 1
let result = originalMethod.apply(this, args); // 2
console.log("装饰器函数: 调用后 " + propertyKey); // 3
return result; // 4
};
}
class Task {
@log
runTask(arg: any): any {
console.log("runTask函数 调用完成, args: " + arg);
return "finished";
}
}
let task = new Task();
let result = task.runTask("learn ts");
console.log("result: " + result); // result: finished
// 输出结果
/**
装饰器函数: 调用前 runTask
runTask函数 调用完成, args: learn ts
装饰器函数: 调用后 runTask
result: finished
*/
4、参数装饰器
参数装饰器顾名思义,是用来装饰函数参数,它接收三个参数:
- target: Object - 被装饰的类
- propertyKey: string | symbol - 方法名
- parameterIndex: number - 方法中参数的索引值
ts
function Log(target: Function, key: string, parameterIndex: number) {
let functionLogged = key || target.prototype.constructor.name;
// The parameter in position 1 at Greeter has been decorated
console.log(`The parameter in position ${parameterIndex} at ${functionLogged} has been decorated`);
}
class Greeter {
greeting: string;
constructor(a:string,@Log phrase: string) {
this.greeting = phrase;
}
}
5、装饰器工厂
装饰器工厂的作用就是,根据使用装饰器的类,类给装饰器工程传递数据,根据数据返回不同的装饰器,这个函数被称之为 装饰器工厂
ts
const musicDecoratorFactory = (str: string):ClassDecorator => {
switch (str) {
case 'Token':
return (target: Function):any => {
target.prototype.play = function():void {
console.log('播放音乐' + str);
}
};
default:
return (target: Function):any => {
target.prototype.play = function():void {
console.log('播放音乐' + str);
}
};
}
}
@musicDecoratorFactory('Token')
class Music {
public play() {}
}
new Music().play()
6、装饰器的叠加
装饰器不仅仅只能使用一个,可以定义多个装饰器,作用于一个类身上,通过叠加装饰器的方式,给类追加多个方法和属性
ts
const moveDecortor:ClassDecorator = (target:Function):any => {
target.prototype.forName = ():number => {
return 1
}
}
const musicDecortor: ClassDecorator = (target:Function):any => {
target.prototype.block = ():void => {
console.log('播放音乐');
}
}
@moveDecortor
@musicDecortor
class User{
public forName() {}
public block() {}
}
const u = new User()
console.log((u.forName())
u.block()