Skip to content

装饰器

装饰器是一种特殊的函数,它可以用来修改类的行为。装饰器的语法是 @expression,其中 expression 是一个函数,它会被应用到被装饰的函数上。

类装饰器

typescript
// 类装饰器示例
function logClass(constructor: Function) {
    // 输出类的构造信息
    console.log(`Constructing a new ${constructor.name}`);
}

@logClass // 应用类装饰器
class MyClass {
    constructor() {
        console.log("Hello, world!"); // 构造函数中的逻辑
    }
}

// 输出:
// Constructing a new MyClass
// Hello, world!

方法装饰器

typescript
// 方法装饰器示例
function logMethod(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
    const originalMethod = descriptor.value; // 保存原始方法
    descriptor.value = function (...args: any[]) {
        // 输出方法调用信息
        console.log(`Calling ${propertyKey} with`, args);
        return originalMethod.apply(this, args); // 调用原始方法
    };
}

class MyClass {
    @logMethod // 应用方法装饰器
    method(arg: string) {
        console.log(`Hello, ${arg}!`); // 原始方法逻辑
    }
}

const myClass = new MyClass();
myClass.method("world");

// 输出:
// Calling method with ["world"]
// Hello, world!

访问器装饰器

typescript
// 访问器装饰器示例
function readonly(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
    descriptor.writable = false; // 设置属性为只读
}

class MyClass {
    @readonly // 应用访问器装饰器
    prop: string = "Hello, world!"; // 定义只读属性
}

const myClass = new MyClass();
myClass.prop = "Goodbye, world!"; // 尝试修改只读属性会抛出错误

// 输出:
// TypeError: Cannot assign to read only property 'prop' of object '#<MyClass>'

属性装饰器

typescript
// 属性装饰器示例
function enumerable(value: boolean) {
    return function (target: any, propertyKey: string) {
        const descriptor = Object.getOwnPropertyDescriptor(target, propertyKey) || {};
        descriptor.enumerable = value; // 设置属性是否可枚举
        Object.defineProperty(target, propertyKey, descriptor); // 更新属性描述符
    };
}

class MyClass {
    @enumerable(false) // 应用属性装饰器
    prop: string = "Hello, world!";
}

const myClass = new MyClass();
for (const key in myClass) {
    console.log(key); // 只有可枚举的属性会被输出
}

// 输出:
// prop

参数装饰器

typescript
// 参数装饰器示例
function minArgs(count: number) {
    return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
        const originalMethod = descriptor.value;
        descriptor.value = function (...args: any[]) {
            // 检查参数数量
            if (args.length < count) {
                throw new Error(`Expected at least ${count} arguments.`);
            }
            return originalMethod.apply(this, args);
        };
    };
}

class MyClass {
    @minArgs(2) // 应用参数装饰器
    method(arg1: string, arg2: string) {
        console.log(`Hello, ${arg1} and ${arg2}!`); // 方法逻辑
    }
}

const myClass = new MyClass();
myClass.method("world"); // 参数不足会抛出错误

// 输出:
// Error: Expected at least 2 arguments.

装饰器工厂

typescript
// 装饰器工厂示例
function log(prefix: string) {
    return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
        const originalMethod = descriptor.value;
        descriptor.value = function (...args: any[]) {
            // 输出带前缀的日志信息
            console.log(`${prefix} ${propertyKey} with`, args);
            return originalMethod.apply(this, args);
        };
    };
}

class MyClass {
    @log("Before") // 应用装饰器工厂
    method(arg: string) {
        console.log(`Hello, ${arg}!`); // 方法逻辑
    }

    @log("After") // 应用装饰器工厂
    method2(arg: string) {
        console.log(`Goodbye, ${arg}!`); // 方法逻辑
    }
}

const myClass = new MyClass();
myClass.method("world");
myClass.method2("world");

// 输出:
// Before method with ["world"]
// Hello, world!
// After method2 with ["world"]
// Goodbye, world!

装饰器组合

typescript
// 多个装饰器组合示例
function readonly(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
    descriptor.writable = false; // 设置属性为只读
}

function enumerable(value: boolean) {
    return function (target: any, propertyKey: string) {
        const descriptor = Object.getOwnPropertyDescriptor(target, propertyKey) || {};
        descriptor.enumerable = value; // 设置属性是否可枚举
        Object.defineProperty(target, propertyKey, descriptor); // 更新属性描述符
    };
}

function minArgs(count: number) {
    return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
        const originalMethod = descriptor.value;
        descriptor.value = function (...args: any[]) {
            // 检查参数数量
            if (args.length < count) {
                throw new Error(`Expected at least ${count} arguments.`);
            }
            return originalMethod.apply(this, args);
        };
    };
}

function log(prefix: string) {
    return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
        const originalMethod = descriptor.value;
        descriptor.value = function (...args: any[]) {
            // 输出带前缀的日志信息
            console.log(`${prefix} ${propertyKey} with`, args);
            return originalMethod.apply(this, args);
        };
    };
}

class MyClass {
    @log("Before") // 日志装饰器
    @minArgs(2)    // 参数检查装饰器
    @enumerable(false) // 属性可枚举性装饰器
    method(arg1: string, arg2: string) {
        console.log(`Hello, ${arg1} and ${arg2}!`); // 方法逻辑
    }

    @log("After") // 日志装饰器
    method2(arg: string) {
        console.log(`Goodbye, ${arg}!`); // 方法逻辑
    }
}

const myClass = new MyClass();
myClass.method("world", "universe");
myClass.method2("world");

// 输出:
// Before method with ["world", "universe"]
// Hello, world and universe!
// After method2 with ["world"]
// Goodbye, world!