JavaScript装饰器是一种依附于类与类成员的特殊函数,属于语言层面的元编程机制。它允许我们在定义阶段对类、方法、存取器、字段进行拦截和再加工,而不必在实例化之后手动去改原型或者重新赋值。借助这种能力,类的结构本身可以被外部逻辑按需重塑,例如自动绑定this、添加缓存、标记废弃接口等。

装饰器的基础语法与执行时机
在当前的Stage 3提案中,装饰器是紧贴在声明前面的函数调用,以@name的形式存在。它并不是在对象创建后才起作用,而是在类定义被求值的过程中同步执行,拿到的是类或者类成员的描述符与上下文。这意味着装饰器修改的是“定义期的元数据”,因此影响所有后续实例。
一个典型的类方法装饰器接收两个参数:目标对象(value)和上下文(context)。上下文里包含kind(如method)、name、以及是否静态等信息。装饰器可以返回一个新的函数替换原方法,也可以什么都不返回,仅对原描述符做副作用处理。下面给出一个最基础的日志装饰器示例:
function log(target, context) {
const methodName = context.name;
return function(...args) {
console.log('调用方法:' + methodName + ',参数:', args);
const result = target.call(this, ...args);
console.log('方法返回:', result);
return result;
};
}
class Calculator {
@log
add(a, b) {
return a + b;
}
}
const c = new Calculator();
c.add(2, 3);
上面的代码在类定义时就会把add方法替换成带有日志逻辑的新函数。所有实例调用add都会经过装饰器包装,这就体现了装饰器在元编程中“定义即增强”的特点。相比在构造函数里逐个绑定,装饰器让增强逻辑声明在字段旁边,维护成本更低。
装饰器如何修改类本身的行为
除了方法级装饰器,还存在类装饰器,它接收构造函数和上下文,可以返回一个新的类来替换原有类。这种能力让我们可以在元数据层面给类追加静态属性、混入其他行为,甚至改变实例化逻辑。比如下面这个例子,给类自动添加一个createdAt静态字段:
function withTimestamp(targetClass, context) {
return class extends targetClass {
static createdAt = new Date().toISOString();
};
}
@withTimestamp
class UserService {
getName() {
return 'user';
}
}
console.log(UserService.createdAt);
类装饰器返回的是子类,因此原类的实例方法完全保留,只是在类对象上多了一层封装。这种方式比直接修改UserService.createdAt更不容易出错,也不会污染全局定义。对于框架作者而言,类装饰器常用来实现依赖注入容器,在定义控制器类时自动将其注册到路由表中。
从元编程视角看,装饰器让我们以声明方式读写类结构。过去要修改类行为,往往需要Object.defineProperty去改原型描述符,而现在装饰器把这一过程标准化。它既保证了执行顺序的可预测性,也避免了运行时反复修补导致的隐性bug。
字段与存取器装饰器的妙用
字段装饰器可以拦截类字段的初始化,用来实现只读、默认值、或者依赖收集。存取器装饰器则能重写get与set,在属性访问时插入校验或派发通知。以下示例展示一个把字段变成只读的装饰器:
function readonly(target, context) {
context.access = {
get: function() {
return target;
},
set: function() {
throw new Error('该字段为只读');
}
};
return target;
}
class Config {
@readonly
apiUrl = 'https://ipipp.com/api';
}
const cfg = new Config();
// cfg.apiUrl = 'x'; 取消注释会报错
console.log(cfg.apiUrl);
这里通过改写上下文中的access对象,让字段在实例上只能读不能写。这种修改发生在类字段语义定义阶段,比在constructor里用Object.defineProperty更直观。元编程的价值就在于:把“怎样控制成员访问”这件事从散落的代码里抽出来,集中成可复用的装饰函数。
在实际项目中,字段装饰器常与反射元数据(如reflect-metadata库)配合,标记哪些字段需要序列化、哪些需要校验。这样业务类只管声明,交叉逻辑由装饰器统一处理,类的行为被无声地扩展却保持代码干净。
装饰器相比传统元编程方式的优势
传统修改类行为的方式包括在构造函数内赋值、修改原型、或者使用高阶函数包裹类。它们要么侵入业务代码,要么执行时机太晚,要么缺乏统一的语法提示。装饰器把增强点固定在声明处,IDE可以识别并提示类型,编译工具也能静态分析。
| 方式 | 执行时机 | 可读性 | 耦合度 |
|---|---|---|---|
| 手动改原型 | 运行时 | 低 | 高 |
| 构造函数内增强 | 实例化时 | 中 | 中 |
| 装饰器 | 定义时 | 高 | 低 |
从表中可以看出,装饰器在定义期完成结构修改,不影响每个实例的创建性能,也避免了对原型链的全局污染。对于大型应用,这种低耦合的元编程手段能显著减少隐藏依赖。
当然,装饰器也不是银弹。它要求运行环境或构建工具支持对应语法,在老旧浏览器中需要编译。并且过度使用装饰器会让类定义被多层包装,调试调用栈变深。因此建议把装饰器用在真正横切的关注点上,而不是替代普通的函数组合。
小结与落地建议
JavaScript装饰器通过定义期拦截类与成员,提供了干净、声明式的元编程通道。它既能修改方法逻辑,也能替换整个类,还能重定义字段访问规则。在开发框架、公用库时,用装饰器封装日志、鉴权、序列化等逻辑,可以让业务类只关注核心模型。
如果你准备在团队引入装饰器,优先通过TypeScript或Babel开启稳定提案支持,并约定装饰器只做无状态增强。这样既能享受元编程带来的灵活,也不会让类行为变得难以追踪。
JavaScript装饰器元编程类行为修改修改时间:2026-08-08 05:00:30