1、装饰器
定义:在不修改原代码的情况下,通过注解方式为类、方法、属性或参数添加额外功能的特性
1.1调用方式
以@expression这种形式,expression求值后必须为一个函数,在被调用时会调用表达式表示的函数
function firstDec (){
console.log('firstDec')
}
function secondDec (){
return function (){
console.log('secondDec')
}
}
class Greeter {
@firstDec
firstGreet(name: string) {
}
@secondDec()
secondGreet(name: string) {
}
}
new Greeter()
//firstDec
//secondDec
1.2装饰器组合
多个装饰器可以同时应用到一个声明上,多装饰器使用遵循以下规则
- 由上至下依次对装饰器表达式求值。
- 求值的结果会被当作函数,由下至上依次调用。
// 书写在同一行上
@f @g x
// 多行使用
@f
@g
x
2、装饰器工厂
装饰器工厂主要的应用场景就是让装饰器接收配置参数,实现动态生成返回。
function color(value: string) { // 这是一个装饰器工厂
return function (target) { // 这是装饰器
// do something with "target" and "value"...
}
}
3、装饰器执行顺序
- 参数装饰器会在方法装饰器之前执行,然后方法装饰器,访问符装饰器,或属性装饰器按书写顺序执行,最后应用到每个实例成员。
- 参数装饰器会在方法装饰器之前执行,然后方法装饰器,访问符装饰器,或属性装饰器按书写顺序执行,最后应用到每个静态成员。
- 参数装饰器应用到构造函数。
- 类装饰器应用到类。
function ParamDecorator(target: any, propertyKey: string | symbol | undefined, parameterIndex: number) {
console.log(`[参数装饰器] 目标: ${target?.constructor?.name || '构造函数'}, 属性: ${String(propertyKey || 'constructor')}, 参数索引: ${parameterIndex}`);
}
// 方法装饰器
function MethodDecorator(target: any, propertyKey: string | symbol, descriptor: PropertyDescriptor) {
console.log(`[方法装饰器] 目标: ${target.constructor.name}, 方法: ${String(propertyKey)}`);
return descriptor;
}
// 访问符装饰器(getter/setter)
function AccessorDecorator(target: any, propertyKey: string | symbol, descriptor: PropertyDescriptor) {
console.log(`[访问符装饰器] 目标: ${target.constructor.name}, 访问符: ${String(propertyKey)}`);
return descriptor;
}
// 属性装饰器
function PropertyDecorator(target: any, propertyKey: string | symbol) {
console.log(`[属性装饰器] 目标: ${target.constructor.name}, 属性: ${String(propertyKey)}`);
}
// 类装饰器
function ClassDecorator(constructor: Function) {
console.log(`[类装饰器] 类名: ${constructor.name}`);
}
@ClassDecorator
class Example {
// 实例方法(带参数装饰器)
@MethodDecorator
instanceMethod(
@ParamDecorator param1: string,
@ParamDecorator param2: number
) {
console.log('执行实例方法');
}
// 实例属性
@PropertyDecorator
instanceProperty: string = '实例属性';
// 实例访问符(getter)
@AccessorDecorator
get instanceAccessor(): string {
return this.instanceProperty;
}
// 实例访问符(setter)
@AccessorDecorator
set instanceAccessor(value: string) {
this.instanceProperty = value;
}
// 静态属性
@PropertyDecorator
static staticProperty: string = '静态属性';
// 静态方法(带参数装饰器)
@MethodDecorator
static staticMethod(
@ParamDecorator param1: string,
@ParamDecorator param2: number
) {
console.log('执行静态方法');
}
// 静态访问符(getter)
@AccessorDecorator
static get staticAccessor(): string {
return Example.staticProperty;
}
// 静态访问符(setter)
@AccessorDecorator
static set staticAccessor(value: string) {
Example.staticProperty = value;
}
// 构造函数(带参数装饰器)
constructor(
@ParamDecorator param1: string,
@ParamDecorator param2: number
) {
console.log('构造函数执行');
}
}
const example = new Example('test', 123);
//[参数装饰器] 目标: Example, 属性: instanceMethod, 参数索引: 1
//[参数装饰器] 目标: Example, 属性: instanceMethod, 参数索引: 0
//[方法装饰器] 目标: Example, 方法: instanceMethod
//[属性装饰器] 目标: Example, 属性: instanceProperty
//[访问符装饰器] 目标: Example, 访问符: instanceAccessor
//[访问符装饰器] 目标: Example, 访问符: instanceAccessor
//[属性装饰器] 目标: Function, 属性: staticProperty
//[参数装饰器] 目标: Function, 属性: staticMethod, 参数索引: 1
//[参数装饰器] 目标: Function, 属性: staticMethod, 参数索引: 0
//[方法装饰器] 目标: Function, 方法: staticMethod
//[访问符装饰器] 目标: Function, 访问符: staticAccessor
//[访问符装饰器] 目标: Function, 访问符: staticAccessor
//[参数装饰器] 目标: Function, 属性: constructor, 参数索引: 1
//[参数装饰器] 目标: Function, 属性: constructor, 参数索引: 0
//[类装饰器] 类名: Example
//构造函数执行
4、装饰器分类
4.1类装饰器
类装饰器在类声明之前被声明(紧靠着类声明)。 类装饰器应用于类构造函数,可以用来监视,修改或替换类定义。
function sealed(constructor: Function) {
Object.seal(constructor);
Object.seal(constructor.prototype);
}
@sealed
class Greeter {
greeting: string;
constructor(message: string) {
this.greeting = message;
}
greet() {
return "Hello, " + this.greeting;
}
}
类装饰器表达式会在运行时当作函数直接调用,类的构造函数作为其唯一的参数。 如果装饰器执行有返回,且是一个函数,则会用这个函数替换构造函数,返回其他类型值会直接报错
function firstDec (){
}
function secondDec (){
return function Test (){
}
}
@firstDec
class Greeter1 {
}
@secondDec
class Greeter2{
}
console.log(new Greeter1())
//Greeter1 {}
console.log(new Greeter2())
//Test {}
4.2方法装饰器
方法装饰器声明在一个方法的声明之前(紧靠着方法声明)。 它会被应用到方法的 属性描述符上,可以用来监视,修改或者替换方法定义。
function ReplaceMethod(target: any, propertyKey: string | symbol, descriptor: PropertyDescriptor) {
console.log(`[替换装饰器] 替换方法: ${String(propertyKey)}`);
// 返回一个新的方法实现
return {
...descriptor,
value: function (...args: any[]) {
console.log(`方法 ${String(propertyKey)} 已被替换!`);
console.log(`原始参数:`, args);
return `替换后的返回值 - 参数: ${args.join(', ')}`;
}
};
}
class Example1 {
@ReplaceMethod
originalMethod(name: string, age: number): string {
return `原始方法: ${name}, ${age}`;
}
}
const ex1 = new Example1();
console.log(ex1.originalMethod('张三', 25));
// 输出: 方法 originalMethod 已被替换!
// 原始参数: [ '张三', 25 ]
// 替换后的返回值 - 参数: 张三, 25
方法装饰器表达式会在运行时当作函数被调用,传入下列3个参数:
- 对于静态成员来说是类的构造函数,对于实例成员是类的原型对象。
- 成员的名字。
- 成员的属性描述符。
注意 如果代码输出目标版本小于
ES5,Property Descriptor将会是undefined。
有时你可以用一些其他的技巧,来对原方法进行扩展
function LogMethod(target: any, propertyKey: string | symbol, descriptor: PropertyDescriptor) {
const originalMethod = descriptor.value; // 保存原始方法
// 返回包装后的方法
return {
...descriptor,
value: function (...args: any[]) {
console.log(`[日志] 调用方法: ${String(propertyKey)}`);
console.log(`[日志] 参数:`, args);
const startTime = Date.now();
const result = originalMethod.apply(this, args); // 调用原始方法
const endTime = Date.now();
console.log(`[日志] 返回值:`, result);
console.log(`[日志] 执行时间: ${endTime - startTime}ms`);
return result;
}
};
}
class Example2 {
@LogMethod
calculate(a: number, b: number): number {
let sum = 0;
for (let i = 0; i < 100; i++) {
sum += i;
}
return a + b;
}
}
const ex2 = new Example2();
console.log(ex2.calculate(10, 20));
4.3访问装饰器
访问器装饰器声明在一个访问器的声明之前(紧靠着访问器声明)。 访问器装饰器应用于访问器的 属性描述符并且可以用来监视,修改或替换一个访问器的定义。
方法装饰器表达式会在运行时当作函数被调用,传入下列3个参数:
- 对于静态成员来说是类的构造函数,对于实例成员是类的原型对象。
- 成员的名字。
- 成员的属性描述符。
注意 如果代码输出目标版本小于
ES5,Property Descriptor将会是undefined。
function logAccess(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
const originalGet = descriptor.get;
const originalSet = descriptor.set;
if (originalGet) {
descriptor.get = function () {
console.log(`访问 getter: ${propertyKey}`);
return descriptor.value;
};
}
if (originalSet) {
descriptor.set = function (value: any) {
console.log(`访问 setter: ${propertyKey}, 值: ${value}`);
descriptor.value = value;
};
}
}
class Example {
set val(value: string) {
}
@logAccess
get val() {
return this.val
}
}
const ex = new Example();
ex.val = 'hello';
console.log(ex.val);
//访问 setter: val, 值: hello
//访问 getter: val
//hello
4.4属性装饰器
属性装饰器声明在一个属性声明之前(紧靠着属性声明)。 属性装饰器不能用在声明文件中(.d.ts),或者任何外部上下文(比如 declare的类)里。
属性装饰器表达式会在运行时当作函数被调用,传入下列2个参数:
- 对于静态成员来说是类的构造函数,对于实例成员是类的原型对象。
- 成员的名字。
// 属性装饰器:标记属性为只读
function readonly(target: any, propertyKey: string) {
const descriptor = Object.getOwnPropertyDescriptor(target, propertyKey) || {};
descriptor.writable = false;
Object.defineProperty(target, propertyKey, descriptor);
}
class User {
@readonly
name: string = '张三';
}
const user = new User();
console.log(user.name); // 张三
// user.name = '李四'; // 错误:无法赋值给只读属性
4.5参数装饰器
参数装饰器声明在一个参数声明之前(紧靠着参数声明)。 参数装饰器应用于类构造函数或方法声明。 参数装饰器不能用在声明文件(.d.ts),重载或其它外部上下文(比如 declare的类)里。
参数装饰器表达式会在运行时当作函数被调用,传入下列3个参数:
- 对于静态成员来说是类的构造函数,对于实例成员是类的原型对象。
- 成员的名字。
- 参数在函数参数列表中的索引。
function logParam(target: any, propertyKey: string | symbol | undefined, parameterIndex: number) {
console.log(`参数装饰器: 方法 ${String(propertyKey)} 的第 ${parameterIndex} 个参数被标记`);
}
class Calculator {
add(@logParam a: number, @logParam b: number): number {
return a + b;
}
}
const calc = new Calculator();
console.log(calc.add(5, 3)); // 8
5、原理
装饰器本质就是语法糖,在编译时转换为普通函数调用而已