TypeScript: TypeScript 装饰器

最后更新:2026-08-26

装饰器(Decorators)是实验性的语法——它用 @decorator 语法给类、方法、属性添加元数据或修改行为。在框架(如 NestJS、Angular)中广泛使用。

1. 装饰器概述

(1) 什么是装饰器

装饰器是一个函数——它接收目标(类/方法/属性)作为参数,可以修改或增强目标的行为:

TYPESCRIPT
// 装饰器函数
function log(target: any, key: string, descriptor: PropertyDescriptor) {
  const original = descriptor.value;
  descriptor.value = function (...args: any[]) {
    console.log(`调用 ${key},参数:${args}`);
    return original.apply(this, args);
  };
}

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

let calc = new Calculator();
calc.add(1, 2);
// 输出:调用 add,参数:1,2

(2) 开启装饰器支持

装饰器是实验性特性,需要在 tsconfig.json 中开启:

JSON
{
  "compilerOptions": {
    "experimentalDecorators": true,
    "emitDecoratorMetadata": true
  }
}

(3) 五种装饰器类型

类型 修饰目标 参数
类装饰器 类定义 构造函数
方法装饰器 类方法 目标、方法名、描述符
属性装饰器 类属性 目标、属性名
参数装饰器 函数参数 目标、方法名、参数索引
访问器装饰器 getter/setter 目标、访问器名、描述符

2. 类装饰器

类装饰器接收构造函数作为参数,可以修改或替换类定义:

(1) 基本用法

TYPESCRIPT
function sealed(constructor: Function) {
  Object.seal(constructor);
  Object.seal(constructor.prototype);
}

@sealed
class Config {
  host: string = "localhost";
  port: number = 3000;
}

// Config 已被密封——不能添加新属性

(2) 装饰器工厂

需要参数时,用工厂函数返回装饰器:

TYPESCRIPT
function className(prefix: string) {
  return function (constructor: Function) {
    constructor.prototype._displayName = `${prefix}_${constructor.name}`;
  };
}

@className("App")
class UserService {}
// UserService.prototype._displayName = "App_UserService"

(3) 替换构造函数

TYPESCRIPT
function logged<T extends { new (...args: any[]): {} }>(constructor: T) {
  return class extends constructor {
    constructor(...args: any[]) {
      console.log(`创建 ${constructor.name},参数:${args}`);
      super(...args);
    }
  };
}

@logged
class User {
  constructor(public name: string, public age: number) {}
}

let user = new User("Charlie", 20);
// 输出:创建 User,参数:Charlie,20

▶ 示例:注册装饰器——自动收集类

TYPESCRIPT
const registry: Map<string, any> = new Map();

function Register(name: string) {
  return function <T extends { new (...args: any[]): {} }>(constructor: T) {
    registry.set(name, constructor);
    return constructor;
  };
}

@register("user")
class UserService {
  getUser(id: number) { return { id, name: "用户" + id }; }
}

@register("product")
class ProductService {
  getProduct(id: number) { return { id, title: "商品" + id }; }
}

// 通过名称获取服务
function getService(name: string) {
  let Service = registry.get(name);
  if (!Service) throw new Error(`未注册的服务:${name}`);
  return new Service();
}

let user = (getService("user") as UserService).getUser(1);
let product = (getService("product") as ProductService).getProduct(2);

console.log(user.name);     // "用户1"
console.log(product.title); // "商品2"
▶ 试一试

3. 方法装饰器

方法装饰器可以观察、修改或替换方法定义:

(1) 基本用法

TYPESCRIPT
function enumerable(value: boolean) {
  return function (target: any, key: string, descriptor: PropertyDescriptor) {
    descriptor.enumerable = value;
  };
}

class Person {
  constructor(public name: string) {}

  @enumerable(false)
  getFullName(): string {
    return this.name;
  }
}

(2) 方法执行日志

TYPESCRIPT
function measure(target: any, key: string, descriptor: PropertyDescriptor) {
  const original = descriptor.value;

  descriptor.value = function (...args: any[]) {
    const start = performance.now();
    const result = original.apply(this, args);
    const end = performance.now();
    console.log(`${key} 执行时间:${(end - start).toFixed(2)}ms`);
    return result;
  };

  return descriptor;
}

class DataProcessor {
  @measure
  processLargeArray(data: number[]): number {
    return data.reduce((sum, n) => sum + n, 0);
  }
}

let processor = new DataProcessor();
processor.processLargeArray(Array.from({ length: 1000000 }, (_, i) => i));
// 输出:processLargeArray 执行时间:XX.XXms

(3) 方法防抖

TYPESCRIPT
function debounce(delay: number) {
  return function (target: any, key: string, descriptor: PropertyDescriptor) {
    const original = descriptor.value;
    let timer: any;

    descriptor.value = function (...args: any[]) {
      clearTimeout(timer);
      timer = setTimeout(() => original.apply(this, args), delay);
    };

    return descriptor;
  };
}

class SearchBox {
  @debounce(300)
  search(query: string): void {
    console.log(`搜索:${query}`);
  }
}

let box = new SearchBox();
box.search("T");     // 300ms 后如果没新输入才执行
box.search("Ty");
box.search("Typ");
box.search("TypeScript");  // 只有这一次会执行

4. 属性装饰器与参数装饰器

(1) 属性装饰器

TYPESCRIPT
function format(formatStr: string) {
  return function (target: any, key: string) {
    // 存储格式化信息到元数据
    Reflect.defineMetadata("format", formatStr, target, key);
  };
}

class User {
  @format("YYYY-MM-DD")
  birthday: string = "2000-01-15";

  @format("HH:mm:ss")
  loginTime: string = "09:30:00";
}

(2) 参数装饰器

TYPESCRIPT
function required(target: any, key: string, index: number) {
  const existing: number[] = Reflect.getMetadata("required", target, key) || [];
  existing.push(index);
  Reflect.defineMetadata("required", existing, target, key);
}

class UserService {
  createUser(@required name: string, @required email: string, age?: number) {
    // 参数验证在运行时由框架处理
  }
}

5. 装饰器组合

多个装饰器可以同时使用——执行顺序是从下到上(靠近目标的先执行):

TYPESCRIPT
function log1(target: any, key: string, descriptor: PropertyDescriptor) {
  console.log("log1 应用");
}

function log2(target: any, key: string, descriptor: PropertyDescriptor) {
  console.log("log2 应用");
}

class Example {
  @log1    // 第二个应用(外层)
  @log2    // 第一个应用(内层,靠近方法)
  method() {}
}
// 输出:log2 应用 → log1 应用

(1) 常见组合模式

TYPESCRIPT
class ApiController {
  @logged
  @measure
  @debounce(100)
  async fetchData(url: string): Promise<any> {
    // 先 debounce → 再 measure → 再 logged(从下到上应用)
  }
}

❓ 常见问题

Q 装饰器是正式特性吗?
A 目前还是实验性特性(Stage 3 提案阶段)。TypeScript 5.0 开始支持新的 Stage 3 装饰器语法(不再需要 experimentalDecorators),但旧语法仍然可用。NestJS、Angular 等框架目前使用旧语法。建议跟随所用框架的推荐方式。
Q 装饰器能用于函数吗?
A 不能。装饰器只能用于类和类成员(方法、属性、参数)。普通函数不支持装饰器——这是 JavaScript 提案的设计限制。如果需要增强函数,用高阶函数(wrapper)模式。
Q 装饰器有什么性能影响?
A 装饰器在类定义时执行一次(不是每次调用时执行),开销很小。方法装饰器修改的包装函数每次调用都有轻微开销(多一层函数调用),但通常可以忽略。性能敏感场景避免在热路径上使用多层装饰器。
Q 不用装饰器能实现同样的效果吗?
A 可以。装饰器本质是语法糖——@log method() 等价于 method = log(method)。不用装饰器可以用高阶函数、mixin 模式、AOP 工具库等方式实现。装饰器的优势是声明式、直观、代码更整洁。

📖 小节

📝 作业

  1. 基础题(难度⭐):写一个 @readonly 方法装饰器,把方法设为不可覆写(writable: false)。在一个类的方法上使用它,尝试在子类覆写该方法,观察效果。
  2. 进阶题(难度⭐⭐):写一个 @deprecated(message) 装饰器工厂——被装饰的方法调用时打印废弃警告。提示:在方法包装函数中加 console.warn
  3. 挑战题(难度⭐⭐⭐):用类装饰器实现简单的依赖注入——@Injectable() 标记服务类,@Inject(Service) 标记依赖属性,Container.resolve(TargetClass) 自动解析并注入依赖。
Web-Tutorial.com

Web-Tutorial 技术团队

由多位开发者共同维护的编程教程平台。每篇教程由对应领域的开发者编写和审核,确保内容准确可靠。如发现任何问题,欢迎向我们反馈。

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏