在JavaScript中并不存在原生的注解语法,通常我们所说的JS注解其实是借助装饰器(Decorator)特性来实现的,装饰器本质是一个函数,用于在类、方法、属性等定义阶段对其添加额外的行为或修改其结构,而元数据则是附加在这些目标上的描述信息,用于后续的逻辑处理。

JS装饰器的基础实现
装饰器目前处于Stage 3提案阶段,在较新版本的Node.js或者配置babel插件后可以使用,基础装饰器函数接收三个参数:目标对象、属性名、属性描述符,不同类型的装饰器参数略有差异。
类装饰器示例
类装饰器接收的是类的构造函数,我们可以在这里修改类的结构或者附加信息:
// 简单的类装饰器,给类添加一个静态属性
function classDecorator(target) {
target.isDecorated = true;
return target;
}
@classDecorator
class User {
constructor(name) {
this.name = name;
}
}
console.log(User.isDecorated); // 输出 true
方法装饰器示例
方法装饰器接收目标对象、方法名、属性描述符三个参数,可以修改方法的执行逻辑:
// 方法装饰器,给方法执行前后添加日志
function logMethod(target, propertyKey, descriptor) {
const originalMethod = descriptor.value;
descriptor.value = function(...args) {
console.log(`调用方法 ${propertyKey},参数:`, args);
const result = originalMethod.apply(this, args);
console.log(`方法 ${propertyKey} 执行完成,结果:`, result);
return result;
};
return descriptor;
}
class MathUtils {
@logMethod
add(a, b) {
return a + b;
}
}
const utils = new MathUtils();
utils.add(1, 2);
// 输出:
// 调用方法 add,参数: [1, 2]
// 方法 add 执行完成,结果: 3
元数据与reflect_metadata库
装饰器本身只能修改目标结构,如果我们需要给目标附加自定义的描述信息(也就是元数据),就需要借助reflect_metadata这个库,它提供了元数据的存储和读取能力,基于Reflect API扩展实现。
安装与基础使用
首先安装依赖:
npm install reflect-metadata
然后在代码入口引入该库,就可以使用相关的API了:
import 'reflect-metadata';
// 定义元数据的key
const METADATA_KEY = 'custom:metadata';
// 给对象设置元数据
const obj = {};
Reflect.defineMetadata(METADATA_KEY, { desc: '这是一个测试对象' }, obj);
// 读取元数据
const metadata = Reflect.getMetadata(METADATA_KEY, obj);
console.log(metadata); // 输出 { desc: '这是一个测试对象' }
装饰器中添加和使用元数据
我们可以把装饰器和元数据结合,在装饰器执行阶段给目标附加元数据,后续在需要的地方读取这些元数据完成逻辑处理。
自定义元数据装饰器
下面实现一个可以给类添加描述信息的装饰器:
import 'reflect-metadata';
const CLASS_METADATA_KEY = 'class:description';
// 接收描述信息作为参数的装饰器
function setClassDescription(desc) {
return function(target) {
// 给类附加元数据,target是类的构造函数
Reflect.defineMetadata(CLASS_METADATA_KEY, desc, target);
return target;
};
}
// 读取类元数据的工具函数
function getClassDescription(target) {
return Reflect.getMetadata(CLASS_METADATA_KEY, target);
}
// 使用装饰器
@setClassDescription('用户实体类,包含用户基础信息')
class User {
constructor(name, age) {
this.name = name;
this.age = age;
}
}
// 读取元数据
const desc = getClassDescription(User);
console.log(desc); // 输出 用户实体类,包含用户基础信息
方法元数据装饰器示例
给方法添加元数据,比如标记方法是否需要权限校验:
import 'reflect-metadata';
const METHOD_METADATA_KEY = 'method:auth';
// 权限装饰器,标记方法需要的权限
function requireAuth(role) {
return function(target, propertyKey, descriptor) {
// 给方法附加元数据,target是类的原型对象,propertyKey是方法名
Reflect.defineMetadata(METHOD_METADATA_KEY, role, target, propertyKey);
return descriptor;
};
}
// 读取方法元数据的工具函数
function getMethodAuth(target, propertyKey) {
return Reflect.getMetadata(METHOD_METADATA_KEY, target, propertyKey);
}
class AdminController {
@requireAuth('admin')
deleteUser() {
console.log('删除用户');
}
@requireAuth('user')
getUserInfo() {
console.log('获取用户信息');
}
}
// 读取方法的权限元数据
const deleteAuth = getMethodAuth(AdminController.prototype, 'deleteUser');
const getAuth = getMethodAuth(AdminController.prototype, 'getUserInfo');
console.log(deleteAuth); // 输出 admin
console.log(getAuth); // 输出 user
不同装饰器的元数据处理差异
不同类型的装饰器,元数据的附加目标有所不同,整理如下:
| 装饰器类型 | 目标对象 | 元数据附加方式 |
|---|---|---|
| 类装饰器 | 类的构造函数 | Reflect.defineMetadata(key, value, target) |
| 方法装饰器 | 类的原型对象、方法名 | Reflect.defineMetadata(key, value, target, propertyKey) |
| 属性装饰器 | 类的原型对象、属性名 | Reflect.defineMetadata(key, value, target, propertyKey) |
| 参数装饰器 | 类的原型对象、方法名、参数索引 | 可以结合数组存储多个参数的元数据 |
注意事项
- 使用
reflect_metadata前必须确保已经引入该库,否则Reflect上不会扩展相关方法。 - 元数据的key建议使用唯一的字符串或者Symbol,避免不同模块之间的元数据冲突。
- 装饰器仅在类定义阶段执行一次,元数据也会在定义时就被附加,运行时读取即可。
- 如果使用TypeScript,可以开启
experimentalDecorators和emitDecoratorMetadata配置,自动生成部分类型相关的元数据。
实际应用场景
装饰器加元数据的组合在很多框架中都有应用,比如TypeORM中通过装饰器给实体类、字段附加数据库映射的元数据,路由框架中通过装饰器标记接口的URL、请求方法等信息,这些场景都依赖于装饰器收集元数据,后续在程序启动时统一处理这些元数据完成初始化逻辑。
JS装饰器注解实现元数据reflect_metadata装饰器原理修改时间:2026-06-26 21:21:20