你无法直接获取装饰器函数本身,因为装饰器在定义时执行并修改目标,运行时只能通过元数据获取其留下的信息。1. 装饰器的作用是修改类或方法的描述符,并在执行时将元数据附加到目标上;2. 使用 reflect.definemetadata 在装饰器中存储信息,如日志消息或权限角色;3. 通过 reflect.getmetadata 从原型链上的方法获取元数据,即使子类继承未覆盖的方法,也能访问父类方法的元数据;4. 若子类重写并重新装饰同名方法,则该方法拥有独立的元数据,原父类元数据需通过父类原型访问;5. 实际应用包括权限控制、api文档生成、依赖注入等,框架可通过读取元数据实现声明式配置。最终结论是:装饰器本身不可获取,但其产生的元数据可通过 reflect api 在原型链上有效读取和利用。

在JavaScript中,如果你想“获取”原型链上的装饰器方法,直接的答案是:你无法在运行时直接“获取”到应用在方法上的装饰器函数本身。装饰器是在代码定义阶段执行的,它们的作用是修改类的定义、方法描述符或属性描述符。一旦它们执行完毕,它们就“消失”了,留下的只是它们对目标所做的修改。因此,真正要获取的是装饰器所留下的信息,也就是元数据(metadata)。这通常通过
Reflect.metadata

要获取原型链上的装饰器方法所关联的信息,最标准且推荐的做法是让装饰器在执行时将相关数据作为元数据附加到目标对象上。随后,你可以利用
Reflect.getMetadata
以下是一个实际的例子,展示了如何创建一个简单的装饰器来附加元数据,以及如何从原型链上的方法中读取它:

// 确保 Reflect-metadata polyfill 已导入,例如:
// import 'reflect-metadata';
/**
* 一个简单的日志装饰器,同时附加元数据
* @param {string} message - 要记录的消息
*/
function LogAndMetadata(message: string) {
return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
// 原始方法
const originalMethod = descriptor.value;
// 附加元数据到方法上
Reflect.defineMetadata('logMessage', message, target, propertyKey);
Reflect.defineMetadata('decoratedBy', 'LogAndMetadata', target, propertyKey);
// 修改方法以添加日志功能
descriptor.value = function (...args: any[]) {
console.log(`[${message}] Calling ${propertyKey} with:`, args);
return originalMethod.apply(this, args);
};
return descriptor;
};
}
class BaseService {
@LogAndMetadata('Service Method Call')
public doSomething(data: string) {
console.log(`Executing doSomething with: ${data}`);
return `Processed: ${data}`;
}
}
class ExtendedService extends BaseService {
// 继承了 doSomething 方法,没有覆盖
@LogAndMetadata('Extended Service Init')
public initialize() {
console.log('ExtendedService initialized.');
}
}
// 尝试获取元数据
const baseServiceInstance = new BaseService();
baseServiceInstance.doSomething('test'); // 触发日志
const extendedServiceInstance = new ExtendedService();
extendedServiceInstance.doSomething('another test'); // 触发继承方法的日志
console.log('\n--- 检查元数据 ---');
// 从 BaseService 的原型上获取 doSomething 的元数据
const baseLogMessage = Reflect.getMetadata('logMessage', BaseService.prototype, 'doSomething');
const baseDecoratorName = Reflect.getMetadata('decoratedBy', BaseService.prototype, 'doSomething');
console.log(`BaseService.doSomething - logMessage: ${baseLogMessage}, decoratedBy: ${baseDecoratorName}`);
// 从 ExtendedService 的原型上获取 doSomething 的元数据
// 即使 ExtendedService 没有直接装饰 doSomething,它也继承了带有元数据的方法
const extendedLogMessage = Reflect.getMetadata('logMessage', ExtendedService.prototype, 'doSomething');
const extendedDecoratorName = Reflect.getMetadata('decoratedBy', ExtendedService.prototype, 'doSomething');
console.log(`ExtendedService.doSomething - logMessage: ${extendedLogMessage}, decoratedBy: ${extendedDecoratorName}`);
// 获取 ExtendedService 自己的 initialize 方法的元数据
const initLogMessage = Reflect.getMetadata('logMessage', ExtendedService.prototype, 'initialize');
console.log(`ExtendedService.initialize - logMessage: ${initLogMessage}`);
// 尝试获取一个不存在的元数据
const nonExistentMetadata = Reflect.getMetadata('nonExistent', BaseService.prototype, 'doSomething');
console.log(`Non-existent metadata: ${nonExistentMetadata}`); // 应该为 undefined这段代码清晰地展示了,当
ExtendedService
BaseService
doSomething
BaseService.prototype
ExtendedService.prototype
doSomething
Reflect.getMetadata
在我看来,这是一个关于“运行时”与“编译/定义时”边界的问题。装饰器,无论是TypeScript还是Babel实现,它们本质上都是一种元编程工具,在你的代码被编译或加载到运行时环境之前,它们就已经完成了对代码结构的修改。你可以把它想象成一个建筑师:他在设计图纸阶段(代码定义)就决定了房间的布局、窗户的样式(方法的行为、属性的特性)。一旦房子建好了(代码运行),你看到的只是最终的房子,而不是建筑师本人或他当时绘制图纸的过程。

所以,当我们谈论“获取装饰器方法”时,我们不是要去抓取那个在定义时执行过的函数引用,因为那个函数已经完成了它的任务。我们真正关心的是,这个“建筑师”在构建过程中留下了什么“标记”或“说明”,这些标记就是元数据。它们是装饰器行为的结果,而不是装饰器本身。这和我们平时使用
Object.defineProperty
defineProperty
在实际项目中,
Reflect.metadata
例如,你可以用它来实现:
@Roles('admin')Reflect.getMetadata('roles', target, methodName)@ApiDescription('获取用户列表')@ApiResponse(UserDto)
@Inject('someService')@Serializable()
@JsonField('custom_name')一个更具体的实践场景是,当你构建一个框架或库时,你希望用户能够通过声明式的方式(即使用装饰器)来配置某些行为。这时,你的框架内部就需要一套机制来读取这些配置。
Reflect.metadata
// 假设你有一个简单的路由框架
const ROUTE_METADATA_KEY = Symbol('route_path');
const METHOD_METADATA_KEY = Symbol('http_method');
function Get(path: string) {
return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
Reflect.defineMetadata(ROUTE_METADATA_KEY, path, target, propertyKey);
Reflect.defineMetadata(METHOD_METADATA_KEY, 'GET', target, propertyKey);
// 可以在这里进行其他处理,例如验证路径格式
};
}
class UserController {
@Get('/users')
public getAllUsers() {
return 'List of all users';
}
@Get('/users/:id')
public getUserById(id: string) {
return `User with ID: ${id}`;
}
}
// 框架启动时,扫描控制器并注册路由
function registerRoutes(controller: any) {
const prototype = controller.prototype;
const methods = Object.getOwnPropertyNames(prototype).filter(name => name !== 'constructor');
console.log(`\n--- 注册 ${controller.name} 的路由 ---`);
for (const methodName of methods) {
const routePath = Reflect.getMetadata(ROUTE_METADATA_KEY, prototype, methodName);
const httpMethod = Reflect.getMetadata(METHOD_METADATA_KEY, prototype, methodName);
if (routePath && httpMethod) {
console.log(`注册路由: [${httpMethod}] ${routePath} -> ${controller.name}.${methodName}`);
// 实际框架中,这里会调用 express/koa 等框架的路由注册方法
}
}
}
registerRoutes(UserController);通过这种方式,你的框架代码完全不必关心装饰器是如何实现的,它只需要知道如何通过
Reflect.getMetadata
理解装饰器元数据在继承链上的行为,关键在于理解JavaScript的原型链本身。当一个方法被装饰时,无论是在类定义还是方法定义上,元数据都是附加到该方法所在的原型对象上的。
例如,如果你有一个
BaseClass
doWork
BaseClass.prototype.doWork
当
SubClass extends BaseClass
SubClass
doWork
SubClass.prototype.doWork
BaseClass.prototype.doWork
Reflect.getMetadata('someKey', SubClass.prototype, 'doWork')BaseClass.prototype.doWork
这正是我们前面示例中
ExtendedService
doSomething
但如果
SubClass
doWork
class BaseService {
@LogAndMetadata('Base Service Method Call')
public doSomething(data: string) { /* ... */ }
}
class OverridingService extends BaseService {
@LogAndMetadata('Overriding Service Method Call') // 这里覆盖并重新装饰了
public doSomething(data: string) {
console.log(`OverridingService is doing something with: ${data}`);
// super.doSomething(data); // 可以选择调用父类方法
return `Overridden: ${data}`;
}
}
// 获取 OverridingService.prototype.doSomething 的元数据
const overridingLogMessage = Reflect.getMetadata('logMessage', OverridingService.prototype, 'doSomething');
console.log(`\nOverridingService.doSomething - logMessage: ${overridingLogMessage}`);
// 此时会输出 'Overriding Service Method Call',而不是 'Base Service Method Call'在这种情况下,
OverridingService.prototype.doSomething
BaseService.prototype.doSomething
理解这一行为对于设计可扩展的、基于装饰器的系统至关重要。它确保了继承的灵活性,同时允许子类通过覆盖和重新装饰来定义自己的行为和元数据。
以上就是js如何获取原型链上的装饰器方法的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号