面试题-TS(八):什么是装饰器(decorators)?如何在 TypeScript 中使用它们?
在TypeScript中,装饰器(Decorators
)是一种用于增强代码功能的特殊类型声明。装饰器提供了一种在类、方法、属性等代码元素上注释或修改的方式,使得我们可以通过装饰器来扩展、修改或监视代码的行为。通过使用装饰器,我们可以在不修改原始代码的情况下,给代码添加新的功能,提高代码的可维护性和灵活性。
一、什么是装饰器?
装饰器是一种特殊类型的声明,它以@
符号为前缀,后跟一个表达式,通常是一个函数。装饰器可以附着在类、方法、属性等代码元素上,并在运行时对这些元素进行注释或修改。
在TypeScript中,装饰器的使用主要通过以下两种方式:
- 类装饰器:装饰类的声明。
- 方法装饰器、属性装饰器、参数装饰器:装饰类中的方法、属性和参数。
二、如何定义装饰器?
装饰器本质上是一个函数,它接收三个参数:
- 对于类装饰器,它的参数是类的构造函数。
- 对于方法装饰器,它的参数是类的原型对象、方法名和方法的属性描述符。
- 对于属性装饰器,它的参数是类的原型对象和属性名。
- 对于参数装饰器,它的参数是类的原型对象、方法名和参数的索引。
装饰器函数可以根据这些参数来获取或修改类、方法、属性或参数的信息,并实现相应的功能。
以下是一个简单的装饰器示例,用于在类上添加一个日志:
function logClass(target: Function) { console.log("Class logged: ", target); } @logClass class MyClass { // ... }
复制
在上面的示例中,我们定义了一个装饰器函数logClass
,它接收类的构造函数target
作为参数,并在控制台输出类的构造函数。然后我们使用装饰器@logClass
来装饰MyClass
类。
三、如何在 TypeScript 中使用装饰器?
要在TypeScript中使用装饰器,首先需要在tsconfig.json
中启用experimentalDecorators
选项:
{ "compilerOptions": { "experimentalDecorators": true } }
复制
然后,我们就可以在类、方法、属性和参数上使用装饰器了。
类装饰器
类装饰器是应用于类声明的装饰器。它会在类声明时调用,并接收类的构造函数作为参数。类装饰器通常用于修改或扩展类的行为。
以下是一个简单的类装饰器示例,用于添加一个静态属性:
function addStaticProperty(target: Function) { target.staticProperty = "This is a static property."; } @addStaticProperty class MyClass { // ... } console.log(MyClass.staticProperty); // 输出:This is a static property.
复制
在上面的示例中,我们定义了一个类装饰器addStaticProperty
,它在类声明时添加了一个静态属性staticProperty
。然后我们使用装饰器@addStaticProperty
来装饰MyClass
类,并在控制台输出静态属性的值。
方法装饰器
方法装饰器是应用于类方法的装饰器。它会在方法声明时调用,并接收类的原型对象、方法名和方法的属性描述符作为参数。方法装饰器通常用于修改或监视方法的行为。
以下是一个简单的方法装饰器示例,用于添加一个日志:
function logMethod(target: any, methodName: string, descriptor: PropertyDescriptor) { const originalMethod = descriptor.value; descriptor.value = function (...args: any[]) { console.log("Method logged: ", methodName); return originalMethod.apply(this, args); }; } class MyClass { @logMethod greet(name: string) { return `Hello, ${name}!`; } } const myClass = new MyClass(); myClass.greet("John"); // 输出:Method logged: greet // 输出:Hello, John!
复制
在上面的示例中,我们定义了一个方法装饰器logMethod
,它在方法声明时添加了一个日志功能。然后我们使用装饰器@logMethod
来装饰MyClass
类的greet
方法。
属性装饰器
属性装饰器是应用于类属性的装饰器。它会在属性声明时调用,并接收类的原型对象和属性名作为参数。属性装饰器通常用于修改或监视属性的行为。
以下是一个简单的属性装饰器示例,用于添加一个日志:
function logProperty(target: any, propertyName: string) { let value = target[propertyName]; const getter = function () { console.log("Property logged: ", propertyName); return value; }; const setter = function (newVal: any) { console.log("Property set: ", propertyName); value = newVal; }; Object.defineProperty(target, propertyName, { get: getter, set: setter, }); } class MyClass { @logProperty message: string = "Hello"; } const myClass = new MyClass(); console.log(myClass.message); // 输出:Property logged: message // 输出:Hello myClass.message = "World"; // 输出:Property set: message console.log(myClass.message); // 输出:Property logged: message // 输出:World
复制
在上面的示例中,我们定义了一个属性装饰器logProperty
,它在属性声明时添加了一个日志功能。然后我们使用装饰器@logProperty
来装饰MyClass
类的message
属性。
参数装饰器
参数装饰器是应用于类方法参数的装饰器。它会在方法参数声明时调用,并接收类的原型对象、方法名和参数的索引作为参数。参数装饰器通常用于修改或监视方法参数的行为。
以下是一个简单的参数装饰器示例,用于添加一个日志:
function logParameter(target: any, methodName: string, parameterIndex: number) { console.log("Parameter logged: ", methodName, parameterIndex); } class MyClass { greet(@logParameter name: string) { return `Hello, ${name}!`; } } const myClass = new MyClass(); myClass.greet("John"); // 输出:Parameter logged: greet 0 // 输出:Hello, John!
复制
在上面的示例中,我们定义了一个参数装饰器logParameter
,它在方法参数声明时添加了一个日志功能。然后我们使用装饰器@logParameter
来装饰MyClass
类的greet
方法的name
参数。
四、装饰器组合
在TypeScript中,我们可以将多个装饰器组合在一起使用。装饰器组合的顺序是从上到下执行的,即从外到内。
以下是一个装饰器组合的示例:
function logClass(target: Function) { console.log("Class logged: ", target); } function addStaticProperty(target: Function) { target.staticProperty = "This is a static property."; } @logClass @addStaticProperty class MyClass { // ... } console.log(MyClass.staticProperty); // 输出:This is a static property.
复制
在上面的示例中,我们先使用装饰器@addStaticProperty
来装饰MyClass
类,然后再使用装饰器@logClass
来装饰它。由于装饰器组合的顺序是从外到内执行的,所以先执行的是@addStaticProperty
装饰器,再执行的是@logClass
装饰器。
五、自定义装饰器
除了使用已有的装饰器,我们还可以自定义装饰器来实现特定的功能。
以下是一个简单的自定义装饰器示例,用于计算方法执行的时间:
function logExecutionTime(target: any, methodName: string, descriptor: PropertyDescriptor) { const originalMethod = descriptor.value; descriptor.value = function (...args: any[]) { console.time(methodName); const result = originalMethod.apply(this, args); console.timeEnd(methodName); return result; }; } class MyClass { @logExecutionTime longRunningTask() { // 模拟耗时任务 for (let i = 0; i < 1000000000; i++) {} } } const myClass = new MyClass(); myClass.longRunningTask(); // 输出:longRunningTask: 2804.869ms
复制
在上面的示例中,我们定义了一个自定义装饰器logExecutionTime
,它在方法执行前后添加了计时功能。然后我们使用装饰器@logExecutionTime
来装饰MyClass
类的longRunningTask
方法。
六、装饰器工厂
装饰器工厂是一个返回装饰器的函数。它可以接收参数,并返回一个装饰器函数。
以下是一个简单的装饰器工厂示例,用于添加一个指定的前缀:
function addPrefix(prefix: string) { return function (target: any, propertyName: string) { const value = target[propertyName]; Object.defineProperty(target, propertyName, { get: function () { return prefix + value; }, set: function (newVal: any) { value = newVal; }, }); }; } class MyClass { @addPrefix("Hello, ") message: string = "World"; } const myClass = new MyClass(); console.log(myClass.message); // 输出:Hello, World
复制
在上面的示例中,我们定义了一个装饰器工厂addPrefix
,它返回一个装饰器函数,用于在属性的值前面添加指定的前缀。然后我们使用装饰器@addPrefix("Hello, ")
来装饰MyClass
类的message
属性。
七、装饰器的应用场景
装饰器在代码中有许多应用场景。以下是一些常见的用例:
-
日志记录:在方法或类上添加日志功能,用于记录方法的执行过程和结果。
-
性能监控:在方法或类上添加性能监控功能,用于计算方法的执行时间。
-
权限验证:在方法或类上添加权限验证功能,用于检查用户是否有权执行某个操作。
-
数据验证:在方法或类上添加数据验证功能,用于检查输入数据是否合法。
-
缓存处理:在方法或类上添加缓存处理功能,用于缓存方法的结果。
总结
在TypeScript中,装饰器是一种用于增强代码功能的特殊类型声明。装饰器提供了一种在类、方法、属性等代码元素上注释或修改的方式,使得我们可以通过装饰器来扩展、修改或监视代码的行为。通过使用装饰器,我们可以在不修改原始代码的情况下,给代码添加新的功能,提高代码的可维护性和灵活性。装饰器有类装饰器、方法装饰器、属性装饰器和参数装饰器等几种类型,每种类型有不同的使用场景和应用方式。让我们充分利用装饰器的优势,提高我们的代码功能和可读性,构建出更健壮和可维护的应用程序。