TS装饰器

在 TypeScript 中,装饰器(Decorators)是一种特殊类型的声明,它可以被附加到类声明、方法、属性或参数上,用来修改类的行为。装饰器提供了一种方式来在不修改原有代码结构的基础上,对类及其成员进行扩展和增强。下面将从基本概念、装饰器类型、装饰器工厂、元数据等方面详细介绍 TypeScript 装饰器。

基本概念

装饰器本质上是一个函数,它接收不同数量的参数,具体取决于它所装饰的目标类型。装饰器可以修改目标的行为,例如添加额外的属性、修改方法的实现等。在 TypeScript 中使用装饰器需要在 tsconfig.json 中开启 experimentalDecorators 选项:

json 复制代码
{
    "compilerOptions": {
        "experimentalDecorators": true
    }
}

装饰器类型

1. 类装饰器

类装饰器应用于类的构造函数,可用于修改类的构造函数或类的原型。类装饰器接收一个参数,即类的构造函数。

typescript 复制代码
function logClass(constructor: Function) {
    console.log(`Class ${constructor.name} is created.`);
}

@logClass
class MyClass {
    constructor() {}
}

在上述代码中,logClass 是一个类装饰器,它会在 MyClass 类创建时输出一条日志。

2. 方法装饰器

方法装饰器应用于类的方法,接收三个参数:目标对象、方法名和属性描述符。可以用于修改方法的行为,例如添加日志、验证输入等。

typescript 复制代码
function logMethod(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
    const originalMethod = descriptor.value;
    descriptor.value = function (...args: any[]) {
        console.log(`Calling method ${propertyKey} with arguments: ${JSON.stringify(args)}`);
        const result = originalMethod.apply(this, args);
        console.log(`Method ${propertyKey} returned: ${result}`);
        return result;
    };
    return descriptor;
}

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

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

在上述代码中,logMethod 是一个方法装饰器,它会在调用 add 方法前后输出日志。

3. 属性装饰器

属性装饰器应用于类的属性,接收两个参数:目标对象和属性名。属性装饰器主要用于记录属性的元数据或修改属性的行为。

typescript 复制代码
function readonly(target: any, propertyKey: string) {
    const descriptor: PropertyDescriptor = {
        writable: false
    };
    Object.defineProperty(target, propertyKey, descriptor);
}

class Person {
    @readonly
    name: string = 'John';
}

const person = new Person();
// 下面这行代码会报错,因为 name 属性是只读的
// person.name = 'Jane'; 

在上述代码中,readonly 是一个属性装饰器,它将 name 属性设置为只读。

4. 参数装饰器

参数装饰器应用于类方法的参数,接收三个参数:目标对象、方法名和参数索引。参数装饰器主要用于记录参数的元数据。

typescript 复制代码
function logParameter(target: any, propertyKey: string, parameterIndex: number) {
    console.log(`Parameter at index ${parameterIndex} of method ${propertyKey} is logged.`);
}

class Example {
    method(@logParameter param: string) {}
}

const example = new Example();
example.method('test');

在上述代码中,logParameter 是一个参数装饰器,它会在调用 method 方法时输出参数的索引信息。

装饰器工厂

装饰器工厂是一个函数,它返回一个装饰器。通过装饰器工厂,可以在使用装饰器时传递参数。

typescript 复制代码
function logWithMessage(message: string) {
    return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
        const originalMethod = descriptor.value;
        descriptor.value = function (...args: any[]) {
            console.log(`${message} - Calling method ${propertyKey} with arguments: ${JSON.stringify(args)}`);
            const result = originalMethod.apply(this, args);
            console.log(`${message} - Method ${propertyKey} returned: ${result}`);
            return result;
        };
        return descriptor;
    };
}

class AnotherCalculator {
    @logWithMessage('Custom log message')
    multiply(a: number, b: number) {
        return a * b;
    }
}

const anotherCalc = new AnotherCalculator();
anotherCalc.multiply(2, 3);

在上述代码中,logWithMessage 是一个装饰器工厂,它接收一个参数 message,并返回一个方法装饰器。

装饰器执行顺序

  • 多个装饰器应用于同一个目标时:装饰器的执行顺序是从下往上(从靠近目标的装饰器开始执行),从内往外(如果有装饰器工厂,先执行装饰器工厂返回的装饰器)。
typescript 复制代码
function decorator1() {
    console.log('Decorator 1 factory');
    return function (target: any) {
        console.log('Decorator 1');
    };
}

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

@decorator1()
@decorator2()
class MyClass2 {}

上述代码的输出顺序为:

dart 复制代码
Decorator 2 factory
Decorator 1 factory
Decorator 2
Decorator 1

元数据

在 TypeScript 中,可以使用 reflect-metadata 库来处理装饰器的元数据。元数据可以用于存储和获取与类、方法、属性或参数相关的额外信息。

typescript 复制代码
import 'reflect-metadata';

const METADATA_KEY = 'exampleMetadata';

function addMetadata(value: any) {
    return function (target: any, propertyKey: string) {
        Reflect.defineMetadata(METADATA_KEY, value, target, propertyKey);
    };
}

function getMetadata(target: any, propertyKey: string) {
    return Reflect.getMetadata(METADATA_KEY, target, propertyKey);
}

class MyClass3 {
    @addMetadata('Some metadata')
    myProperty: string;
}

const instance = new MyClass3();
const metadata = getMetadata(instance, 'myProperty');
console.log(metadata); // 输出: Some metadata

在上述代码中,使用 reflect-metadata 库的 Reflect.defineMetadataReflect.getMetadata 方法来定义和获取元数据。

总结

TypeScript 装饰器提供了一种强大的方式来扩展和修改类及其成员的行为。通过不同类型的装饰器和装饰器工厂,可以实现各种功能,如日志记录、权限验证、只读属性等。同时,结合 reflect-metadata 库可以处理装饰器的元数据,进一步增强装饰器的功能。

相关推荐
前端小巷子3 分钟前
Web 实时通信:从短轮询到 WebSocket
前端·javascript·面试
神仙别闹7 分钟前
基于C#+SQL Server实现(Web)学生选课管理系统
前端·数据库·c#
web前端神器13 分钟前
指定阿里镜像原理
前端
枷锁—sha18 分钟前
【DVWA系列】——CSRF——Medium详细教程
android·服务器·前端·web安全·网络安全·csrf
枷锁—sha19 分钟前
跨站请求伪造漏洞(CSRF)详解
运维·服务器·前端·web安全·网络安全·csrf
群联云防护小杜35 分钟前
深度隐匿源IP:高防+群联AI云防护防绕过实战
运维·服务器·前端·网络·人工智能·网络协议·tcp/ip
汉得数字平台1 小时前
【鲲苍提效】全面洞察用户体验,助力打造高性能前端应用
前端·前端监控
花海如潮淹1 小时前
前端性能追踪工具:用户体验的毫秒战争
前端·笔记·ux
_丿丨丨_6 小时前
XSS(跨站脚本攻击)
前端·网络·xss
天天进步20156 小时前
前端安全指南:防御XSS与CSRF攻击
前端·安全·xss