JavaScript Reflect Metadata 实现原理与使用指南

作者:拾光时间:2026-10-24 03:04:30浏览:309

本文深入解析 JavaScript 中的 Reflect Metadata 规范,介绍如何通过装饰器为类和方法添加自定义元数据。详细说明了 metadata、target、property 等核心概念,对比了 getMetadata 与 getOwnMetadata 的区别,并揭示了其基于 WeakMap 的内部存储机制。

引言

引言

ES6 规范中已包含 Reflect API,其主要功能是将变量操作函数化,即实现反射机制。关于该 API 的详细内容,可参考相关教程。

本文重点讨论 Reflect 中尚未正式纳入规范的部分,即 Reflect Metadata。

Metadata

对于熟悉 Java 或 C# 的开发者而言,Metadata(元数据)并不陌生。最典型的应用是通过反射获取类属性上的注解(在 JavaScript 中对应装饰器),从而更优雅地控制代码逻辑。

JavaScript 目前支持装饰器,尽管仍处于 Stage2 阶段。现有的装饰器主要用于对函数或属性进行操作,例如修改值、代理变量或自动绑定 this 等。

因此,下文将使用 TypeScript 进行讲解,因为 TypeScript 已完整实现了装饰器功能。

虽然 Babel 也能实现,但需要复杂的配置,出于便捷考虑,本文不采用此方案。

然而,现有装饰器无法通过反射获取添加到类或方法上的所有装饰器列表。

为此,Reflect Metadata 应运而生。

Reflect Metadata 核心概念

Reflect Metadata

简单来说,Reflect Metadata 允许你通过装饰器向类添加自定义信息,并通过反射提取这些信息;同样也可以通过反射添加这些信息。示例如下:


@Reflect.metadata('name', 'A')
class A {
 @Reflect.metadata('hello', 'world')
 public hello(): string {
  return 'hello world'
 }
}

Reflect.getMetadata('name', A) // 'A'
Reflect.getMetadata('hello', new A()) // 'world'
// 这里为什么要用 new A(),用 A 不行么?后文会讲到

看起来非常简单,下面进行简要介绍。

概念

首先需要区分四个核心概念:

  1. Metadata Key {Any},后文简写为 k。这是元数据的键,一个对象可以拥有多个元数据,每个元数据对应一个 Key。例如,可以设置名为 'name' 的 Key 来存储名字,或用 'created time' 的 Key 表示创建时间。Key 可以是任意类型,其内部本质是一个 Map 对象。
  2. Metadata Value {Any},后文简写为 v。这是元数据的值,可以是任意类型。
  3. Target {Object},后文简写为 o。表示要在哪个对象上添加元数据。
  4. Property {String|Symbol},后文简写为 p。用于指定在哪个属性上添加元数据。虽然可以在对象层面添加元数据,但也可以在对象的特定属性上添加。当仅指定对象时,相当于默认将 Property 设为 undefined。

理解上述概念后,前文的示例便一目了然。

安装与使用

安装/使用

接下来介绍如何开始使用 Reflect Metadata。

首先,需要安装 reflect-metadata polyfill,引入后 Reflect 对象下会出现多个关于 Metadata 的函数。由于该规范尚未正式进入协议,因此需要安装垫片(polyfill)才能使用。

Reflect 只是一个全局变量。

无需担心垫片的质量,因为 Angular 等框架也在广泛使用。

随后,即可在 TypeScript 中运行前文提到的示例代码。

类/属性/方法 装饰器与查找规则

类/属性/方法 装饰器

请看以下示例。


@Reflect.metadata('name', 'A')
class A {
 @Reflect.metadata('name', 'hello')
 hello() {}
}

const objs = [A, new A, A.prototype]
const res = objs.map(obj => [
 Reflect.getMetadata('name', obj),
 Reflect.getMetadata('name', obj, 'hello'),
 Reflect.getOwnMetadata('name', obj),
 Reflect.getOwnMetadata('name', obj ,'hello')
])
// 大家猜测一下 res 的值会是多少?

思考一下,给你 10 秒钟。

10 9 8 7 6 5 4 3 2 1

res


[
 ['A', undefined, 'A', undefined],
 [undefined, 'hello', undefined, undefined],
 [undefined, 'hello', undefined, 'hello'],
]

下面解释为何会产生这样的结果。

所有对类的修饰都定义在类对象本身,而所有对类属性或方法的修饰都定义在类的原型上,并以属性或方法的 key 作为 property。这就是 getMetadata 产生上述效果的原因。

那么,带有 'Own' 前缀的函数又是什么情况呢?

这需要从元数据的查找规则讲起。

原型链查找

类似于类的继承,元数据的查找也是通过原型链进行的。

如前例所示,即使实例化了一个 new A(),依然可以找到其原型链上的元数据。

举例说明。


class A {
 @Reflect.metadata('name', 'hello')
 hello() {}
}

const t1 = new A()
const t2 = new A()
Reflect.defineMetadata('otherName', 'world', t2, 'hello')
Reflect.getMetadata('name', t1, 'hello') // 'hello'
Reflect.getMetadata('name', t2, 'hello') // 'hello'
Reflect.getMetadata('otherName', t2, 'hello') // 'world'

Reflect.getOwnMetadata('name', t2, 'hello') // undefined
Reflect.getOwnMetadata('otherName', t2, 'hello') // 'world'

主要用途

用途

所有用途的核心目的都是给对象添加额外信息,且不改变对象结构。这一点至关重要:添加元数据后,对象不会新增 property,也不会修改现有 property。但由此可以衍生出多种其他用途。

  • Angular 中对特殊字段进行修饰(如 Input),从而提升代码可读性。
  • 使装饰器具备真正‘装饰’对象而不改变对象的能力,赋予对象更多语义功能。

API 概览

API


namespace Reflect {
 // 用于装饰器
 metadata(k, v): (target, property?) => void
 
 // 在对象上面定义元数据
 defineMetadata(k, v, o, p?): void
 
 // 是否存在元数据
 hasMetadata(k, o, p?): boolean
 hasOwnMetadata(k, o, p?): boolean
 
 // 获取元数据
 getMetadata(k, o, p?): any
 getOwnMetadata(k, o, p?): any
 
 // 获取所有元数据的 Key
 getMetadataKeys(o, p?): any[]
 getOwnMetadataKeys(o, p?): any[]
 
 // 删除元数据
 deleteMetadata(k, o, p?): boolean
}

注意,部分操作有带 'Own' 的函数,这是因为某些操作可通过原型链进行,后文将详细讲解。

实现原理与源码分析

深入 Reflect Metadata

实现原理

查阅官方文档可知,所有元数据都存在于对象下的 [[Metadata]] 属性中。起初我也认为新建一个 Symbol('Metadata'),并将元数据放入该 Symbol 对应的 Property 中即可。但查看源码后发现并非如此,请看示例:


@Reflect.metadata('name', 'A')
class A {}

Object.getOwnPropertySymbols(A) // []

实际上并不存在所谓的 Symbol,那么这些元数据究竟存储在哪里?

它们存储在内部的一个 WeakMap 中。正是利用了 WeakMap 不增加引用计数的特性,将对象作为 Key,元数据集合作为 Value,存入 WeakMap 中。

若深入探究,会发现其内部数据结构如下:


WeakMap<any, Map<any, Map<any, any>>>

虽然结构看似复杂,但从调用角度思考,逻辑并不复杂。


weakMap.get(o).get(p).get(k)

先根据对象获取,再根据属性,最后根据元数据的 Key 获取最终数据。

End

由于 Reflect Metadata 相对简单,此处不再赘述。更多内容请查阅 Spec。

题外话与总结

题外话

阅读源码后令人惊讶的是,许多 polyfill 要求提供前置 polyfill 后才能使用,但 reflect-metadata 内部自行实现了包括 Map、Set、WeakMap、UUID 在内的多种 polyfill 和算法。最令人惊讶的是 WeakMap 的实现,若不仔细查阅,可能会误以为它仍会增加引用计数。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持本站。

作者最新文章
相关文章
更多