Skip to main content

装饰器(decorator)

NestJS 整个系统就是围绕装饰器(decorators)来建立的!

JS 装饰器早期提案

下面这篇文章影响比较大:Exploring EcmaScript Decorators

下面是中文转写:

探索 ES7 装饰器

这篇文章由 Google Developers 在 Medium 上发布,作者是 [一位开发者],主要探讨了 JavaScript 中 ES7(实际上是 ECMAScript 2016 后的提案)装饰器(Decorators)提案。这是一个当时处于 Stage 2 的 TC39 提案(注:文章发表于 2015 年左右,当时装饰器提案经历了多次变化)。

什么是装饰器?

装饰器是一种特殊的函数,用于修改或注解类、方法、属性或参数的行为。它使用 @ 符号语法,类似于 Python 或 C# 中的装饰器。装饰器本质上是函数,在类定义时被调用,可以用来添加元数据、日志、验证等功能,而无需修改原有代码。

装饰器提案灵感来源于 AngularJS 中的注解系统,旨在为 JavaScript 提供更强大的元编程能力。

装饰器的工作原理

装饰器可以应用于:

  1. 类装饰器
    应用于整个类,接收类本身作为参数。

    示例:

    @sealed
    class Greeter {
    greeting: string;
    constructor(message: string) {
    this.greeting = message;
    }
    greet() {
    return "Hello, " + this.greeting;
    }
    }

    function sealed(target) {
    Object.seal(target);
    Object.seal(target.prototype);
    }

    这里 @sealed 装饰器封印了类及其原型,防止添加新属性。

  2. 方法装饰器
    应用于类的方法,接收三个参数:目标对象(原型)、方法名、属性描述符。

    示例:日志装饰器

    class Greeter {
    @log
    greet(message: string) {
    console.log(message);
    }
    }

    function log(target: any, key: string, descriptor: PropertyDescriptor) {
    const original = descriptor.value;
    descriptor.value = function(...args: any[]) {
    console.log(`Calling ${key} with`, args);
    return original.apply(this, args);
    };
    return descriptor;
    }
  3. 属性装饰器
    应用于类的属性,接收目标对象和属性名(没有描述符,因为属性默认是可配置的)。

    示例:可观察属性

    class Person {
    @observable
    name: string;
    }

    function observable(target: any, key: string) {
    let value = target[key];
    Object.defineProperty(target, key, {
    get() { return value; },
    set(newValue) {
    console.log(`${key} changed to ${newValue}`);
    value = newValue;
    }
    });
    }
  4. 参数装饰器
    应用于方法参数,接收目标对象、方法名和参数索引。

    示例:验证参数

    class Greeter {
    greet(@required name: string) {
    // ...
    }
    }

    function required(target: any, key: string, index: number) {
    // 在运行时或编译时记录该参数为必需
    }

装饰器的组合与工厂

装饰器可以叠加使用,从下往上执行(靠近定义的先执行)。

也可以使用装饰器工厂(返回装饰器函数)来传递参数:

@configurable(false)
class Point { }

function configurable(value: boolean) {
return function(target: any) {
Object.defineProperty(target, 'configurable', { value });
};
}

提案状态与注意事项

文章强调,当时装饰器提案处于 TC39 Stage 2,语法和语义可能还会变化(事实上,后续提案确实经历了重大修改,包括从基于描述符到基于函数的实现方式转变)。

作者提到,在生产环境中使用需谨慎,因为需要 Babel 或 TypeScript 等转译器支持,且提案不稳定可能导致未来不兼容。

潜在用例

  • 元数据注解(如 Angular 中的 @Component)
  • 混入(mixins)实现
  • 自动日志、性能监控
  • 依赖注入
  • 属性验证和响应式编程

结论

装饰器为 JavaScript 带来了更优雅的元编程方式,能显著提升代码的可读性和复用性。尽管提案还在演进中,但它展示了 JavaScript 语言未来的潜力。文章鼓励开发者实验这个特性,同时关注 TC39 的最新进展。

(注:本文基于原文内容忠实复述,未添加或删减核心信息。原文发表于装饰器提案早期阶段,后续 JavaScript 装饰器提案已更新为新的语法和行为。)

JS 装饰的最近发展(2025-12)

截至2025年12月27日,JavaScript(ECMAScript)装饰器(Decorators)提案的最新发展状况如下:

提案状态

  • 当前阶段Stage 3(候选阶段)。该提案自2022年3月进入Stage 3以来,已保持稳定,没有重大语法变化,但尚未推进到Stage 4(完成阶段),因此尚未正式纳入ECMAScript标准
  • 原生JavaScript(在浏览器或Node.js中)仍不支持装饰器,需要通过转译工具(如Babel或TypeScript)来使用。
  • 预计可能在ECMAScript 2026或更晚版本中正式落地,但没有确切时间表。提案已相对成熟,社区反馈积极,实施者在积极推进。

主要特点(当前Stage 3版本)

  • 支持装饰类、类方法、访问器(getter/setter)、字段(属性)和自动访问器(auto-accessors)。
  • 装饰器是普通函数,接收一个上下文对象(context),而不是旧版中的描述符(descriptor)。
  • 支持装饰器元数据(Decorator Metadata),这是一个独立的Stage 3提案,提供Symbol.metadata来存储和访问元数据,便于反射和框架使用。
  • 不支持参数装饰器(parameter decorators),但有一个独立的Stage 1提案在讨论中,可能未来添加。
  • 与旧版(legacy)装饰器的区别:
    • Legacy版(基于2014-2018年的旧提案)在Angular、NestJS等框架中广泛使用,但与当前提案不完全兼容。
    • 新版更注重性能、可静态分析性和与类字段的兼容性,许多旧模式需要调整。

工具支持情况

  • Babel:完全支持Stage 3装饰器(推荐使用"version": "2023-11"或最新配置),并保留legacy模式以兼容旧项目。Babel 8将只支持新版和legacy。
  • TypeScript
    • TypeScript 5.0(2023年)开始,默认支持Stage 3装饰器(无需experimentalDecorators标志)。
    • 旧的experimental/legacy装饰器仍可通过experimentalDecorators: true启用,但推荐迁移到新版。
    • TypeScript 6.0及更高版本进一步优化了装饰器和元数据支持,使其更适合大规模应用。
  • 其他工具(如esbuild、SWC)也在逐步添加实验支持。

实际应用建议

  • 新项目:直接使用Stage 3新版装饰器,更未来兼容。
  • 旧项目(如Angular):继续使用legacy模式,但建议规划迁移路径(许多装饰器库已提供双模式支持)。
  • 常见用例:日志、验证、依赖注入、响应式编程、ORM映射等。框架如MobX、Lit、NestJS已适配或正在适配新版。

总体而言,装饰器提案已非常接近标准化,Stage 3的实现稳定可靠,许多开发者已在生产环境中通过转译器使用。如果你正在使用装饰器,关注TC39仓库(https://github.com/tc39/proposal-decorators)的更新是最准确的来源。