首页 > web前端 > js教程 > 正文

js如何获取原型链上的装饰器方法

星降
发布: 2025-08-18 12:01:01
原创
344人浏览过

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

js如何获取原型链上的装饰器方法

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

Reflect.metadata
登录后复制
API 来实现。

js如何获取原型链上的装饰器方法

解决方案

要获取原型链上的装饰器方法所关联的信息,最标准且推荐的做法是让装饰器在执行时将相关数据作为元数据附加到目标对象上。随后,你可以利用

Reflect.getMetadata
登录后复制
API 来检索这些信息。

以下是一个实际的例子,展示了如何创建一个简单的装饰器来附加元数据,以及如何从原型链上的方法中读取它:

js如何获取原型链上的装饰器方法
// 确保 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实现,它们本质上都是一种元编程工具,在你的代码被编译或加载到运行时环境之前,它们就已经完成了对代码结构的修改。你可以把它想象成一个建筑师:他在设计图纸阶段(代码定义)就决定了房间的布局、窗户的样式(方法的行为、属性的特性)。一旦房子建好了(代码运行),你看到的只是最终的房子,而不是建筑师本人或他当时绘制图纸的过程。

js如何获取原型链上的装饰器方法

所以,当我们谈论“获取装饰器方法”时,我们不是要去抓取那个在定义时执行过的函数引用,因为那个函数已经完成了它的任务。我们真正关心的是,这个“建筑师”在构建过程中留下了什么“标记”或“说明”,这些标记就是元数据。它们是装饰器行为的结果,而不是装饰器本身。这和我们平时使用

Object.defineProperty
登录后复制
类似,你定义了一个属性后,并不能反过来查询是哪个
defineProperty
登录后复制
调用创建了它。装饰器提供了更优雅的语法糖,但底层逻辑依然是属性描述符的操作。

如何在实践中使用 Reflect.metadata 来管理装饰器信息?

在实际项目中,

Reflect.metadata
登录后复制
的应用场景远不止于简单的日志。它提供了一个强大且标准化的机制来为你的类、方法、属性附加任意的上下文信息。

例如,你可以用它来实现:

如此AI员工
如此AI员工

国内首个全链路营销获客AI Agent

如此AI员工 71
查看详情 如此AI员工
  1. 权限控制: 定义
    @Roles('admin')
    登录后复制
    装饰器,将所需角色信息附加到方法上。在HTTP请求处理时,通过
    Reflect.getMetadata('roles', target, methodName)
    登录后复制
    获取这些角色信息,然后进行权限校验。
  2. API文档生成: 创建
    @ApiDescription('获取用户列表')
    登录后复制
    @ApiResponse(UserDto)
    登录后复制
    等装饰器,将这些信息附加到控制器方法上。在启动时扫描所有控制器,提取这些元数据,自动生成OpenAPI(Swagger)文档。
  3. 依赖注入: 框架可以利用
    @Inject('someService')
    登录后复制
    装饰器来标记构造函数参数或属性,然后通过反射读取这些元数据,自动解析并注入相应的服务实例。
  4. 序列化/反序列化: 定义
    @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中文网其它相关文章!

最佳 Windows 性能的顶级免费优化软件
最佳 Windows 性能的顶级免费优化软件

每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。

下载
来源:php中文网
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn
最新问题
开源免费商场系统广告
热门教程
更多>
最新下载
更多>
网站特效
网站源码
网站素材
前端模板
关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新 English
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送
PHP中文网APP
随时随地碎片化学习

Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号