core-js 中 ECMAScript Reflect 全面解析:模块清单、内置方法签名与多入口使用指南
【免费下载链接】core-jsStandard Library项目地址: https://gitcode.com/GitHub_Trending/co/core-js
本篇指南以 docs/web/docs/features/ecmascript/reflect.md 为骨架,系统梳理 core-js 对 ECMAScriptReflect内置对象的 polyfill 实现:涵盖 14 个独立模块的完整清单、TypeScript 签名、es | stable | actual | full四套入口路径的用法,并深入源码剖析每个方法的底层实现与引擎 bug 修复逻辑。读完本文,你将能够按需精确引入任意Reflect方法,理解 core-js 如何在不支持的运行环境中忠实还原Reflect语义,并能用Reflect.ownKeys、Reflect.construct等写出可运行、可验证的实战代码。
一、为什么 core-js 需要 polyfill Reflect
Reflect是 ES2015(ES6)引入的全局内置对象,提供了与Object上对应静态方法一一映射的反射语义操作,但它并非构造函数,不能new。两者最直观的区别在于返回值约定:Object.defineProperty失败时抛异常,而Reflect.defineProperty返回false;delete运算符在严格模式下对不可配置属性会抛错,而Reflect.deleteProperty返回false。此外Reflect还提供Object没有的方法(如Reflect.construct、Reflect.ownKeys、Reflect.apply)。
core-js 的定位是"标准库 polyfill",它通过 packages/core-js/modules/ 目录下一系列es.reflect.*.js模块,在旧引擎(尤其是 MS Edge 旧版本、旧版 Firefox 等)缺失或不完整实现Reflect时逐方法补齐。每个模块都通过internals/export以{ target: 'Reflect', stat: true }的方式把方法挂到Reflect上,并针对已知引擎 bug 做forced(强制覆盖)与sham(模拟实现)标记——这一点在下面的源码剖析中会反复出现。
二、Modules:14 个 Reflect 模块完整清单
原文档列出 core-js 为Reflect提供的全部模块,每个模块独立成文件、可单独引入,文件均位于 packages/core-js/modules/:
| 方法 | 对应模块文件 | 作用 |
|---|---|---|
Reflect.apply | es.reflect.apply.js | 以指定this与参数列表调用函数 |
Reflect.construct | es.reflect.construct.js | 以参数数组调用构造函数 |
Reflect.defineProperty | es.reflect.define-property.js | 定义属性,失败返回false |
Reflect.deleteProperty | es.reflect.delete-property.js | 删除属性,不可配置返回false |
Reflect.get | es.reflect.get.js | 读取属性值(可走原型链与 getter) |
Reflect.getOwnPropertyDescriptor | es.reflect.get-own-property-descriptor.js | 获取自有属性描述符 |
Reflect.getPrototypeOf | es.reflect.get-prototype-of.js | 获取原型 |
Reflect.has | es.reflect.has.js | 等价于in运算符 |
Reflect.isExtensible | es.reflect.is-extensible.js | 判断对象是否可扩展 |
Reflect.ownKeys | es.reflect.own-keys.js | 返回自有键(含 Symbol) |
Reflect.preventExtensions | es.reflect.prevent-extensions.js | 阻止扩展 |
Reflect.set | es.reflect.set.js | 设置属性值,失败返回false |
Reflect.setPrototypeOf | es.reflect.set-prototype-of.js | 设置原型,失败返回false |
此外,入口聚合文件 es/reflect/index.js 还会额外引入 es.reflect.to-string-tag.js 与es.object.to-string,保证Reflect[Symbol.toStringTag] === 'Reflect'(Object.prototype.toString.call(Reflect)输出[object Reflect]),这是规范对Reflect对象的额外要求。
三、Built-ins signatures:完整方法签名与逐项说明
原文档给出了Reflect命名空间的完整 TypeScript 签名,逐方法继承如下:
namespace Reflect { apply(target: Function, thisArgument: any, argumentsList: Array<mixed>): any; construct(target: Function, argumentsList: Array<mixed>, newTarget?: Function): Object; defineProperty(target: Object, propertyKey: PropertyKey, attributes: PropertyDescriptor): boolean; deleteProperty(target: Object, propertyKey: PropertyKey): boolean; get(target: Object, propertyKey: PropertyKey, receiver?: any): any; getOwnPropertyDescriptor(target: Object, propertyKey: PropertyKey): PropertyDescriptor | void; getPrototypeOf(target: Object): Object | null; has(target: Object, propertyKey: PropertyKey): boolean; isExtensible(target: Object): boolean; ownKeys(target: Object): Array<string | symbol>; preventExtensions(target: Object): boolean; set(target: Object, propertyKey: PropertyKey, V: any, receiver?: any): boolean; setPrototypeOf(target: Object, proto: Object | null): boolean; // required __proto__ - IE11+ }逐项说明参数与返回值约定:
Reflect.apply(target, thisArgument, argumentsList):等价于Function.prototype.apply,但argumentsList必须为类数组对象(源码中用anObject强制校验,见 es.reflect.apply.js)。Reflect.construct(target, argumentsList, newTarget?):等价于new target(...argumentsList);第三参数可改变new.target,返回新实例对象。Reflect.defineProperty/Reflect.deleteProperty/Reflect.set/Reflect.setPrototypeOf/Reflect.preventExtensions:失败不抛异常,返回false。Reflect.get与Reflect.set的receiver参数:可指定 getter/setter 执行时的this,实现代理语义。Reflect.ownKeys:返回的数组同时包含字符串键与 Symbol 键,且返回类型为Array<string | symbol>。Reflect.setPrototypeOf的注释required __proto__ - IE11+:表明在 IE11 及更新版本中要求引擎支持__proto__才能正确 polyfill 该语义(源码中该模块仅在objectSetPrototypeOf存在时才定义,见 es.reflect.set-prototype-of.js)。
四、源码级剖析:core-js 如何还原 Reflect 语义
4.1Reflect.ownKeys:一行委托 + 内置键收集
es.reflect.own-keys.js 非常简洁,直接委托给internals/own-keys工具:它内部合并Object.getOwnPropertyNames与Object.getOwnPropertySymbols的结果,从而同时覆盖字符串键与 Symbol 键,这正是Reflect.ownKeys区别于Object.keys(仅返回可枚举字符串键)的核心。
4.2Reflect.apply:检测 MS Edge 的省略参数 bug
es.reflect.apply.js 用fails检测旧版 MS Edge 允许省略argumentsList参数的偏差行为(OPTIONAL_ARGUMENTS_LIST),一旦检测到就forced强制覆盖为规范实现:先aCallable(target)校验可调用性,再anObject(argumentsList)校验参数列表,最终调用functionApply完成调用。
4.3Reflect.construct:两大引擎 bug 与零参数优化
es.reflect.construct.js 是模块中最复杂的实现,它检测并修复了两类已知问题:
NEW_TARGET_BUG(Firefox Nightly):原生实现接受第三个参数newTarget,但不会依据它创建this,导致nativeConstruct(fn, [], F) instanceof F为假。ARGS_BUG:某些引擎调用Reflect.construct(fn)(省略参数列表)会失败。
当存在上述任一 bug 时,模块被标记为forced且sham,使用自定义实现。在Target === newTarget的常见路径下,代码对 0~4 个参数做了new Target(...)的展开优化;参数更多时则通过Function.prototype.bind拼接参数后new。当newTarget被改写时,则从newTarget.prototype创建原型对象、以该对象为this调用Target,并处理返回值是否为对象的两种情况。这解释了为什么Reflect.construct(C, [20, 22])可以构造出instance.c === 42的实例(见下文示例)。
4.4Reflect.get/Reflect.set:原型链递归与 receiver 语义
es.reflect.get.js 的内部函数$get是递归实现:先查自有属性描述符,数据描述符直接返回值,访问器描述符则以receiver为this调用 getter;若属性不存在则沿getPrototypeOf向上递归,直到原型为null。当target === receiver时走快速路径target[propertyKey]。
es.reflect.set.js 的$set则完整实现了规范写入算法:处理只读数据属性返回false、在receiver上执行defineProperty、调用原型链上的 setter,并通过MS_EDGE_BUG检测修复了 MS Edge 17-18 中"允许向原型只读属性所在对象写入"的错误。
4.5Reflect.defineProperty/Reflect.deleteProperty/Reflect.has
- es.reflect.define-property.js 用
ERROR_INSTEAD_OF_FALSE检测 MS Edge 抛异常而非返回false的偏差,且会先做ToPropertyDescriptor校验(getter/setter 必须是可调用或undefined),非法描述符直接抛TypeError——这与规范"属性描述符错误应抛异常、定义失败才返回 false"的边界一致。 - es.reflect.delete-property.js 先取属性描述符,若
configurable === false返回false,否则执行delete target[key]——这正是"严格模式下delete抛错、Reflect.deleteProperty返回 false"的实现来源。 - es.reflect.has.js 整个实现就是
propertyKey in target,与in运算符等价。
4.6 其余模块与sham标记
getOwnPropertyDescriptor、getPrototypeOf分别依赖internals/descriptors与internals/correct-prototype-getter的能力检测,在不支持属性描述符或原生原型 getter 的环境下标记sham(即该实现只是近似模拟)。is-extensible、prevent-extensions、set-prototype-of均以各自 internals 工具为基底,setPrototypeOf在引擎不支持时直接不定义该属性。这些forced/sham标记正是 core-js 在 packages/core-js/modules/ 中统一采用的"先能力检测、后决定是否覆盖"的 polyfill 策略。
五、Entry points:四套入口与单方法精确引入
原文档给出了Reflect的完整入口路径模板(详见文档的 Entry points 章节):
core-js(-pure)/es|stable|actual|full/reflect core-js(-pure)/es|stable|actual|full/reflect/apply core-js(-pure)/es|stable|actual|full/reflect/construct core-js(-pure)/es|stable|actual|full/reflect/define-property core-js(-pure)/es|stable|actual|full/reflect/delete-property core-js(-pure)/es|stable|actual|full/reflect/get core-js(-pure)/es|stable|actual|full/reflect/get-own-property-descriptor core-js(-pure)/es|stable|actual|full/reflect/get-prototype-of core-js(-pure)/es|stable|actual|full/reflect/has core-js(-pure)/es|stable|actual|full/reflect/is-extensible core-js(-pure)/es|stable|actual|full/reflect/own-keys core-js(-pure)/es|stable|actual|full/reflect/prevent-extensions core-js(-pure)/es|stable|actual|full/reflect/set core-js(-pure)/es|stable|actual|full/reflect/set-prototype-of使用要点:
es/stable/actual/full四个层级分别对应"仅标准 ES 特性""稳定特性(不含提案)""已按最新语义更新的当前版本""完整(含提案特性)"。Reflect属于已定稿的 ES2015 特性,四套入口均可用,按项目对提案特性的开放程度选择即可。仓库中对应的聚合入口文件见 packages/core-js/es/reflect/index.js、packages/core-js/stable/reflect/、packages/core-js/actual/reflect/、packages/core-js/full/reflect/目录。core-js与core-js-pure:前者直接修改全局Reflect,后者通过core-js-pure包返回独立副本,不污染全局(packages/core-js-pure/override/ 存放对应的覆写模块)。- 单方法入口:如
core-js/es/reflect/own-keys只引入ownKeys一个方法,避免全量引入带来的体积开销,适合按需加载。
典型用法:
// 引入全部 Reflect 方法(写入全局) import 'core-js/es/reflect'; // 或只引入需要的单方法 import 'core-js/es/reflect/own-keys'; import 'core-js/es/reflect/construct'; // 使用 core-js-pure,不污染全局 const { Reflect: PureReflect } = require('core-js-pure/es/reflect');六、Examples:可运行的实战示例
原文档给出了两个核心示例,这里完整继承并补充验证:
let object = { a: 1 }; Object.defineProperty(object, 'b', { value: 2 }); object[Symbol('c')] = 3; Reflect.ownKeys(object); // => ['a', 'b', Symbol(c)] function C(a, b) { this.c = a + b; } let instance = Reflect.construct(C, [20, 22]); instance.c; // => 42补充的实战场景:
// 1) Reflect.apply:以指定 this 调用,参数列表必须是类数组 Reflect.apply(String.prototype.replace, 'abc', ['b', 'x']); // => 'axc' // 2) Reflect.set 失败时返回 false(严格模式 delete 会抛错,这里不会) const frozen = Object.freeze({ a: 1 }); Reflect.set(frozen, 'a', 2); // => false Reflect.deleteProperty(frozen, 'a'); // => false // 3) 带 receiver 的 getter 调用 const target = { get x() { return this.y; } }; Reflect.get(target, 'x', { y: 42 }); // => 42 // 4) has 等价于 in Reflect.has({ a: 1 }, 'a'); // => true // 5) construct 的 newTarget 参数 function Parent() { this.type = 'parent'; } function Child() {} const obj = Reflect.construct(Parent, [], Child); obj instanceof Parent; // => false obj instanceof Child; // => true这些语义在仓库的单元测试中均有覆盖,例如 tests/unit-global/es.reflect.own-keys.js、tests/unit-global/es.reflect.construct.js、tests/unit-global/es.reflect.set.js 等,读者可在tests/unit-global/目录下找到全部 14 个对应测试文件,作为行为验证与回归依据。
七、小结
core-js 对Reflect的 polyfill 是"逐方法模块化 + 引擎 bug 检测 + 规范语义还原"三者的结合:14 个es.reflect.*.js模块(另加to-string-tag补齐标签)均可独立引入;es | stable | actual | full与core-js(-pure)的组合提供了从全局注入到无污染副本的灵活加载方式;而fails检测驱动的forced/sham机制(如Reflect.construct的NEW_TARGET_BUG、Reflect.apply的 MS Edge 省略参数问题)确保了在旧引擎上也能获得与规范一致的行为。按需引入core-js/es/reflect/xxx单方法入口,即可在保持体积可控的同时获得可靠的反射语义支持。
【免费下载链接】core-jsStandard Library项目地址: https://gitcode.com/GitHub_Trending/co/core-js
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考