装饰器(Decorators)是 JavaScript 中最受期待的语言特性之一。它允许开发者以声明式的方式修改类、方法、属性等的行为,为元编程提供了强大的表达能力。从最早的 TypeScript 实验性实现,到如今 TC39 提案进入 Stage 3,装饰器经历了漫长而曲折的标准化过程。本文将带你梳理装饰器提案从 Stage 2 到 Stage 3 的关键语法演进,帮助你理解最终形态的设计思路。
什么是装饰器?
装饰器本质上是一种特殊的函数,它可以附加到类、方法、访问器、字段或参数上,用来观察、修改或替换被装饰的目标。在 TypeScript 和 Angular 等框架中,装饰器早已被广泛使用,但那些实现基于早期的 Stage 2 提案,与最终的 Stage 3 标准存在显著差异。
一个典型的装饰器用法如下:
@logged
class MyClass {
@readonly
name = 'example';
}
这种写法简洁直观,但背后的语义在提案演进中发生了根本性变化。
Stage 2 时代的装饰器
Stage 2 提案(大约 2018 年前后)是 TypeScript 实验性装饰器和 Babel legacy 模式所依据的版本。它的核心特点包括:
- 装饰器接收的描述符对象:对于方法装饰器,参数是
target、propertyKey和descriptor,开发者可以直接修改descriptor来改变方法行为。 - 支持参数装饰器:可以装饰构造函数或方法的参数,这在依赖注入场景中非常有用。
- 装饰器表达式可以是任意表达式:包括成员访问、函数调用等。
- 执行时机与顺序:装饰器在类定义时立即执行,多个装饰器按从下到上、从内到外的顺序应用。
Stage 2 的装饰器功能强大,但存在一些难以解决的语义问题。例如,装饰器可以任意修改描述符,导致引擎难以进行静态优化;参数装饰器的语义与类字段的初始化顺序存在冲突;此外,装饰器与 [[OwnPropertyKeys]] 等内部方法的交互也不够清晰。
这些问题使得委员会决定重新设计装饰器,而不是在原有基础上修补。
Stage 3 的重新设计
经过多次讨论,装饰器提案在 2022 年正式进入 Stage 3。新的设计在保留声明式风格的同时,对语义进行了大幅简化与规范化。主要变化如下:
1. 装饰器不再接收描述符
在 Stage 3 中,方法装饰器接收的参数变为 value 和 context。value 是被装饰的方法本身,context 是一个包含 kind、name、access、static、private 等元信息的对象。
function logged(value, context) {
if (context.kind === 'method') {
return function (...args) {
console.log(`调用 ${context.name}`);
return value.apply(this, args);
};
}
}
装饰器通过返回值来替换被装饰的目标,而不是直接修改描述符。这种“纯函数”风格让引擎更容易优化,也让装饰器的行为更加可预测。
2. 取消参数装饰器
Stage 3 移除了参数装饰器。委员会认为参数装饰器的使用场景可以通过其他方式实现,且其语义与类字段初始化顺序的冲突难以调和。如果你需要类似依赖注入的功能,可以通过类装饰器或方法装饰器配合元数据来实现。
3. 新增类自动访问器(Accessor)
Stage 3 引入了一个新的关键字 accessor,用于声明自动访问器字段。装饰器可以作用于 accessor,从而拦截 getter 和 setter 的行为。
class C {
@logged
accessor x = 1;
}
这为响应式编程和状态管理提供了标准化的钩子。
4. 装饰器表达式的限制
Stage 3 规定装饰器表达式必须是以下形式之一:
- 标识符(如
@logged) - 成员访问(如
@obj.logged) - 函数调用(如
@logged('info')) - 括号表达式
不允许任意表达式,例如 @(a + b) 或 @foo().bar。这一限制简化了语法解析,也避免了副作用难以追踪的问题。
5. 执行顺序的明确
Stage 3 明确定义了装饰器的求值顺序和应用顺序:
- 对于类,装饰器表达式按从上到下、从左到右的顺序求值。
- 装饰器函数按从下到上、从右到左的顺序应用。
- 类装饰器在所有成员装饰器之后应用。
这种顺序与 Stage 2 类似,但更加精确,减少了实现歧义。
从 Stage 2 迁移到 Stage 3
对于已经使用 TypeScript 或 Babel legacy 装饰器的项目,迁移到 Stage 3 需要一些调整:
- 方法装饰器不再接收
descriptor,需要改为返回新方法。 - 参数装饰器不再可用,需寻找替代方案。
- 属性装饰器不再能直接修改初始化行为,需改用
accessor或类字段装饰器。 - 装饰器表达式需符合新的语法限制。
TypeScript 5.0 已经支持 Stage 3 装饰器,但需要开启 experimentalDecorators 以外的配置。Babel 也提供了相应的插件。值得注意的是,Stage 3 装饰器与 legacy 装饰器不能混用,迁移时需要逐步替换。
总结
装饰器提案从 Stage 2 到 Stage 3 的演进,是一次从“灵活但模糊”到“规范且可优化”的转变。新的设计虽然牺牲了部分灵活性(如参数装饰器),但换来了更清晰的语义、更好的引擎优化潜力以及更广泛的跨平台一致性。随着 TypeScript 5.0 和现代构建工具的跟进,Stage 3 装饰器正在成为 JavaScript 元编程的标准方式。
对于开发者而言,理解这些变化不仅有助于迁移现有代码,更能让你在未来的框架和库设计中充分利用装饰器的能力。装饰器的故事还在继续,Stage 3 之后还有 Stage 4 的最终定稿,但方向已经明确:更简单、更可靠、更标准。
未经允许不得转载:任鹏个人博客 » JavaScript 装饰器提案详解:从 Stage 2 到 Stage 3 的语法演进

