装饰器(decorator)
NestJS 整个系统就是围绕装饰器(decorators)来建立的!
JS 装饰器早期提案
下面这篇文章影响比较大:Exploring EcmaScript Decorators
下面是中文转写:
探索 ES7 装饰器
这篇文章由 Google Developers 在 Medium 上发布,作者是 [一位开发者],主要探讨了 JavaScript 中 ES7(实际上是 ECMAScript 2016 后的提案)装饰器(Decorators)提案。这是一个当时处于 Stage 2 的 TC39 提案(注:文章发表于 2015 年左右,当时装饰器提案经历了多次变化)。
什么是装饰器?
装饰器是一种特殊的函数,用于修改或注解类、方法、属性或参数的行为。它使用 @ 符号语法,类似于 Python 或 C# 中的装饰器。装饰器本质上是函数,在类定义时被调用,可以用来添加元数据、日志、验证等功能,而无需修改原有代码。
装饰器提案灵感来源于 AngularJS 中的注解系统,旨在为 JavaScript 提供更强大的元编程能力。
装饰器的工作原理
装饰器可以应用于:
-
类装饰器
应用于整个类,接收类本身作为参数。示例:
@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装饰器封印了类及其原型,防止添加新属性。 -
方法装饰器
应用于类的方法,接收三个参数:目标对象(原型)、方法名、属性描述符。示例:日志装饰器
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;
} -
属性装饰器
应用于类的属性,接收目标对象和属性名(没有描述符,因为属性默认是可配置的)。示例:可观察属性
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;
}
});
} -
参数装饰器
应用于方法参数,接收目标对象、方法名和参数索引。示例:验证参数
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及更高版本进一步优化了装饰器和元数据支持,使其更适合大规模应用。
- 从TypeScript 5.0(2023年)开始,默认支持Stage 3装饰器(无需
- 其他工具(如esbuild、SWC)也在逐步添加实验支持。
实际应用建议
- 新项目:直接使用Stage 3新版装饰器,更未来兼容。
- 旧项目(如Angular):继续使用legacy模式,但建议规划迁移路径(许多装饰器库已提供双模式支持)。
- 常见用例:日志、验证、依赖注入、响应式编程、ORM映射等。框架如MobX、Lit、NestJS已适配或正在适配新版。
总体而言,装饰器提案已非常接近标准化,Stage 3的实现稳定可靠,许多开发者已在生产环境中通过转译器使用。如果你正在使用装饰器,关注TC39仓库(https://github.com/tc39/proposal-decorators)的更新是最准确的来源。