Skip to content
第 10 章 前端 ⏱ 12 分钟阅读

第 10 章:装饰器 ​

学习目标 ​

  • 理解装饰器的本质(类/方法包装函数)
  • 学会类装饰器、方法装饰器、属性装饰器
  • 掌握参数装饰器与元数据反射
  • 识别装饰器的使用限制

一、装饰器是什么 ​

装饰器是一个函数,用来包装类、方法、属性等。

typescript
function log(target: any): void {
  console.log(`类被装饰:${target.name}`);
}

@log
class User {}
// 输出:类被装饰:User

⚠️ 坑 1:装饰器是实验性特性,tsconfig.json 必须开启 experimentalDecorators: true。

二、类装饰器 ​

typescript
// 装饰器函数:接收类的构造函数
function sealed(constructor: Function): void {
  Object.seal(constructor);
  Object.seal(constructor.prototype);
}

@sealed
class User {
  constructor(public name: string) {}
}

装饰器工厂(返回装饰器):

typescript
function log(prefix: string) {
  return function (constructor: Function): void {
    console.log(`${prefix}: ${constructor.name} 被创建`);
  };
}

@log('INFO')
class User {}
// INFO: User 被创建

三、方法装饰器 ​

typescript
function measure(
  target: any,
  propertyKey: string,
  descriptor: PropertyDescriptor,
): void {
  const original = descriptor.value;

  descriptor.value = function (...args: any[]): any {
    const start = Date.now();
    const result = original.apply(this, args);
    const end = Date.now();
    console.log(`${propertyKey} 耗时:${end - start}ms`);
    return result;
  };
}

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

new Calculator().add(1, 2);  // add 耗时:0ms(打印)

⚠️ 坑 2:方法装饰器拿到的 descriptor.value 是原方法,必须在 descriptor.value 上改写才生效,直接 return 装饰器函数不行。

四、属性装饰器 ​

typescript
function format(format: string) {
  return function (target: any, propertyKey: string): void {
    let value: string;

    Object.defineProperty(target, propertyKey, {
      get(): string { return value; },
      set(v: string): void {
        value = format === 'upper' ? v.toUpperCase() : v.toLowerCase();
      },
      enumerable: true,
      configurable: true,
    });
  };
}

class User {
  @format('upper')
  name!: string;
}

const u = new User();
u.name = 'tom';
console.log(u.name);  // 'TOM'

⚠️ 坑 3:属性装饰器不能直接修改原型上的属性(原型链共享),只能用 Object.defineProperty 在实例上定义。

五、参数装饰器 ​

typescript
function requirePositive(target: any, propertyKey: string, parameterIndex: number): void {
  console.log(`${propertyKey} 的第 ${parameterIndex} 个参数需要正数`);
}

class Account {
  deposit(@requirePositive amount: number): void {
    console.log(`存入:${amount}`);
  }
}

六、装饰器执行顺序 ​

多个装饰器由下往上包装,执行从上到下:

typescript
function first() { return (target: any) => console.log('first'); }
function second() { return (target: any) => console.log('second'); }

@first()
@second()
class User {}
// 输出:first → second

七、实战:权限装饰器 ​

typescript
function requireRole(role: 'admin' | 'user') {
  return function (
    target: any,
    propertyKey: string,
    descriptor: PropertyDescriptor,
  ): void {
    const original = descriptor.value;
    descriptor.value = function (...args: any[]): any {
      const current = (this as any).role;
      if (current !== role) throw new Error(`需要 ${role} 权限`);
      return original.apply(this, args);
    };
  };
}

class AdminController {
  role: 'admin' = 'admin';

  @requireRole('admin')
  deleteUser(id: number): void {
    console.log(`删除用户 ${id}`);
  }
}

new AdminController().deleteUser(1);  // ✅

八、与 reflect-metadata 配合 ​

装饰器常配合 reflect-metadata 库(在 NestJS / Angular 里大量使用):

typescript
import 'reflect-metadata';

function Column(type: string) {
  return function (target: any, propertyKey: string): void {
    Reflect.defineMetadata('design:type', type, target, propertyKey);
  };
}

class User {
  @Column('string')
  name!: string;
}

const type = Reflect.getMetadata('design:type', new User(), 'name');  // 'string'

九、本章小结 ​

要点关键
装饰器包装类/方法/属性的函数,实验性语法
开启experimentalDecorators: true
类装饰器接收类的构造函数
方法装饰器改写 descriptor.value
属性装饰器用 Object.defineProperty
工厂返回装饰器的高阶函数
顺序由下往上包装,从上往下执行

动手练习 ​

  1. 类装饰器:写 @logClass 装饰器,类被实例化时打印类名
  2. 方法装饰器:写 @deprecated 装饰器,调用方法时打印"该方法即将废弃"
  3. 权限装饰器:写 @requireRole('admin'),权限不足抛错

下一章:第 11 章:类型推导 →

本站基于 VitePress 构建 · 由 StackHub 团队维护