前置知识: JavaScript

装饰器标准实现

21 min高级

TypeScript Stage 3装饰器标准实现详解:类装饰器、方法装饰器与元数据。

阅读提示:正文以代码和白话为主,不出现类型论公式。进阶文档中若出现 Γ ⊢ e : τ 这类记号,第一遍可完全跳过(完整规则见 001-HowToReadThisCourse)。

前置知识

学习目标

  • 掌握「1. 历史动机与发展脉络」的核心机制、典型用法与常见陷阱
  • 掌握「2. 形式化定义」的核心机制、典型用法与常见陷阱
  • 掌握「3. 理论推导与原理解析」的核心机制、典型用法与常见陷阱
  • 掌握「4. 代码示例」的核心机制、典型用法与常见陷阱
  • 掌握「5. 对比分析」的核心机制、典型用法与常见陷阱

本文档对标 MIT 6.S192、Stanford CS110、CMU 15-214 等课程教学水准,系统讲解 TypeScript 装饰器(Decorators)从 Legacy 到 Stage 3 标准的演进、形式化语义、元数据协议与工程级应用。所有代码示例均可在 TS 5.4 + target: ES2022 下编译通过。


1. 历史动机与发展脉络

1.1 装饰器的起源

装饰器(Decorator)的概念最早可追溯到 1960 年代的 Lisp 语言,其在函数定义时通过 defadvice 修饰函数行为。Python 在 2004 年的 PEP 318 中正式引入 @decorator 语法,Java 在 JSR 250(2006 年)中引入注解(Annotation),C# 在 2.0(2005 年)引入 Attribute。

JavaScript 装饰器提案由 Yehuda Katz 与 Brian Terlson 在 2014 年首次提交至 TC39,历经 Stage 1(2014)、Stage 2(2016)、Stage 3(2022)三个阶段。整个提案历时多年,是 TC39 历史上耗时最长的提案之一;截至 2026-09,提案仍处于 Stage 3(未定稿)。

1.2 Stage 1 → Stage 2 → Stage 3 的演进

Stage 1(2014-2017):Legacy 时代

最早的装饰器提案基于 ES5 描述符(descriptor)API,由 Yehuda Katz 主导设计。TypeScript 在 TS 5.0 之前 一直通过 experimentalDecorators: true 标志支持这一版本。Legacy 装饰器接收三个参数:

  • 类装饰器:(target: Function) => Function | void
  • 方法装饰器:(target, key, descriptor: PropertyDescriptor) => PropertyDescriptor | void
  • 属性装饰器:(target, key) => void

Stage 2(2016-2022):Descriptor 时代

Stage 2 提案引入了更严格的类型签名,但仍基于描述符。这一阶段的核心变化是:

  • 装饰器函数返回一个描述符,而非直接修改
  • 引入了成员装饰器(member decorator)的统一形式
  • 元数据协议初步设计

Stage 3(2022 至今):Context 时代

2022 年 3 月,TC39 将装饰器提案推进至 Stage 3。Stage 3 装饰器由 Daniel Ehrenberg 主导设计,引入了上下文对象(context object)作为第二参数:

  • 类装饰器:(value: Class, context: ClassDecoratorContext) => Class | void
  • 方法装饰器:(value: Function, context: ClassMethodDecoratorContext) => Function | void
  • 属性装饰器:(value: undefined, context: ClassFieldDecoratorContext) => (initialValue) => initialValue
  • 自动访问器装饰器:(value: ClassAccessorDecoratorTarget, context: ClassAccessorDecoratorContext) => ClassAccessorDecoratorResult

TypeScript 在 TS 5.0(2023 年 3 月) 正式支持 Stage 3 装饰器,无需 experimentalDecorators 标志。

1.3 版本演进时间线

2014-04  TC39 Stage 1   Yehuda Katz 提交初版装饰器提案
2015-04  TS 1.5         experimentalDecorators 标志引入(Legacy 装饰器)
2016 初  TC39 Stage 2   描述符 API 阶段(期间模型多次重写)
2022-03  TC39 Stage 3   上下文对象 API 阶段(截至 2026-09 仍未定稿,未进 Stage 4)
2023-03  TS 5.0         正式支持 Stage 3 装饰器(含 Symbol.metadata、context.access、addInitializer)

1.4 设计动机深度分析

Daniel Ehrenberg 在 TC39 Stage 3 提案 中阐述了 Stage 3 装饰器的三大核心动机:

动机一:类型安全的元编程。 Legacy 装饰器通过 PropertyDescriptor 操作成员,类型签名宽松(any 充斥)。Stage 3 通过上下文对象提供精确的类型签名,使装饰器实现可在编译期校验。

动机二:标准化元数据协议。 Legacy 装饰器依赖 reflect-metadata polyfill 与 emitDecoratorMetadata 标志,非 ECMAScript 标准。Stage 3 引入 Symbol.metadata 作为标准元数据载体,待提案定稿后由引擎原生支持(当前阶段由 TypeScript 编译为标准运行时代码实现)。

动机三:与类字段语义对齐。 ES2022 类字段引入后,属性装饰器需要处理”字段尚未赋值”的情况。Stage 3 属性装饰器返回一个初始化函数 (initialValue) => initialValue,与类字段语义自然对齐。

1.5 社区生态发展

装饰器社区的发展可划分为三个阶段:

  • 2015-2020 Legacy 时代:Angular、NestJS、TypeORM 依赖 experimentalDecorators,社区形成庞大生态
  • 2020-2023 过渡期:TC39 提案演进至 Stage 3,框架开始评估迁移路径
  • 2023 至今 Stage 3 时代:TS 5.0 落地后,新项目默认采用 Stage 3;旧项目通过兼容层逐步迁移

1.6 当前社区共识

TypeScript 核心团队的方向一贯明确:

  • 新项目默认采用 Stage 3 装饰器,不再启用 experimentalDecorators
  • Legacy 装饰器进入维护模式,不再添加新特性,但保持向后兼容
  • reflect-metadata polyfill 仍需用于 Legacy 装饰器,Stage 3 装饰器使用原生 Symbol.metadata
  • NestJS、TypeORM 等框架的迁移路径:通过适配层同时支持 Legacy 与 Stage 3,未来版本逐步切换至 Stage 3

需要特别注意的是:截至 2026-09,装饰器提案仍处于 Stage 3(未定稿),参数装饰器不在标准装饰器的当前形态中;构造器参数注入类需求(如 NestJS 风格的 @Inject)在标准装饰器下需改用字段装饰器加元数据,或继续使用 Legacy 装饰器。


2. 形式化定义

2.1 装饰器的代数语义

装饰器可形式化为一个高阶函数(higher-order function),接收被装饰的实体(entity)与上下文(context),返回同类型的实体或 void:

Decorator≜(E×C)→(E∪{⊥})\text{Decorator} \triangleq (E \times C) \to (E \cup \{\bot\})

其中:

  • EE 为被装饰实体的类型(类、方法、属性、访问器)
  • CC 为上下文对象(包含 kind、name、metadata 等字段)
  • ⊥\bot 表示 void(不替换原实体)

2.2 类装饰器的形式化

类装饰器接收一个类(构造函数)与类上下文,返回一个新类或 void:

Γ⊢C:ClassΓ⊢ctx:ClassDecoratorContextΓ⊢ClassDecorator(C,ctx):Class∪{⊥}\frac{\Gamma \vdash C : \text{Class} \quad \Gamma \vdash \text{ctx} : \text{ClassDecoratorContext}}{\Gamma \vdash \text{ClassDecorator}(C, \text{ctx}) : \text{Class} \cup \{\bot\}}

类上下文 ClassDecoratorContext 的形式化定义:

ClassDecoratorContext≜{kind:’class’,name:string∪undefined,metadata:Metadata}\text{ClassDecoratorContext} \triangleq \{ \text{kind}: \text{'class'}, \text{name}: \text{string} \cup \text{undefined}, \text{metadata}: \text{Metadata} \}

2.3 方法装饰器的形式化

方法装饰器接收一个函数与方法上下文,返回一个新函数或 void:

Γ⊢f:FunctionΓ⊢ctx:ClassMethodDecoratorContextΓ⊢MethodDecorator(f,ctx):Function∪{⊥}\frac{\Gamma \vdash f : \text{Function} \quad \Gamma \vdash \text{ctx} : \text{ClassMethodDecoratorContext}}{\Gamma \vdash \text{MethodDecorator}(f, \text{ctx}) : \text{Function} \cup \{\bot\}}

方法上下文的形式化定义:

ClassMethodDecoratorContext≜{kind:’method’,name:string,access:{get:(o)→f,set:(o,v)→void},static:boolean,metadata:Metadata,addInitializer:(f)→void}\text{ClassMethodDecoratorContext} \triangleq \{ \text{kind}: \text{'method'}, \text{name}: \text{string}, \text{access}: \{ \text{get}: (o) \to f, \text{set}: (o, v) \to \text{void} \}, \text{static}: \text{boolean}, \text{metadata}: \text{Metadata}, \text{addInitializer}: (f) \to \text{void} \}

关键点:

  • access 字段提供安全的属性访问器,避免直接 obj[key] 破坏私有性
  • addInitializer 允许在类实例化时执行额外的初始化逻辑
  • metadata 是类级别的元数据对象,所有装饰器共享

2.4 属性装饰器的形式化

属性装饰器接收 undefined(属性尚无值)与属性上下文,返回一个初始化函数:

Γ⊢ctx:ClassFieldDecoratorContextΓ⊢FieldDecorator(undefined,ctx):((v)→v)∪{⊥}\frac{\Gamma \vdash \text{ctx} : \text{ClassFieldDecoratorContext}}{\Gamma \vdash \text{FieldDecorator}(\text{undefined}, \text{ctx}) : ((v) \to v) \cup \{\bot\}}

属性装饰器返回的初始化函数 init: (initialValue) => initialValue 在实例化时被调用,可修改初始值。

2.5 元数据协议的形式化

Stage 3 装饰器通过 Symbol.metadata 在类上挂载元数据对象。形式化定义:

Γ⊢C:ClassΓ⊢C[Symbol.metadata]:Metadata\frac{\Gamma \vdash C : \text{Class}}{\Gamma \vdash C[\text{Symbol.metadata}] : \text{Metadata}}

所有装饰器共享同一个 metadata 对象:

Metadata≜{ki↦vi}(mutable, shared across decorators)\text{Metadata} \triangleq \{ k_i \mapsto v_i \} \quad \text{(mutable, shared across decorators)}

2.6 装饰器组合的代数结构

多个装饰器叠加应用时,构成函数组合(function composition)的代数结构:

(@A∘@B∘@C) C≜A(B(C(C,ctxC),ctxB),ctxA)(@A \circ @B \circ @C)\ C \triangleq A(B(C(C, \text{ctx}_C), \text{ctx}_B), \text{ctx}_A)

执行顺序:

  1. 求值顺序:从上到下(@A 先求值)
  2. 应用顺序:从下到上(@C 先应用到类,然后 @B,最后 @A)

形式化规则:

@A@B@Cclass Xevaluated as A(B(C(X)))\frac{@A \quad @B \quad @C \quad \text{class } X}{\text{evaluated as } A(B(C(X)))}

3. 理论推导与原理解析

3.1 装饰器求值时机

装饰器在类定义时求值(class evaluation time),而非实例化时。形式化:

class declaration is evaluateddecorators are applied immediately\frac{\text{class declaration is evaluated}}{\text{decorators are applied immediately}}

实例代码:

@logged
class User {
  @memoize
  getName() { return 'Alice'; }
}

执行顺序:

  1. User 类被构造
  2. @memoize 应用于 getName 方法,返回新函数
  3. @logged 应用于 User 类,返回新类
  4. 类定义完成,可被实例化

3.2 addInitializer 机制

context.addInitializer(fn) 允许在类实例化时执行额外逻辑。形式化:

ctx.addInitializer(f) is calledf is invoked during class instantiation\frac{\text{ctx.addInitializer}(f) \text{ is called}}{f \text{ is invoked during class instantiation}}

对于类装饰器,初始化函数在类定义后立即调用;对于方法/属性装饰器,初始化函数在实例化时调用。

实例:

function bound(value, context) {
  context.addInitializer(function () {
    this[context.name] = this[context.name].bind(this);
  });
}

class Logger {
  @bound
  log(msg) { console.log(msg); }
}

const logger = new Logger();
const log = logger.log;
log('hello');  // 'hello'(已绑定 this)

3.3 元数据传播机制

context.metadata 是一个共享对象,所有装饰器对同一个类的元数据写入都汇聚到同一个对象。形式化:

decorator D1 writes (k1,v1)decorator D2 writes (k2,v2)Class[Symbol.metadata]={k1↦v1,k2↦v2}\frac{\text{decorator } D_1 \text{ writes } (k_1, v_1) \quad \text{decorator } D_2 \text{ writes } (k_2, v_2)}{\text{Class}[\text{Symbol.metadata}] = \{ k_1 \mapsto v_1, k_2 \mapsto v_2 \}}

实例:

function route(path) {
  return (value, context) => {
    context.metadata.routes ??= {};
    context.metadata.routes[context.name] = path;
  };
}

class ApiController {
  @route('/users')
  getUsers() {}

  @route('/users/:id')
  getUser() {}
}

console.log(ApiController[Symbol.metadata].routes);
// { getUsers: '/users', getUser: '/users/:id' }

3.4 自动访问器(Auto-Accessor)的特殊语义

ES2022 引入自动访问器语法 accessor field,Stage 3 装饰器对自动访问器有专门的处理。形式化:

AutoAccessorDecorator≜(AccessorTarget,AccessorContext)→AccessorResult\text{AutoAccessorDecorator} \triangleq (\text{AccessorTarget}, \text{AccessorContext}) \to \text{AccessorResult}

其中 AccessorResult 包含 get 与 set 方法,允许装饰器拦截读写操作:

class Tracked {
  accessor value = 0;

  @tracked
  accessor count = 0;
}

function tracked(target, context) {
  return {
    get() { return target.get.call(this); },
    set(value) {
      console.log(`Setting ${context.name} to ${value}`);
      target.set.call(this, value);
    },
  };
}

3.5 装饰器组合的结合律

装饰器组合满足结合律(associativity):

((@A∘@B)∘@C)≡(@A∘(@B∘@C))((@A \circ @B) \circ @C) \equiv (@A \circ (@B \circ @C))

但不满足交换律(commutativity):

@A∘@B≢@B∘@A@A \circ @B \not\equiv @B \circ @A

实例:

@log
@validate
class Service {}

// 等价于
const _temp = validate(class Service {});
const _final = log(_temp);

3.6 装饰器与继承的交互

装饰器在子类继承父类时,不会被继承。形式化:

class B extends AA has decorator @DB does not have @D\frac{\text{class } B \text{ extends } A \quad A \text{ has decorator } @D}{B \text{ does not have } @D}

但装饰器修改的方法实现会被继承:

@logged
class Base {
  greet() { return 'hello'; }
}

class Derived extends Base {}

const d = new Derived();
d.greet();  // 'hello'(greet 方法被 @logged 修饰的逻辑会被继承)
// 但 Derived 类本身没有 @logged 装饰器

3.7 编译产物的形式化

Stage 3 装饰器编译产物使用 applyDecs2312(或类似运行时辅助函数)应用装饰器。形式化:

ClassDefinition→applyDecs2312(Class,DecoratorsList,MemberDecorators)\text{ClassDefinition} \to \text{applyDecs2312}(\text{Class}, \text{DecoratorsList}, \text{MemberDecorators})

编译产物示例:

// 源码
@logged
class User {
  @memoize
  getName() { return 'Alice'; }
}

// 编译产物(简化)
let _getName;
class User {
  constructor() {
    _initClass.call(this);
  }
  getName() { return _getName.call(this); }
}

let _initClass;
(() => {
  _getName = __memoize(User.prototype, 'getName', _getName_desc)[0];
  _initClass = __logged(User, classMetadata)[0];
  __setFunctionName(_getName, 'getName');
})();

4. 代码示例

4.1 类装饰器:日志记录

// TS 5.4, tsconfig.json: { "target": "ES2022" }

/**
 * 日志装饰器
 * 在类实例化时打印参数,用于调试
 */
function logged<T extends { new(...args: any[]): {} }>(value: T, context: ClassDecoratorContext) {
  const name = context.name;
  return class extends value {
    constructor(...args: any[]) {
      console.log(`[logged] Creating ${String(name)} with args:`, args);
      super(...args);
    }
  };
}

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

new User('Alice', 30);
// 输出:[logged] Creating User with args: ['Alice', 30]

4.2 方法装饰器:防抖

/**
 * 防抖装饰器
 * 延迟方法执行,在指定时间内多次调用只执行最后一次
 */
function debounce(delay: number) {
  return function (value: Function, context: ClassMethodDecoratorContext) {
    let timer: ReturnType<typeof setTimeout>;

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

class Search {
  @debounce(300)
  handleInput(query: string) {
    console.log('Searching:', query);
  }
}

const search = new Search();
search.handleInput('a');
search.handleInput('ab');
search.handleInput('abc');
// 300ms 后输出:Searching: abc

4.3 方法装饰器:节流

/**
 * 节流装饰器
 * 在指定时间窗口内最多执行一次
 */
function throttle(delay: number) {
  return function (value: Function, context: ClassMethodDecoratorContext) {
    let lastCall = 0;
    return function (this: any, ...args: any[]) {
      const now = Date.now();
      if (now - lastCall >= delay) {
        lastCall = now;
        value.call(this, ...args);
      }
    };
  };
}

class ScrollHandler {
  @throttle(100)
  onScroll(event: Event) {
    console.log('Scroll position:', window.scrollY);
  }
}

4.4 方法装饰器:记忆化

/**
 * 记忆化装饰器
 * 缓存方法返回值,对相同参数直接返回缓存
 * 注意:参数需可序列化为字符串
 */
function memoize(value: Function, context: ClassMethodDecoratorContext) {
  const cache = new Map<string, any>();

  return function (this: any, ...args: any[]) {
    const key = JSON.stringify(args);
    if (cache.has(key)) {
      return cache.get(key);
    }
    const result = value.call(this, ...args);
    cache.set(key, result);
    return result;
  };
}

class Calculator {
  @memoize
  fibonacci(n: number): number {
    if (n < 2) return n;
    return this.fibonacci(n - 1) + this.fibonacci(n - 2);
  }
}

const calc = new Calculator();
console.time('first');
console.log(calc.fibonacci(40));  // 102334155
console.timeEnd('first');  // ~1s

console.time('second');
console.log(calc.fibonacci(40));  // 102334155(缓存命中)
console.timeEnd('second');  // ~0ms

4.5 属性装饰器:必填校验

/**
 * 必填校验装饰器
 * 在实例化时校验属性值不为 null 或 undefined
 */
function required(value: undefined, context: ClassFieldDecoratorContext) {
  return function (this: any, initialValue: any) {
    if (initialValue === undefined || initialValue === null) {
      throw new Error(`${String(context.name)} is required`);
    }
    return initialValue;
  };
}

class User {
  @required
  name: string;

  @required
  email: string;

  constructor(name: string, email: string) {
    this.name = name;
    this.email = email;
  }
}

new User('Alice', 'a@x.com');  // OK
// new User('', '');  // Error: name is required

4.6 属性装饰器:默认值

/**
 * 默认值装饰器
 * 如果属性值为 undefined,使用提供的默认值
 */
function defaultValue<T>(defaultVal: T) {
  return function (value: undefined, context: ClassFieldDecoratorContext) {
    return function (initialValue: T) {
      return initialValue === undefined ? defaultVal : initialValue;
    };
  };
}

class AppConfig {
  @defaultValue(8080)
  port: number;

  @defaultValue('localhost')
  host: string;

  @defaultValue(true)
  debug: boolean;
}

const config = new AppConfig();
config.port = 3000;  // 显式设置
console.log(config.host);  // 'localhost'(使用默认值)
console.log(config.debug); // true

4.7 元数据:路由注册

/**
 * 路由装饰器
 * 将方法注册为 HTTP 路由处理器
 * 使用 context.metadata 共享路由表
 */
function route(path: string) {
  return function (value: Function, context: ClassMethodDecoratorContext) {
    context.metadata.routes ??= {};
    context.metadata.routes[context.name] = path;
  };
}

/**
 * HTTP 方法装饰器
 * 标注方法接受的 HTTP 方法
 */
function httpMethod(method: string) {
  return function (value: Function, context: ClassMethodDecoratorContext) {
    context.metadata.methods ??= {};
    context.metadata.methods[context.name] = method;
  };
}

class ApiController {
  @route('/users')
  @httpMethod('GET')
  getUsers() {
    return [];
  }

  @route('/users/:id')
  @httpMethod('GET')
  getUser() {
    return {};
  }

  @route('/users')
  @httpMethod('POST')
  createUser() {
    return {};
  }
}

// 提取元数据,构建路由表
const routes = (ApiController as any)[Symbol.metadata]?.routes;
const methods = (ApiController as any)[Symbol.metadata]?.methods;

console.log(routes);
// { getUsers: '/users', getUser: '/users/:id', createUser: '/users' }

console.log(methods);
// { getUsers: 'GET', getUser: 'GET', createUser: 'POST' }

4.8 自动访问器:响应式状态

/**
 * 响应式状态装饰器
 * 使用自动访问器拦截读写,触发更新
 */
function reactive(target: ClassAccessorDecoratorTarget<any, any>, context: ClassAccessorDecoratorContext) {
  const subscribers = new WeakMap<object, Set<() => void>>();

  function notify(obj: any) {
    const subs = subscribers.get(obj);
    if (subs) {
      subs.forEach(fn => fn());
    }
  }

  return {
    get(this: any) {
      return target.get.call(this);
    },
    set(this: any, value: any) {
      target.set.call(this, value);
      notify(this);
    },
  };
}

class Counter {
  @reactive
  accessor count = 0;

  @reactive
  accessor name = 'counter';

  subscribe(fn: () => void) {
    // 简化示例:实际应使用 Effect 系统
  }
}

const counter = new Counter();
counter.count = 1;  // 触发通知
counter.count = 2;  // 触发通知

4.9 装饰器组合:API 服务

/**
 * 组合多个装饰器实现完整的 API 服务
 * - @injectable: 标记为可注入
 * - @logged: 记录调用日志
 * - @cached: 缓存响应
 * - @timeout: 请求超时
 */

function injectable<T extends { new(...args: any[]): {} }>(value: T, context: ClassDecoratorContext) {
  context.metadata.injectable = true;
  return value;
}

function cached(ttl: number) {
  return function (value: Function, context: ClassMethodDecoratorContext) {
    const cache = new Map<string, { value: any; expiry: number }>();

    return async function (this: any, ...args: any[]) {
      const key = JSON.stringify(args);
      const now = Date.now();
      const cached = cache.get(key);

      if (cached && cached.expiry > now) {
        return cached.value;
      }

      const result = await value.call(this, ...args);
      cache.set(key, { value: result, expiry: now + ttl });
      return result;
    };
  };
}

function timeout(ms: number) {
  return function (value: Function, context: ClassMethodDecoratorContext) {
    return async function (this: any, ...args: any[]) {
      return Promise.race([
        value.call(this, ...args),
        new Promise((_, reject) =>
          setTimeout(() => reject(new Error(`Timeout after ${ms}ms`)), ms),
        ),
      ]);
    };
  };
}

function loggedMethod(value: Function, context: ClassMethodDecoratorContext) {
  return async function (this: any, ...args: any[]) {
    console.log(`[${String(context.name)}] called with:`, args);
    const start = Date.now();
    try {
      const result = await value.call(this, ...args);
      console.log(`[${String(context.name)}] returned in ${Date.now() - start}ms`);
      return result;
    } catch (err) {
      console.error(`[${String(context.name)}] error:`, err);
      throw err;
    }
  };
}

@injectable
class UserService {
  @loggedMethod
  @cached(60_000)
  @timeout(5_000)
  async getUser(id: string): Promise<{ id: string; name: string }> {
    const res = await fetch(`/api/users/${id}`);
    return res.json();
  }
}

4.10 依赖注入装饰器

/**
 * 依赖注入容器
 * 使用 @injectable 标记可注入类,@inject 注入依赖
 */

interface InjectableMetadata {
  token: symbol;
  singleton: boolean;
}

const container = new Map<symbol, any>();

function injectable(token: symbol, singleton = true) {
  return function <T extends { new(...args: any[]): {} }>(value: T, context: ClassDecoratorContext) {
    const metadata: InjectableMetadata = { token, singleton };
    context.metadata.injectable = metadata;

    if (singleton) {
      container.set(token, new value());
    } else {
      container.set(token, value);
    }

    return value;
  };
}

function inject(token: symbol) {
  return function (value: undefined, context: ClassFieldDecoratorContext) {
    return function (initialValue: any) {
      const instance = container.get(token);
      if (!instance) {
        throw new Error(`No provider for token: ${token.toString()}`);
      }
      return instance;
    };
  };
}

// 服务定义
const LOGGER_TOKEN = Symbol('Logger');
const DB_TOKEN = Symbol('Database');

@injectable(LOGGER_TOKEN)
class Logger {
  log(msg: string) { console.log('[Logger]', msg); }
}

@injectable(DB_TOKEN)
class Database {
  async query(sql: string) { return [{ id: 1 }]; }
}

// 使用依赖注入
class UserService {
  @inject(LOGGER_TOKEN)
  logger!: Logger;

  @inject(DB_TOKEN)
  db!: Database;

  async getUser(id: number) {
    this.logger.log(`Getting user ${id}`);
    return this.db.query(`SELECT * FROM users WHERE id = ${id}`);
  }
}

const service = new UserService();
service.getUser(1);  // [Logger] Getting user 1

5. 对比分析

5.1 与 Java 注解对比

Java 注解(Annotation)是元数据标记,本身不含逻辑:

// Java
@RestController
@RequestMapping("/api")
public class UserController {

    @GetMapping("/users")
    public List<User> getUsers() {
        return userService.findAll();
    }
}

TypeScript 装饰器是可执行函数,可修改被装饰的实体:

// TypeScript
@Controller('/api')
class UserController {

  @Get('/users')
  async getUsers(): Promise<User[]> {
    return userService.findAll();
  }
}

关键差异:

维度Java 注解TypeScript 装饰器
执行时机运行时通过反射读取装饰器立即执行
元数据Annotation 对象Symbol.metadata 对象
逻辑仅标记,需外部框架处理装饰器本身可包含逻辑
类型安全编译期校验Stage 3 编译期校验
运行时开销反射开销装饰器求值开销(一次性)

5.2 与 Python 装饰器对比

Python 装饰器是高阶函数,接收函数返回函数:

# Python
def logged(func):
    def wrapper(*args, **kwargs):
        print(f"Calling {func.__name__}")
        return func(*args, **kwargs)
    return wrapper

@logged
def greet(name):
    print(f"Hello, {name}")

greet("Alice")  # Calling greet\nHello, Alice

TypeScript 装饰器与 Python 装饰器在概念上相似,但有关键差异:

维度Python 装饰器TypeScript 装饰器
应用范围函数、类、方法类、方法、属性、访问器(不含顶层函数)
上下文对象无(仅函数本身)Stage 3 提供 context 对象
元数据通过 functools.wraps 保留通过 Symbol.metadata 共享
类型安全鸭子类型静态类型校验
私有性无法访问私有属性通过 context.access 安全访问

5.3 与 C# Attribute 对比

C# Attribute 是元数据标记,类似 Java 注解:

// C#
[Serializable]
[Obsolete("Use NewClass instead")]
public class OldClass {
    [XmlElement("Name")]
    public string Name { get; set; }
}

关键差异:

  • C# Attribute 在编译期生成元数据,运行时通过反射读取
  • TypeScript 装饰器在类定义时立即执行
  • C# Attribute 不能修改被装饰的实体,TypeScript 装饰器可以

5.4 与 Legacy 装饰器对比

特性Legacy (experimentalDecorators)Stage 3(标准)
启用方式experimentalDecorators: true默认支持
API 风格基于描述符(descriptor)基于上下文对象(context)
类型安全较弱(any 充斥)强(精确类型签名)
元数据需 reflect-metadata polyfill + emitDecoratorMetadata原生 Symbol.metadata
属性装饰器接收 (target, key)返回初始化函数
参数装饰器支持不支持(Stage 3 暂未包含)
生态成熟度极成熟(Angular/NestJS/TypeORM)新生态,逐步成熟

5.5 与 Scala Annotations 对比

Scala 的注解类似 Java,但支持宏(macro)实现编译期代码生成:

// Scala
@deprecated("use newMethod instead", "2.0")
def oldMethod(): Unit = ???

TypeScript 装饰器在运行时执行,而 Scala 宏在编译期生成代码,这是本质差异。


6. 常见陷阱与最佳实践

6.1 陷阱一:箭头函数丢失 this

问题:装饰器返回的箭头函数无法绑定实例 this。

function bad(value: Function, context: ClassMethodDecoratorContext) {
  return (...args: any[]) => {
    // this 是 undefined
    value.call(this, ...args);  // Error
  };
}

解决:使用普通函数 + this: any 注解。

function good(value: Function, context: ClassMethodDecoratorContext) {
  return function (this: any, ...args: any[]) {
    return value.call(this, ...args);
  };
}

6.2 陷阱二:装饰器执行顺序误解

问题:误以为装饰器从上到下执行。

@A
@B
@C
class X {}

// 实际执行顺序:C → B → A

解决:记住”求值从上到下,应用从下到上”。装饰器作为函数调用时,先调用最下方的。

6.3 陷阱三:元数据未共享

问题:在多个装饰器中独立创建元数据对象,而非共享 context.metadata。

function badRoute(path: string) {
  return (value: Function, context: ClassMethodDecoratorContext) => {
    // 错误:每个装饰器创建独立对象
    const routes = {};
    routes[context.name] = path;
  };
}

解决:始终使用 context.metadata 共享对象。

function goodRoute(path: string) {
  return (value: Function, context: ClassMethodDecoratorContext) => {
    context.metadata.routes ??= {};
    context.metadata.routes[context.name] = path;
  };
}

6.4 陷阱四:属性装饰器返回值错误

问题:属性装饰器返回静态值而非初始化函数。

function badDefault(val: any) {
  return (value: undefined, context: ClassFieldDecoratorContext) => {
    return val;  // 错误:返回静态值会被当作初始值
  };
}

解决:返回初始化函数。

function goodDefault(defaultVal: any) {
  return (value: undefined, context: ClassFieldDecoratorContext) => {
    return function (initialValue: any) {
      return initialValue === undefined ? defaultVal : initialValue;
    };
  };
}

6.5 陷阱五:装饰器与继承冲突

问题:装饰器修改类后,子类的 super 调用可能失败。

@logged
class Base {
  greet() { return 'hello'; }
}

class Derived extends Base {
  greet() {
    return super.greet() + ' world';  // 可能调用到 @logged 包装后的方法
  }
}

解决:装饰器修改方法时,保留原方法引用;或在文档中明确说明装饰器对继承的影响。

6.6 陷阱六:私有字段访问受限

问题:装饰器无法直接访问私有字段(#field)。

class User {
  #password: string;

  @validate
  #setPassword(pw: string) {  // Error: 装饰器无法访问私有方法
    this.#password = pw;
  }
}

解决:Stage 3 装饰器通过 context.access 提供安全访问,但仍有限制。考虑将私有字段改为 private(编译期私有)而非 #(运行时私有)。

6.7 陷阱七:Symbol.metadata 未定义

问题:旧版 TypeScript 或未正确配置时,Symbol.metadata 未定义。

console.log(MyClass[Symbol.metadata]);  // undefined

解决:

  • 确保 TypeScript 版本 >= 5.0(5.0 起编译器支持 Symbol.metadata)
  • 添加 polyfill:Symbol.metadata ??= Symbol('Symbol.metadata');
  • 检查 target 配置:"target": "ES2022" 或更高

6.8 陷阱八:Legacy 与 Stage 3 混用

问题:同时启用 experimentalDecorators 与 Stage 3 装饰器会导致冲突。

// 错误配置
{
  "compilerOptions": {
    "experimentalDecorators": true
    // Stage 3 会被禁用
  }
}

解决:

  • 新项目:移除 experimentalDecorators,使用 Stage 3
  • 旧项目:保持 experimentalDecorators: true,等待框架迁移后切换

6.9 最佳实践

  1. 新项目优先使用 Stage 3 装饰器,避免 Legacy 装饰器的类型不安全
  2. 装饰器应保持单一职责,复杂逻辑通过组合实现
  3. 使用 context.metadata 共享状态,而非全局变量
  4. 为装饰器编写详细文档,说明副作用与执行时机
  5. 测试装饰器的边界情况:继承、私有字段、异步方法
  6. 监控装饰器的运行时开销:装饰器在类定义时求值,可能影响启动时间
  7. 使用 addInitializer 而非修改构造函数,保持装饰器的可组合性

7. 工程实践

7.1 项目 tsconfig 配置

// tsconfig.json - Stage 3 装饰器
{
  "compilerOptions": {
    "target": "ES2022",
    "module": "ESNext",
    "moduleResolution": "Bundler",
    "strict": true,
    "experimentalDecorators": false,
    "lib": ["ES2022", "DOM", "DOM.Iterable"],
    "useDefineForClassFields": true
  }
}
// tsconfig.legacy.json - Legacy 装饰器(兼容旧项目)
{
  "compilerOptions": {
    "target": "ES2021",
    "experimentalDecorators": true,
    "emitDecoratorMetadata": true
  }
}

7.2 元数据 polyfill

// src/polyfills/metadata.ts

// Symbol.metadata polyfill(适用于 TS < 5.2 或旧浏览器)
(Symbol as any).metadata ??= Symbol('Symbol.metadata');

// reflect-metadata polyfill(仅 Legacy 装饰器需要)
// import 'reflect-metadata';

7.3 装饰器测试

// src/decorators/__tests__/logged.test.ts
import { describe, it, expect, vi } from 'vitest';

function logged<T extends { new(...args: any[]): {} }>(value: T, context: ClassDecoratorContext) {
  return class extends value {
    constructor(...args: any[]) {
      console.log(`Creating ${String(context.name)}`);
      super(...args);
    }
  };
}

describe('@logged', () => {
  it('should log on instantiation', () => {
    const spy = vi.spyOn(console, 'log').mockImplementation(() => {});

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

    new User('Alice');

    expect(spy).toHaveBeenCalledWith('Creating User');
    spy.mockRestore();
  });

  it('should preserve instance properties', () => {
    @logged
    class User {
      constructor(public name: string) {}
    }

    const user = new User('Alice');
    expect(user.name).toBe('Alice');
    expect(user).toBeInstanceOf(User);
  });
});

7.4 性能调优

// 装饰器在类定义时求值,影响启动时间
// 优化策略:

// 1. 延迟初始化:装饰器仅注册元数据,实际逻辑在首次调用时执行
function lazyRoute(path: string) {
  return function (value: Function, context: ClassMethodDecoratorContext) {
    context.metadata.routes ??= {};
    context.metadata.routes[context.name] = { path, handler: value };
    // 不立即包装函数,延迟到路由注册时
  };
}

// 2. 缓存装饰器结果
const memoCache = new WeakMap();
function memoizedDecorator(value: Function, context: ClassMethodDecoratorContext) {
  if (memoCache.has(value)) {
    return memoCache.get(value);
  }
  const wrapped = function (this: any, ...args: any[]) {
    return value.call(this, ...args);
  };
  memoCache.set(value, wrapped);
  return wrapped;
}

// 3. 避免在热路径使用装饰器
// 装饰器适用于初始化、配置、AOP 切面,不适用于高频调用

7.5 调试技巧

// 1. 打印装饰器上下文
function debug(value: any, context: any) {
  console.log('Decorator applied:', context.kind, context.name);
  console.log('Context:', context);
  console.log('Metadata:', context.metadata);
  return value;
}

// 2. 检查元数据
class MyService {
  @debug
  method() {}
}

console.log((MyService as any)[Symbol.metadata]);

// 3. 使用 source map 跟踪装饰器求值
// tsconfig.json: { "sourceMap": true }

7.6 构建工具集成

# .github/workflows/ci.yml
name: CI
on: [push, pull_request]
jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: '20'
      - run: npm ci
      - name: Type check
        run: npx tsc --noEmit
      - name: Unit tests
        run: npx vitest run

8. 案例研究

8.1 案例一:NestJS 依赖注入

NestJS 是基于装饰器的 Node.js 框架,使用 @Injectable、@Controller、@Module 等装饰器实现 IoC:

// NestJS 示例
import { Injectable, Controller, Get, Module } from '@nestjs/common';

@Injectable()
class UserService {
  private users = [{ id: 1, name: 'Alice' }];

  findAll() {
    return this.users;
  }
}

@Controller('users')
class UserController {
  constructor(private readonly userService: UserService) {}

  @Get()
  list() {
    return this.userService.findAll();
  }
}

@Module({
  controllers: [UserController],
  providers: [UserService],
})
class AppModule {}

NestJS 装饰器特点:

  • 使用 Legacy 装饰器 + reflect-metadata 实现类型注入
  • 通过 @Module 元数据组织依赖图
  • 控制器方法装饰器(@Get、@Post)注册路由

8.2 案例二:TypeORM 实体映射

TypeORM 使用装饰器将类映射到数据库表:

import { Entity, PrimaryGeneratedColumn, Column, OneToMany } from 'typeorm';

@Entity()
class User {
  @PrimaryGeneratedColumn()
  id: number;

  @Column()
  name: string;

  @Column({ unique: true })
  email: string;

  @OneToMany(() => Post, post => post.author)
  posts: Post[];
}

@Entity()
class Post {
  @PrimaryGeneratedColumn()
  id: number;

  @Column()
  title: string;

  @ManyToOne(() => User, user => user.posts)
  author: User;
}

TypeORM 通过装饰器元数据生成 SQL:

CREATE TABLE user (id INTEGER PRIMARY KEY, name VARCHAR, email VARCHAR UNIQUE);
CREATE TABLE post (id INTEGER PRIMARY KEY, title VARCHAR, authorId INTEGER REFERENCES user(id));

8.3 案例三:MikroORM 实体映射

MikroORM 是另一个基于装饰器的 ORM,使用更现代的装饰器 API:

import { Entity, PrimaryKey, Property, ManyToOne } from '@mikro-orm/core';

@Entity()
class User {
  @PrimaryKey()
  id!: number;

  @Property()
  name!: string;

  @Property({ unique: true })
  email!: string;
}

8.4 案例四:Angular 依赖注入

Angular 从 v20 开始支持 Stage 3 装饰器,取代旧的 experimentalDecorators:

import { Component, Injectable, inject } from '@angular/core';

@Component({
  selector: 'app-root',
  template: '<h1>{{ title }}</h1>',
})
class AppComponent {
  title = 'Angular App';
}

@Injectable({ providedIn: 'root' })
class UserService {
  private http = inject(HttpClient);

  getUsers() {
    return this.http.get('/api/users');
  }
}

8.5 案例五:tsoa OpenAPI 生成

tsoa 使用装饰器从 TypeScript 类生成 OpenAPI 规范:

import { Controller, Get, Route, Tags, Query } from 'tsoa';

@Route('users')
@Tags('User')
class UsersController extends Controller {
  @Get()
  public async getUsers(
    @Query() name?: string,
    @Query() limit = 10,
  ): Promise<User[]> {
    return userService.findAll({ name, limit });
  }
}

// 自动生成 openapi.json

8.6 案例六:typestack class-validator

class-validator 使用装饰器声明校验规则:

import { IsString, IsInt, Min, Max, IsEmail, validate } from 'class-validator';

class User {
  @IsString()
  name: string;

  @IsInt()
  @Min(0)
  @Max(150)
  age: number;

  @IsEmail()
  email: string;
}

const user = new User();
user.name = 'Alice';
user.age = 200;  // 不合法
user.email = 'invalid';

const errors = await validate(user);
// [{ property: 'age', constraints: { max: 'age must not be greater than 150' } },
//  { property: 'email', constraints: { isEmail: 'email must be an email' } }]

填空题知识点讲解

题目 1:Stage 3 装饰器在 TypeScript ____ 版本正式支持。

5.0

题目 2:完成以下类装饰器实现,使其打印类名:

function logged<T extends { new(...args: any[]): {} }>(value: T, context: ClassDecoratorContext) {
  return class extends value {
    constructor(...args: any[]) {
      console.log(`Creating __________`);
      super(...args);
    }
  };
}
// 答案:${String(context.name)}

题目 3:完成以下属性装饰器实现,提供默认值:

function defaultValue(defaultVal: any) {
  return function (value: undefined, context: ClassFieldDecoratorContext) {
    return function (initialValue: any) {
      return initialValue === undefined ? __________ : __________;
    };
  };
}
// 答案:defaultVal / initialValue

题目 4:装饰器组合 @A @B @C class X 等价于函数调用 ____。

A(B(C(X)))

编程题知识点讲解

题目 1:实现 @deprecated 装饰器,在方法调用时打印弃用警告。

function deprecated(message?: string) {
  return function (value: Function, context: ClassMethodDecoratorContext) {
    return function (this: any, ...args: any[]) {
      console.warn(
        `DeprecationWarning: ${String(context.name)} is deprecated` +
        (message ? `. ${message}` : '')
      );
      return value.call(this, ...args);
    };
  };
}

class UserService {
  @deprecated('Use getUserById instead')
  getUser(id: string) {
    return { id, name: 'Alice' };
  }

  getUserById(id: string) {
    return { id, name: 'Alice' };
  }
}

const service = new UserService();
service.getUser('1');
// DeprecationWarning: getUser is deprecated. Use getUserById instead

题目 2:实现 @retry 装饰器,在方法抛出异常时自动重试指定次数。

function retry(times: number) {
  return function (value: Function, context: ClassMethodDecoratorContext) {
    return async function (this: any, ...args: any[]) {
      let lastError: Error;
      for (let i = 0; i < times; i++) {
        try {
          return await value.call(this, ...args);
        } catch (err) {
          lastError = err as Error;
          console.warn(`Attempt ${i + 1}/${times} failed:`, lastError.message);
        }
      }
      throw lastError!;
    };
  };
}

class ApiService {
  @retry(3)
  async fetchData(url: string) {
    const res = await fetch(url);
    if (!res.ok) throw new Error(`HTTP ${res.status}`);
    return res.json();
  }
}

题目 3:实现 @measure 装饰器,测量方法执行时间并记录到元数据。

function measure(value: Function, context: ClassMethodDecoratorContext) {
  context.metadata.measurements ??= {};

  return async function (this: any, ...args: any[]) {
    const start = Date.now();
    try {
      return await value.call(this, ...args);
    } finally {
      const duration = Date.now() - start;
      context.metadata.measurements[context.name] = duration;
      console.log(`[${String(context.name)}] took ${duration}ms`);
    }
  };
}

class DataService {
  @measure
  async process(data: any[]) {
    return data.map(item => ({ ...item, processed: true }));
  }
}

const service = new DataService();
await service.process([1, 2, 3]);
// [process] took 5ms

console.log((DataService as any)[Symbol.metadata].measurements);
// { process: 5 }

10.1 学术论文与提案

  1. Ehrenberg, D., & Katz, Y. (2022). Decorators Proposal (Stage 3). TC39. https://github.com/tc39/proposal-decorators

  2. Bierman, G., Abadi, M., & Torgersen, M. (2014). Understanding TypeScript. ECOOP ‘14. https://doi.org/10.1007/978-3-662-44202-9_10

  3. Bracha, G., & Cook, W. (1990). Mixin-based Inheritance. OOPSLA ‘90. https://doi.org/10.1145/97946.97982

  4. Forman, I., & Danforth, S. (1999). Putting Metaclasses to Work: A New Dimension in Object-Oriented Programming. Addison-Wesley. ISBN: 978-0201433052.

10.2 官方文档与规范

  1. Microsoft. (2024). TypeScript 5.0 Release Notes: Decorators. https://devblogs.microsoft.com/typescript/announcing-typescript-5-0/#decorators

  2. Microsoft. (2024). TypeScript Handbook: Decorators. https://www.typescriptlang.org/docs/handbook/decorators.html

  3. ECMA-262. (2024). ECMAScript 2024 Language Specification: Class Definitions. https://tc39.es/ecma262/#sec-class-definitions

  4. Microsoft. (2023). TypeScript 5.0 Release Notes: Decorator Metadata. https://devblogs.microsoft.com/typescript/announcing-typescript-5-0/#decorators

10.4 教材与课程

  1. Gamma, E., Helm, R., Johnson, R., & Vlissides, J. (1994). Design Patterns: Elements of Reusable Object-Oriented Software. Addison-Wesley. ISBN: 978-0201633610.

  2. Fowler, M. (2002). Patterns of Enterprise Application Architecture. Addison-Wesley. ISBN: 978-0321127426.

  3. MIT OpenCourseWare. (2024). 6.S192: Front-End Development with TypeScript. https://ocw.mit.edu/

  4. Stanford University. (2024). CS110: Principles of Computer Systems. https://cs110.stanford.edu/


11.1 进阶主题

  • AOP(面向切面编程):装饰器是 AOP 在 TypeScript 中的实现方式,可与 Spring AOP、AspectJ 对比学习
  • 元编程:Proxy、Reflect 与装饰器的关系,理解 JavaScript 元编程全景
  • 依赖注入模式:Martin Fowler 的 Inversion of Control 文章,深入理解 IoC 容器设计
  • Mixin 与 Trait:装饰器与 Mixin 模式的对比,何时使用何种技术

11.2 相关工具

  • reflect-metadata:Legacy 装饰器的元数据 polyfill
  • tsyringe:Microsoft 出品的轻量级 DI 容器
  • inversifyjs:功能强大的 IoC 容器
  • class-validator:基于装饰器的校验库
  • class-transformer:基于装饰器的对象转换库

11.3 社区博客

11.4 视频课程

  • NestJS - The Complete Guide:Maximilian Schwarzmüller
  • TypeScript: The Complete Developer’s Guide:Stephen Grider
  • Angular - The Complete Guide:Maximilian Schwarzmüller

11.5 开源项目

11.6 相关 TC39 提案


结语

装饰器是 TypeScript 元编程的核心技术,从 Legacy 到 Stage 3 的演进体现了 TC39 在类型安全、元数据协议、与 ES2022 类字段语义对齐方面的持续努力。本教程从历史动机、形式化定义、工程实践、案例分析等多维度系统讲解 Stage 3 装饰器,旨在帮助开发者:

  1. 理解装饰器的代数语义:将装饰器视为高阶函数,掌握组合律与执行顺序
  2. 掌握 Stage 3 装饰器 API:类、方法、属性、自动访问器四种形态的精确类型签名
  3. 使用元数据协议:通过 Symbol.metadata 共享状态,实现声明式配置
  4. 避免常见陷阱:this 绑定、元数据共享、属性装饰器返回值、私有字段访问
  5. 应用于工程实践:依赖注入、AOP 切面、ORM 实体映射、路由注册

装饰器是 TypeScript 高级开发者的必备技能,但并非银弹。在简单场景中,高阶函数可能更清晰;在复杂场景中,装饰器配合元数据协议能显著提升代码表达力。希望读者通过本教程的学习,能够合理运用装饰器,构建类型安全、可维护的企业级应用。


附录:术语表

术语英文解释
装饰器Decorator修改类、方法、属性、访问器的高阶函数
Legacy 装饰器Legacy DecoratorTS 5.0 之前的装饰器实现,需 experimentalDecorators
Stage 3 装饰器Stage 3 DecoratorTC39 提案 Stage 3 的标准装饰器,TS 5.0 正式支持
上下文对象Context ObjectStage 3 装饰器的第二参数,包含 kind、name、metadata 等字段
元数据Metadata通过 Symbol.metadata 共享的类级别数据对象
描述符DescriptorLegacy 装饰器操作的 PropertyDescriptor 对象
自动访问器Auto-AccessorES2022 引入的 accessor 关键字,Stage 3 装饰器可拦截其读写
装饰器组合Decorator Composition多个装饰器叠加应用,等价于函数组合
求值顺序Evaluation Order装饰器作为函数的求值顺序,从上到下
应用顺序Application Order装饰器应用到实体的顺序,从下到上

附录:装饰器类型签名速查表

类装饰器

type ClassDecorator<T extends { new(...args: any[]): {} }> = (
  value: T,
  context: ClassDecoratorContext
) => T | void;

interface ClassDecoratorContext {
  kind: 'class';
  name: string | undefined;
  metadata: Record<string | symbol, unknown>;
  addInitializer: (initializer: () => void) => void;
}

方法装饰器

type MethodDecorator = (
  value: Function,
  context: ClassMethodDecoratorContext
) => Function | void;

interface ClassMethodDecoratorContext {
  kind: 'method';
  name: string | symbol;
  access: { get: (obj: any) => Function; set: (obj: any, value: Function) => void };
  static: boolean;
  private: boolean;
  metadata: Record<string | symbol, unknown>;
  addInitializer: (initializer: (this: any) => void) => void;
}

属性装饰器

type FieldDecorator = (
  value: undefined,
  context: ClassFieldDecoratorContext
) => ((initialValue: unknown) => unknown) | void;

interface ClassFieldDecoratorContext {
  kind: 'field';
  name: string | symbol;
  access: { get: (obj: any) => unknown; set: (obj: any, value: unknown) => void };
  static: boolean;
  private: boolean;
  metadata: Record<string | symbol, unknown>;
  addInitializer: (initializer: (this: any) => void) => void;
}

自动访问器装饰器

type AccessorDecorator = (
  value: ClassAccessorDecoratorTarget<any, any>,
  context: ClassAccessorDecoratorContext
) => ClassAccessorDecoratorResult<any, any> | void;

interface ClassAccessorDecoratorContext {
  kind: 'accessor';
  name: string | symbol;
  access: { get: (obj: any) => unknown; set: (obj: any, value: unknown) => void };
  static: boolean;
  private: boolean;
  metadata: Record<string | symbol, unknown>;
  addInitializer: (initializer: (this: any) => void) => void;
}

interface ClassAccessorDecoratorTarget<V, R> {
  get: (this: R) => V;
  set: (this: R, value: V) => void;
}

interface ClassAccessorDecoratorResult<V, R> {
  get: (this: R) => V;
  set: (this: R, value: V) => void;
  initialize?: (this: R, value: V) => V;
}

速查表(Legacy 签名为主)

本节速查表默认使用 Legacy 装饰器签名(experimentalDecorators: true,(target, key, descriptor) 三参数风格),适用于维护 Angular、NestJS、TypeORM 等存量生态代码。新项目请优先使用本节末尾”标准装饰器(TS 5.0+)“小节的两参数 (value, context) 签名——注意标准装饰器当前不含参数装饰器。

装饰器基础

基本写法:类装饰器 function <装饰器>(<target>: <构造器>) { }

// 类装饰器接收构造器返回新构造器
function Log<T extends new (...args: any[]) => any>(target: T): T {
    return class extends target { }
}
@Log class Foo {}

基本写法:方法装饰器 function <装饰器>(<target>, <key>, <descriptor>) { }

// 方法装饰器接收原型 方法名 描述符
function Log(target: any, key: string, desc: PropertyDescriptor) {
    const orig = desc.value
    desc.value = function (...args: any[]) {
        console.log(`call ${key}`)
        return orig.apply(this, args)
    }
}

基本写法:属性装饰器 function <装饰器>(<target>, <key>) { }

// 属性装饰器接收原型 与 属性名
function Meta(target: any, key: string) {
    Object.defineProperty(target, key, { value: null })
}

基本写法:参数装饰器 function <装饰器>(<target>, <key>, <index>) { }

// 参数装饰器接收原型 方法名 参数索引
function Required(target: any, key: string, index: number) {
    console.log(`param ${index} of ${key}`)
}

方法装饰器实战

基本写法:日志装饰器 function <Log>(<target>, <key>, <descriptor>)

// 记录方法调用
function Log(target: any, key: string, desc: PropertyDescriptor) {
    const orig = desc.value
    desc.value = function (...args: any[]) {
        console.log(`${key} called with`, args)
        return orig.apply(this, args)
    }
}
class Service { @Log greet(name: string) { return `hi ${name}` } }

基本写法:性能测量装饰器 function <Measure>(<target>, <key>, <descriptor>)

// 测量方法执行时间
function Measure(target: any, key: string, desc: PropertyDescriptor) {
    const orig = desc.value
    desc.value = function (...args: any[]) {
        const start = performance.now()
        const result = orig.apply(this, args)
        console.log(`${key} took ${performance.now() - start}ms`)
        return result
    }
}

基本写法:错误捕获装饰器 function <Catch>(<target>, <key>, <descriptor>)

// 统一捕获方法异常
function Catch(target: any, key: string, desc: PropertyDescriptor) {
    const orig = desc.value
    desc.value = async function (...args: any[]) {
        try { return await orig.apply(this, args) }
        catch (e) { console.error(`${key} error`, e) }
    }
}

类装饰器实战

基本写法:单例装饰器 function <Singleton><<T>>(<target>: <T>)

// 强制类为单例
function Singleton<T extends new (...args: any[]) => any>(target: T): T {
    let instance: InstanceType<T>
    return class extends target {
        constructor(...args: any[]) {
            if (instance) return instance
            super(...args)
            instance = this as any
        }
    }
}
@Singleton class Config {}

基本写法:混入装饰器 function <Mixin>(...<bases>): <类装饰器>

// 混入多个类的方法
function Mixin(...bases: any[]) {
    return function (target: any) {
        bases.forEach(base => {
            Object.getOwnPropertyNames(base.prototype).forEach(name => {
                target.prototype[name] = base.prototype[name]
            })
        })
    }
}

基本写法:标签元数据 function <Tag>(<名称>: string): <类装饰器>

// 给类附加元数据
function Tag(name: string) {
    return function <T extends new (...args: any[]) => any>(target: T): T {
        (target as any).tag = name
        return target
    }
}
@Tag("service") class Service {}

属性装饰器实战

基本写法:默认值装饰器 function <Default>(<值>): <属性装饰器>

// 为属性设置默认值
function Default(value: any) {
    return function (target: any, key: string) {
        target[key] = value
    }
}
class Config { @Default(8080) port: number }

基本写法:只读属性装饰器 function <ReadOnly>(<target>, <key>)

// 让属性只读
function ReadOnly(target: any, key: string) {
    Object.defineProperty(target, key, { writable: false })
}

装饰器工厂

基本写法:装饰器工厂 function <装饰器>(<配置>): <装饰器>

// 工厂返回实际装饰器
function Log(prefix: string) {
    return function (target: any, key: string, desc: PropertyDescriptor) {
        const orig = desc.value
        desc.value = function (...args: any[]) {
            console.log(`${prefix}: ${key}`)
            return orig.apply(this, args)
        }
    }
}
class S { @Log("APP") run() {} }

基本写法:多装饰器组合 @<A> @<B> @<C> <声明>

// 多装饰器从下往上执行
function A(target: any, key: string) { console.log("A") }
function B(target: any, key: string) { console.log("B") }
class S { @A @B method() {} }  // B A

元数据反射

基本写法:emitDecoratorMetadata import "reflect-metadata"

// 需要 tsconfig 开启 emitDecoratorMetadata
import "reflect-metadata"
function Meta(target: any, key: string) {
    const type = Reflect.getMetadata("design:type", target, key)
    console.log(type)  // String
}
class S { @Meta name: string = "" }

基本写法:自定义元数据 Reflect.defineMetadata(<键>, <值>, <目标>)

// 存储自定义元数据
function Role(role: string) {
    return function (target: any, key: string) {
        Reflect.defineMetadata("role", role, target, key)
    }
}

基本写法:读取元数据 Reflect.getMetadata(<键>, <目标>)

// 读取存储的元数据
function getRole(target: any, key: string) {
    return Reflect.getMetadata("role", target, key)
}

参数装饰器实战

基本写法:必填参数装饰器 function <Required>(<target>, <key>, <index>)

// 标记参数必填
const required: Set<number> = new Set()
function Required(target: any, key: string, index: number) {
    required.add(index)
}
class S { greet(@Required name: string) {} }

基本写法:参数注入 function <Inject>(<token>): <参数装饰器>

// 依赖注入标记
function Inject(token: string) {
    return function (target: any, key: string, index: number) {
        Reflect.defineMetadata("inject", token, target, key)
    }
}
class S { constructor(@Inject("DB") db: any) {} }

访问器装饰器

基本写法:getter setter 装饰器 function <装饰器>(<target>, <key>, <descriptor>)

// 装饰访问器属性
function Log(target: any, key: string, desc: PropertyDescriptor) {
    const orig = desc.get
    desc.get = function () {
        const v = orig?.call(this)
        console.log(`get ${key}`)
        return v
    }
}
class S { private _v = 1; @Log get v() { return this._v } }

标准装饰器(TS 5.0+ 新版)

基本写法:Stage 3 装饰器 function <装饰器>(<target>, <context>) { }

// TS 5.0 标准 TC39 装饰器
function log(target: any, context: ClassMethodDecoratorContext) {
    return function (this: any, ...args: any[]) {
        console.log(`call ${String(context.name)}`)
        return target.apply(this, args)
    }
}
class S { @log run() {} }

基本写法:新版类装饰器 function <装饰器>(<target>, <context>): <新类>

// TC39 类装饰器
function tag(target: any, context: ClassDecoratorContext) {
    return class extends target {
        tag = context.name
    }
}
@tag class S {}

基本写法:新版自动访问器 accessor <字段>

// TS 5.0 自动访问器
class S {
    accessor count = 0  // 自动生成 getter setter
}

实用模式

基本写法:缓存装饰器 function <Memoize>(<target>, <key>, <descriptor>)

// 缓存方法结果
function Memoize(target: any, key: string, desc: PropertyDescriptor) {
    const orig = desc.value
    const cache = new Map()
    desc.value = function (...args: any[]) {
        const k = JSON.stringify(args)
        if (!cache.has(k)) cache.set(k, orig.apply(this, args))
        return cache.get(k)
    }
}

基本写法:防抖装饰器 function <Debounce>(<等待>): <方法装饰器>

// 方法防抖
function Debounce(wait: number) {
    return function (target: any, key: string, desc: PropertyDescriptor) {
        const orig = desc.value
        let timer: any
        desc.value = function (...args: any[]) {
            clearTimeout(timer)
            timer = setTimeout(() => orig.apply(this, args), wait)
        }
    }
}

基本写法:节流装饰器 function <Throttle>(<等待>): <方法装饰器>

// 方法节流
function Throttle(wait: number) {
    return function (target: any, key: string, desc: PropertyDescriptor) {
        const orig = desc.value
        let last = 0
        desc.value = function (...args: any[]) {
            const now = Date.now()
            if (now - last >= wait) { last = now; return orig.apply(this, args) }
        }
    }
}

基本写法:权限校验装饰器 function <Auth>(<角色>): <方法装饰器>

// 校验调用权限
function Auth(role: string) {
    return function (target: any, key: string, desc: PropertyDescriptor) {
        const orig = desc.value
        desc.value = function (...args: any[]) {
            if (currentUser.role !== role) throw new Error("forbidden")
            return orig.apply(this, args)
        }
    }
}
class Admin { @Auth("admin") delete() {} }

基本写法:重试装饰器 function <Retry>(<次数>): <方法装饰器>

// 异步方法重试
function Retry(times: number) {
    return function (target: any, key: string, desc: PropertyDescriptor) {
        const orig = desc.value
        desc.value = async function (...args: any[]) {
            for (let i = 0; i < times; i++) {
                try { return await orig.apply(this, args) }
                catch (e) { if (i === times - 1) throw e }
            }
        }
    }
}

tsconfig 配置

基本写法:开启装饰器 "experimentalDecorators": true

// tsconfig.json 开启装饰器支持
{
    "compilerOptions": {
        "experimentalDecorators": true,
        "emitDecoratorMetadata": true
    }
}