一、啥是 TypeScript 装饰器

咱先聊聊 TypeScript 装饰器是个啥。简单来说,装饰器就像是给代码穿上一件“魔法外衣”,能在不改变原有代码结构的基础上,给代码添加额外的功能。它是一种特殊的声明,能被附加到类声明、方法、属性或者参数上,来修改类的行为。

比如说,我们有一个简单的类:

// TypeScript 示例
class Person {
    constructor(public name: string) {}
    sayHello() {
        console.log(`Hello, my name is ${this.name}`);
    }
}

现在,我们可以用装饰器来给这个类添加一些额外的功能。下面是一个简单的装饰器示例:

// TypeScript 示例
function log(target: any) {
    // 保存原始的构造函数
    const original = target;
    // 新的构造函数
    const f: any = function (...args: any[]) {
        console.log(`Creating new instance of ${original.name}`);
        // 创建实例
        return new original(...args);
    };
    // 复制原型,确保实例仍具有原始类的方法
    f.prototype = original.prototype;
    return f;
}

@log
class Person {
    constructor(public name: string) {}
    sayHello() {
        console.log(`Hello, my name is ${this.name}`);
    }
}

const person = new Person('John');
person.sayHello();

在这个示例中,log 就是一个装饰器。当我们创建 Person 类的实例时,装饰器会在控制台输出一条日志信息。

二、装饰器的工作原理

装饰器本质上就是一个函数,它接收目标对象作为参数,然后返回一个新的对象。这个新对象可以是修改后的类、方法或者属性等。

1. 类装饰器

类装饰器是应用在类定义上的装饰器。它接收构造函数作为参数,并且可以返回一个新的构造函数来替换原来的构造函数。

// TypeScript 示例
function classDecorator(constructor: Function) {
    console.log('Class decorator called');
    // 可以在这里修改构造函数的行为
    constructor.prototype.newMethod = function () {
        console.log('New method added by decorator');
    };
}

@classDecorator
class MyClass {
    constructor() {
        console.log('MyClass constructor called');
    }
}

const myObj = new MyClass();
// 调用新方法
(myObj as any).newMethod();

在这个示例中,classDecorator 是一个类装饰器,它给 MyClass 类添加了一个新的方法 newMethod

2. 方法装饰器

方法装饰器应用在类的方法上。它接收三个参数:目标对象、方法名和方法的描述符。

// TypeScript 示例
function methodDecorator(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
    console.log('Method decorator called');
    const originalMethod = descriptor.value;
    descriptor.value = function (...args: any[]) {
        console.log(`Before method ${propertyKey} is called`);
        const result = originalMethod.apply(this, args);
        console.log(`After method ${propertyKey} is called`);
        return result;
    };
    return descriptor;
}

class MyClass {
    @methodDecorator
    sayHello() {
        console.log('Hello!');
    }
}

const myObj = new MyClass();
myObj.sayHello();

在这个示例中,methodDecorator 是一个方法装饰器,它在调用 sayHello 方法前后分别输出日志信息。

3. 属性装饰器

属性装饰器应用在类的属性上。它接收两个参数:目标对象和属性名。

// TypeScript 示例
function propertyDecorator(target: any, propertyKey: string) {
    console.log('Property decorator called');
    let value = target[propertyKey];
    const getter = () => {
        console.log(`Getting value of ${propertyKey}`);
        return value;
    };
    const setter = (newValue: any) => {
        console.log(`Setting value of ${propertyKey} to ${newValue}`);
        value = newValue;
    };
    Object.defineProperty(target, propertyKey, {
        get: getter,
        set: setter,
        enumerable: true,
        configurable: true
    });
}

class MyClass {
    @propertyDecorator
    public myProperty: string = 'Initial value';
}

const myObj = new MyClass();
console.log(myObj.myProperty);
myObj.myProperty = 'New value';

在这个示例中,propertyDecorator 是一个属性装饰器,它在获取和设置 myProperty 属性时分别输出日志信息。

三、应用场景

1. 日志记录

就像上面的示例一样,我们可以用装饰器来记录类的创建、方法的调用等信息,方便调试和监控。

// TypeScript 示例
function logMethod(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
    const originalMethod = descriptor.value;
    descriptor.value = function (...args: any[]) {
        console.log(`Method ${propertyKey} called with arguments: ${JSON.stringify(args)}`);
        const result = originalMethod.apply(this, args);
        console.log(`Method ${propertyKey} returned: ${JSON.stringify(result)}`);
        return result;
    };
    return descriptor;
}

class Calculator {
    @logMethod
    add(a: number, b: number) {
        return a + b;
    }
}

const calc = new Calculator();
const result = calc.add(2, 3);

2. 权限验证

我们可以用装饰器来验证用户是否有访问某个方法的权限。

// TypeScript 示例
function checkPermission(role: string) {
    return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
        const originalMethod = descriptor.value;
        descriptor.value = function (...args: any[]) {
            // 模拟权限验证
            if (role === 'admin') {
                return originalMethod.apply(this, args);
            } else {
                console.log('You do not have permission to access this method');
                return null;
            }
        };
        return descriptor;
    };
}

class AdminService {
    @checkPermission('admin')
    deleteUser(id: number) {
        console.log(`Deleting user with id ${id}`);
    }
}

const adminService = new AdminService();
adminService.deleteUser(1);

3. 性能监控

我们可以用装饰器来监控方法的执行时间。

// TypeScript 示例
function performanceMonitor(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
    const originalMethod = descriptor.value;
    descriptor.value = async function (...args: any[]) {
        const start = performance.now();
        const result = await originalMethod.apply(this, args);
        const end = performance.now();
        console.log(`Method ${propertyKey} took ${end - start} ms to execute`);
        return result;
    };
    return descriptor;
}

class DataService {
    @performanceMonitor
    async fetchData() {
        // 模拟异步操作
        await new Promise(resolve => setTimeout(resolve, 1000));
        return { data: 'Some data' };
    }
}

const dataService = new DataService();
dataService.fetchData();

四、技术优缺点

优点

  • 代码复用:装饰器可以将一些通用的功能封装起来,在多个类或者方法中复用,减少代码重复。
  • 代码可读性:使用装饰器可以让代码更加简洁,提高代码的可读性。比如在权限验证的示例中,我们只需要在方法上添加一个装饰器,就可以实现权限验证的功能,而不需要在每个方法中都写权限验证的代码。
  • 扩展性:装饰器可以很方便地扩展类或者方法的功能,而不需要修改原有的代码。比如我们可以随时添加新的日志记录装饰器或者性能监控装饰器。

缺点

  • 调试困难:由于装饰器会修改原有的代码,当出现问题时,调试可能会比较困难。比如在使用装饰器修改方法的行为后,可能很难找到问题出在装饰器代码还是原有的方法代码中。
  • 学习成本:对于初学者来说,理解装饰器的工作原理和使用方法可能需要一定的时间和精力。

五、注意事项

  • 装饰器的执行顺序:多个装饰器应用在同一个目标上时,它们的执行顺序是从下到上。比如:
// TypeScript 示例
function decorator1(target: any) {
    console.log('Decorator 1 called');
    return target;
}

function decorator2(target: any) {
    console.log('Decorator 2 called');
    return target;
}

@decorator1
@decorator2
class MyClass {}

在这个示例中,decorator2 会先执行,然后 decorator1 再执行。

  • 装饰器的兼容性:装饰器是 TypeScript 的一个特性,在一些旧版本的 JavaScript 环境中可能不支持。所以在使用装饰器时,需要确保运行环境支持。

六、文章总结

TypeScript 装饰器是一种非常实用的元编程技巧,它可以让我们在不改变原有代码结构的基础上,给代码添加额外的功能。通过类装饰器、方法装饰器和属性装饰器,我们可以实现日志记录、权限验证、性能监控等多种功能。虽然装饰器有一些缺点,比如调试困难和学习成本较高,但它的优点远远大于缺点。在实际开发中,合理使用装饰器可以提高代码的复用性、可读性和扩展性。