JavaScript装饰器是Stage 3提案,需Babel或TS编译支持;本质是接收目标、名称、描述符并返回新描述符的运行时高阶函数;类装饰器改构造函数,方法装饰器须显式返回修改后的descriptor。

JavaScript 的装饰器目前仍是 Stage 3 提案,未被正式纳入语言标准,class 和方法上的 @decorator 语法在原生环境中默认不可用——必须依赖 Babel(@babel/plugin-proposal-decorators)或 TypeScript 编译支持。
装饰器本质是函数,接收目标、名称、描述符并返回新描述符
装饰器不是语法糖,而是运行时可执行的高阶函数。类装饰器接收 target(构造函数),方法装饰器接收 target(原型)、name(方法名)、descriptor(属性描述符)。你修改 descriptor.value 就等于替换了原始方法。
常见错误:直接在装饰器里写 return function() {...} 却忘了重新赋值给 descriptor.value,导致装饰无效。
- 方法装饰器必须显式返回修改后的
descriptor,否则行为不变 - 类装饰器可返回新构造函数,但需谨慎处理
instanceof和原型链 - Babel 默认使用
legacy: true模式,此时descriptor是“旧式”对象(无initializer),TypeScript 则按提案最新语义处理
用 @log 装饰方法:捕获调用、参数与返回值
这是最典型的入门场景。注意不能只靠 console.log,要保留原方法的 this 绑定和返回值,否则会破坏调用链。
立即学习“Java免费学习笔记(深入)”;
function log(target, name, descriptor) {
const original = descriptor.value;
descriptor.value = function(...args) {
console.log(`[LOG] ${name} called with`, args);
const result = original.apply(this, args);
console.log(`[LOG] ${name} returned`, result);
return result;
};
return descriptor;
}
class Calculator {
@log
add(a, b) {
return a + b;
}
}
关键点:
-
original.apply(this, args)保证this正确指向实例 - 必须返回
result,否则所有被装饰方法都返回undefined - 若方法是异步的,需单独处理
async和Promise返回值
@readonly 和 @configurable:操作属性描述符的底层控制
这些装饰器直接改写 descriptor 的布尔标志,属于元编程中最基础也最容易出错的操作。
典型陷阱:Object.defineProperty 在严格模式下对不可配置属性重复设置会抛 TypeError;装饰器链中多个装饰器同时修改同一描述符,顺序会影响最终结果。
-
@readonly应设descriptor.writable = false,且通常配合configurable: false防止后续覆盖 -
@nonenumerable设descriptor.enumerable = false,避免该方法出现在for...in或Object.keys()中 - 不要在装饰器里调用
Object.defineProperty(target, name, descriptor)—— 这是 Babel/TS 编译器的工作,手动调用会导致重复定义报错
TypeScript 中装饰器与类型检查的脱节问题
TypeScript 编译器仅校验装饰器调用语法,不校验装饰器内部逻辑是否真能改变类型行为。比如你写了 @deprecated,TS 不会阻止你继续调用该方法,也不会自动推导返回值类型变化。
更隐蔽的问题:装饰器返回新函数后,原方法的 JSDoc 注释、参数类型、this 类型注解不会自动继承。你需要手动在装饰器内部用 as any 或泛型透传类型,或借助 ReturnType / Parameters 工具类型增强返回签名。
真正难的从来不是写一个装饰器,而是让装饰后的类仍能通过严格类型检查、被 IDE 正确跳转、在 bundle 分析中保持可读性——这些细节在快速原型阶段常被忽略,上线后才暴露。











