一、简介
- 装饰器本质上是一种特殊的函数,使用
@expression形式,可以对类、属性、方法和参数进行扩展,同时能让代码更加简洁。 - 装饰器有5种:类装饰器、属性装饰器、方法装饰器、参数装饰器、访问器装饰器。
二、类装饰器
-
基本语法
类装饰器是应用在类声明上的函数,可以为类提供额外的功能,或者添加额外的逻辑。
function Demo(target: Function) {
console.log(`Class name: ${target}`);
}
@Demo
class Person {
constructor(public name: string, public age: number) {}
}
2. ### 应用举例
function CustomToString(target: Function) {
target.prototype.toString = function() {
return JSON.stringify(this);
}
}
@CustomToString
class Person {
constructor(public name: string, public age: number) {}
}
const p = new Person("John",12);
console.log(p.toString()); // Output: {"name":"John","age":12}
上面的例子中,我们为类Person定义了一个CustomToString装饰器,其功能是给Person的实例添加toString方法,并让其返回Person的实例的JSON.stringify后的结果。
-
关于返回值
- 有返回值:若装饰器返回一个新的类,那这个新的类将会替换被装饰的类。
function Demo(target: Function) {
return class {
test() {
console.log("welcom!");
}
}
}
@Demo
class Person {
test() {
console.log("Hello, World!");
}
}
const p = new Person();
p.test(); // welcom
4. ### 关于构造类型
在typescript中,Function所表示的范围十分广泛,包括:普通函数、箭头函数、方法等等。并非Function类型的方法都可以被new关键字实例化,例如箭头函数不能被实例化。那么typescript中如何声明构造器类型呢,以下有两种方法
- 仅声明构造器类型
type Constructor = new (...args: any[]) => {}
function test(fn: Constructor) {}
class Person {
}
test(Person)
2. 声明构造器类型+静态属性
type Constructor = {
new (...args: any[]): {}
wife: string
}
function test(fn: Constructor) {}
class Person {
static wife: string
}
test(Person)
5. ### 替换被装饰的类
设计一个logTime的装饰器,可以给实例添加一个属性,用于记录对象的创建时间,并且提供一个方法,返回对象的创建时间
type Constructor = new (...args: any[]) =>{}
interface Person {
getCreateTime(): void
}
function LogTime<T extends Constructor>(target: T) {
return class extends target {
createTime: Date
constructor(...args: any[]) {
super(...args);
this.createTime = new Date();
}
getCreateTime() {
return `创建该对象的时间为${this.createTime}`;
}
}
}
@LogTime
class Person {
constructor(public name: string, public age: number) {}
speak(): void {
console.log(`${this.name} is speaking...`);
}
}
const p = new Person("John", 20);
console.log(p.getCreateTime()); // 创建该对象的时间为Thu Feb 11 2021 14:48:47 GMT+0800 (GMT+08:00)
三、装饰器工厂
装饰器工厂是一个返回装饰器函数的函数,可以为装饰器传递参数,可以更灵活的控制装饰器的行为。
下面的例子定义了一个可以接收参数的装饰器工厂函数,在target的原型上添加了一个introduce方法,打印n词呢日志信息。
interface Person {
introduce(): void
}
function logInfo(n: number) {
return function(target: Function) {
target.prototype.introduce = function() {
for(let i = 0; i < n; i++) {
console.log(`Hello, I am ${this.name}`);
}
}
}
}
@logInfo(3)
class Person {
constructor(public name: string, public age: number) {}
speak() {
console.log(`hello`);
}
}
const p = new Person("John", 25);
p.introduce();
四、装饰器组合
装饰器组合可以组合使用,执行顺序是【由上到下】依次执行所有的装饰器工厂,获取到装饰器;在【由下到上】执行所有的装饰器
-
执行顺序
function test1(target: Function) {
console.log('test1')
}
function test2() {
console.log('test2 工厂')
return function(target: Function) {
console.log('test2')
}
}
function test3() {
console.log('test3 工厂')
return function(target: Function) {
console.log('test3')
}
}
function test4(target: Function) {
console.log('test4')
}
@test1
@test2()
@test3()
@test4
class Test {}
// 控制台打印的顺序是:
// test2 工厂
// test3 工厂
// test4
// test3
// test2
// test1
2. 应用
interface Person {
introduce(): void
getCreateTime(): void
}
function CustomToString(target: Function) {
target.prototype.toString = function() {
return JSON.stringify(this);
}
}
type Constructor = new (...args: any[]) =>{}
function LogTime<T extends Constructor>(target: T) {
return class extends target {
createTime: Date
constructor(...args: any[]) {
super(...args);
this.createTime = new Date();
}
getCreateTime() {
return `创建该对象的时间为${this.createTime}`;
}
}
}
function logInfo(n: number) {
return function(target: Function) {
target.prototype.introduce = function() {
for(let i = 0; i < n; i++) {
console.log(`Hello, I am ${this.name}`);
}
}
}
}
@CustomToString
@logInfo(3)
@LogTime
class Person {
constructor(public name: string, public age: number) {}
}
const p = new Person("John",12);
console.log(p.toString()); // Output: {"name":"John","age":12}
console.log(p.getCreateTime()); // Output: 创建该对象的时间为Thu Dec 10 2021 19:42:57 GMT+0800 (China Standard Time)
p.introduce();
五、属性装饰器
属性装饰器(Property Decorator)是一种特殊的声明,它能够被附加到类的属性声明上,用于在运行时或编译时提供关于该属性的额外信息或修改其行为。属性装饰器在TypeScript中是通过@expression的语法形式来使用的,其中expression必须计算为一个函数,这个函数会在运行时被调用,并传入关于被装饰属性的元数据信息。
-
基本语法
/**
* 参数说明:
* - target: 对于静态属性来说是类,对于实例属性来说是类的原型对象
* - propertyKey: 属性名
*
*/
function logPropertyName(target: object, propertyKey: string) {
console.log(target)
console.log(`Property ${propertyKey} has been decorated.`);
}
class MyClass {
@logPropertyName myProperty: string;
@logPropertyName static myStatic: string
constructor(myProperty: string) {
this.myProperty = myProperty
}
}
2. #### 关于属性遮蔽
如下代码:当构造器上的this.name = name试图在实例上赋值时,实际上是调用了原型上name属性的set方法。
class Person {
name: string;
age: number;
constructor(name: string, age: number) {
this.name = name;
this.age = age;
}
}
let value = 'John';
Object.defineProperty(Person.prototype, "name", {
get: function () {
return value;
},
set(v) {
value = v;
},
})
const person1 = new Person("John Doe", 25);
console.log(person1.name); // John Doe
console.log(Person.prototype.name) // John Doe
-
应用举例
以下代码实现了一个State装饰器,其功能在更新属性时监听到了属性值的变化。
function State(target: object, propertyKey: string) {
let key = `__${propertyKey}`
Object.defineProperty(target, propertyKey, {
get: function() {
return this[key];
},
set: function(value) {
console.log(`${propertyKey} 更新了`)
this[key] = value;
}
});
}
class Person {
@State name: string;
@State age: number;
constructor(name: string, age: number) {
this.name = name;
this.age = age;
}
}
const person = new Person('John Doe', 30);
person.name = 'John';
person.age = 25;
六、方法装饰器
方法装饰器(Method Decorator)是一种允许你在类的方法上添加额外逻辑或行为的装饰器。方法装饰器接收三个参数:target(类的原型)、propertyName(方法名),以及descriptor(方法的描述符对象,它包含了方法的一些元数据,比如是否可枚举、可配置等,以及方法的实现)。
通过方法装饰器,你可以在方法被调用之前或之后执行额外的代码,比如进行日志记录、性能测量、权限检查、参数验证等。
-
基本语法
/**
*
* @param target 对于静态方法来说是类,对于实例方法来说是原型对象
* @param propertyKey 被装饰的方法名称
* @param descriptor 被装饰的方法的描述对象
*/
function Demo(target: object, propertyKey: string, descriptor: PropertyDescriptor): void{
console.log('target:' ,target)
console.log('propertyKey:', propertyKey)
console.log('descriptor:', descriptor)
}
class Person {
constructor(public name: string, public age: number) {}
@Demo
speak() {
console.log(`Hello, my name is ${this.name} and I am ${this.age} years old.`);
}
@Demo
static test() {
console.log('This is a static method.');
}
}
上面的代码运行结果如下:
-
应用举例
以下代码实现了一个方法装饰器Logger,功能是在被装饰方法执行前打印一段日志,执行后打印一段日志。
function Logger(target: object, prepertyKey: string, descriptor: PropertyDescriptor) {
// 获取原始方法
const originnal = descriptor.value;
// 重写原始方法
descriptor.value = function(...args: any[]) {
console.log(`Before invoking ${prepertyKey} method...`);
const result = originnal.call(this, ...args);
console.log(`After invoking ${prepertyKey} method, result: ${result}`);
return result;
}
}
class Person {
constructor(public name: string, public age: number) {}
@Logger
speak() {
console.log(`${this.name} is speaking!`);
}
@Logger
static isAduit(age: number): boolean {
return age >= 18;
}
}
const p = new Person('John', 20);
p.speak();
Person.isAduit(20);
控制台打印结果如下:
七、访问器装饰器
访问器装饰器(Accessor Decorator)是一种特殊的装饰器,它允许你在类的属性的getter或setter方法上添加额外的逻辑或行为。这种装饰器接收三个参数:target(类的原型)、propertyName(属性名)以及一个描述该属性是getter还是setter的descriptor对象。
访问器装饰器可以让你在访问或修改属性时执行额外的代码,比如日志记录、数据验证或转换等。
-
基本语法
function Demo(target: object, propertyKey: string, descriptor: PropertyDescriptor) {
console.log(target)
console.log(propertyKey)
console.log(descriptor)
}
class Person {
constructor(public name: string, public age: number) {}
@Demo
get address() {
return "东升科技园"
}
@Demo
get country() {
return "中国"
}
}
2. 应用举例
function RangeValidate(min: number, max: number) {
return function(target: object, property: string, descriptor: PropertyDescriptor) { // 保存原始setter
const originalSetter = descriptor.set
// 重写原始setter
descriptor.set = function(value: number) {
if (value >= min && value <= max) {
originalSetter && originalSetter.call(this, value)
} else {
throw new Error(`Temperature must be between ${min} and ${max} degrees Celsius`)
}
}
}
}
class Weather {
private _temp: number
constructor(temp: number) {
this._temp = temp
}
get temp() {
return this._temp
}
@RangeValidate(-40, 40)
set temp(value: number) {
this._temp = value
}
}
const w = new Weather(20)
w.temp = 30
w.temp = 100
八、参数装饰器
参数装饰器是一种特殊的装饰器,用于在类的方法或构造函数的参数被定义时进行额外的操作。参数装饰器接收三个参数:target(类的原型)、methodName(方法的名字)和paramIndex(参数的索引)。通过参数装饰器,你可以在参数被使用之前对其进行验证、转换或其他处理
-
基本语法
/**
*
* @param target 如果修饰的是【实例方法】的参数,值是类的【原型对象】;如果修饰的是【静态方法】的参数,值是【类】
* @param propertyKey 参数所在的方法名称
* @param parameterIndex 参数所在函数参数列表中的索引
*/
function Demo(target: object, propertyKey: string, parameterIndex: number) {
}
class Person{
constructor(public name:string){}
speak(@Demo message:string) {
console.log(`${this.name} says: ${message}`);
}
}
-
应用举例
// 参数装饰器定义
function logParameter(target: any, methodName: string, paramIndex: number) {
// 创建一个新的函数来替换原有的参数
const originalMethod = target[methodName];
target[methodName] = function (...args: any[]) {
// 在这里可以对参数进行任何处理
console.log(`Argument at index ${paramIndex} is: ${args[paramIndex]}`);
// 调用原始方法
return originalMethod.apply(this, args);
};
}
// 使用参数装饰器的类
class Example {
// 在参数上应用装饰器
greet(@logParameter message: string): void {
console.log(`Hello, ${message}!`);
}
}
// 创建类的实例并调用方法
const example = new Example();
example.greet('world');
在这个例子中,logParameter是一个参数装饰器,它被应用于Example类的greet方法的message参数上。当greet方法被调用时,装饰器会首先执行,打印出参数的日志,然后才会执行原始的方法体。
输出将会是:
Argument at index 0 is: world
Hello, world!