core-js 的 ECMAScript Object 补丁全景:模块清单、类型签名、入口点与实现原理
【免费下载链接】core-jsStandard Library项目地址: https://gitcode.com/GitHub_Trending/co/core-js
本文基于 core-js 仓库的官方文档 docs/web/docs/features/ecmascript/object.md,系统梳理 ECMAScript 规范中Object内建对象在 core-js 中的全部补丁模块、TypeScript 类型签名、按需导入入口点,并结合 packages/core-js/modules 下的真实源码剖析assign、groupBy、hasOwn、fromEntries、__proto__等关键方法的底层实现与兼容性修复策略。读完本文,你将能够精准定位任意Object方法的补丁文件、正确选择最小化的导入路径,并理解 core-js 如何在老引擎上还原 ES2015+ 语义。
一、Object 相关补丁模块全景
core-js 将 ES 规范中的每一个Object方法拆分为独立的模块文件,统一放在 packages/core-js/modules 目录下,命名规则为es.object.<method>.js(es.前缀表示该特性已进入正式 ECMAScript 规范)。原文档列出的完整模块清单如下:
- es.object.assign
- es.object.create
- es.object.define-getter
- es.object.define-property
- es.object.define-properties
- es.object.define-setter
- es.object.entries
- es.object.freeze
- es.object.from-entries
- es.object.get-own-property-descriptor
- es.object.get-own-property-descriptors
- es.object.get-own-property-names
- es.object.get-prototype-of
- es.object.group-by
- es.object.has-own
- es.object.is
- es.object.is-extensible
- es.object.is-frozen
- es.object.is-sealed
- es.object.keys
- es.object.lookup-getter
- es.object.lookup-setter
- es.object.prevent-extensions
- es.object.proto
- es.object.to-string
- es.object.seal
- es.object.set-prototype-of
- es.object.values
这些模块被聚合入口 packages/core-js/es/object/index.js 统一require,并最终导出path.Object(即全局Object本身)。注意该聚合入口还会一并加载es.symbol、es.json.to-string-tag、es.math.to-string-tag、es.reflect.to-string-tag等关联模块——这是因为Symbol.toStringTag是Object.prototype.toString标签机制的基础。
二、Built-ins 类型签名:一份可校验的 API 契约
原文档给出了Object补丁后完整 API 的 TypeScript 签名,这是理解 core-js 覆盖范围最直接的参照:
class Object { toString(): string; // ES2015+ fix: @@toStringTag support __defineGetter__(property: PropertyKey, getter: Function): void; __defineSetter__(property: PropertyKey, setter: Function): void; __lookupGetter__(property: PropertyKey): Function | void; __lookupSetter__(property: PropertyKey): Function | void; __proto__: Object | null; // required a way setting of prototype - will not in IE10-, it's for modern engines like Deno static assign(target: Object, ...sources: Array<Object>): Object; static create(prototype: Object | null, properties?: { [property: PropertyKey]: PropertyDescriptor }): Object; static defineProperties(object: Object, properties: { [property: PropertyKey]: PropertyDescriptor })): Object; static defineProperty(object: Object, property: PropertyKey, attributes: PropertyDescriptor): Object; static entries(object: Object): Array<[string, mixed]>; static freeze(object: any): any; static fromEntries(iterable: Iterable<[key, value]>): Object; static getOwnPropertyDescriptor(object: any, property: PropertyKey): PropertyDescriptor | void; static getOwnPropertyDescriptors(object: any): { [property: PropertyKey]: PropertyDescriptor }; static getOwnPropertyNames(object: any): Array<string>; static getPrototypeOf(object: any): Object | null; static groupBy(items: Iterable, callbackfn: (value: any, index: number) => key): { [key]: Array<mixed> }; static hasOwn(object: object, key: PropertyKey): boolean; static is(value1: any, value2: any): boolean; static isExtensible(object: any): boolean; static isFrozen(object: any): boolean; static isSealed(object: any): boolean; static keys(object: any): Array<string>; static preventExtensions(object: any): any; static seal(object: any): any; static setPrototypeOf(target: any, prototype: Object | null): any; // required __proto__ - IE11+ static values(object: any): Array<mixed>; }几个值得注意的语义细节:
toString()标注为 "ES2015+ fix: @@toStringTag support":旧引擎的原生toString不认识Symbol.toStringTag,core-js 通过 es.object.to-string 在检测到TO_STRING_TAG_SUPPORT不足时,用内部实现 internals/object-to-string 覆盖Object.prototype.toString,从而支持[object Foo]这类自定义标签输出。__proto__访问器:注释明确指出在 IE10- 中不可用,它面向的是 Deno 这类现代引擎。从源码 es.object.proto 看,其修复条件是DESCRIPTORS && getPrototypeOf && setPrototypeOf && ({}[PROTO] !== ObjectPrototype),即仅在原生缺失__proto__访问器时才通过defineBuiltInAccessor补上,且 setter 内部会先用isPossiblePrototype校验新原型是否合法。setPrototypeOf与__proto__的依赖关系:注释 "required__proto__- IE11+" 说明在 IE11 上setPrototypeOf的可用性依赖__proto__访问器被正确安装。
三、Entry Points:按需加载的入口点清单
core-js 的核心理念是"只用多少,加载多少"。原文档给出了Object相关全部入口点,路径模板为core-js(-pure)/es|stable|actual|full/object[/<method>]:
core-js(-pure)/es|stable|actual|full/object core-js(-pure)/es|stable|actual|full/object/assign core-js(-pure)/es|stable|actual|full/object/is core-js(-pure)/es|stable|actual|full/object/set-prototype-of core-js(-pure)/es|stable|actual|full/object/get-prototype-of core-js(-pure)/es|stable|actual|full/object/create core-js(-pure)/es|stable|actual|full/object/define-property core-js(-pure)/es|stable|actual|full/object/define-properties core-js(-pure)/es|stable|actual|full/object/get-own-property-descriptor core-js(-pure)/es|stable|actual|full/object/get-own-property-descriptors core-js(-pure)/es|stable|actual|full/object/group-by core-js(-pure)/es|stable|actual|full/object/has-own core-js(-pure)/es|stable|actual|full/object/keys core-js(-pure)/es|stable|actual|full/object/values core-js(-pure)/es|stable|actual|full/object/entries core-js(-pure)/es|stable|actual|full/object/get-own-property-names core-js(-pure)/es|stable|actual|full/object/freeze core-js(-pure)/es|stable|actual|full/object/from-entries core-js(-pure)/es|stable|actual|full/object/seal core-js(-pure)/es|stable|actual|full/object/prevent-extensions core-js/es|stable|actual|full/object/proto core-js(-pure)/es|stable|actual|full/object/is-frozen core-js(-pure)/es|stable|actual|full/object/is-sealed core-js(-pure)/es|stable|actual|full/object/is-extensible core-js/es|stable|actual|full/object/to-string core-js(-pure)/es|stable|actual|full/object/define-getter core-js(-pure)/es|stable|actual|full/object/define-setter core-js(-pure)/es|stable|actual|full/object/lookup-getter core-js(-pure)/es|stable|actual|full/object/lookup-setter入口点语法详解(详见官方文档 docs/web/docs/usage.md):
- 命名空间层级:
es(仅稳定 ES 特性)→stable(稳定 ES + Web 标准)→actual(稳定 + Web 标准 + stage 3 提案)→full(全部特性,含早期提案)。官方建议优先使用/actual/,因为它覆盖了所有实际可用的 JavaScript 特性,又不包含主要用于实验的不稳定早期提案。 - pure 变体:
core-js-pure不污染全局命名空间,适合库作者在依赖中安全使用;core-js是全局版本。 proto与to-string两个入口仅存在于core-js(无-pure变体):原因在于这两个模块直接修改Object.prototype,无法在不污染原型的前提下以 pure 方式提供,这与 pure 版本"原型方法转换为静态方法"的定位一致。- 实际使用示例:
import "core-js/actual/object/group-by"只加载Object.groupBy一个补丁;import "core-js/full/object"则一次性加载上文聚合入口 packages/core-js/es/object/index.js 列出的全部es.object.*模块。仓库中已生成对应的具体入口文件,例如 packages/core-js/es/object/assign.js、packages/core-js/es/object/group-by.js、packages/core-js/es/object/has-own.js。
四、源码级原理:关键方法的兼容性实现
1.Object.assign:针对 Edge 与 V8 旧版 Bug 的修复
es.object.assign 本身只是一个薄壳,真正逻辑位于 internals/object-assign.js。该文件先执行两个原生实现的缺陷检测:
- Edge 顺序 Bug:在属性 getter 动态新增不可枚举属性时,原生
assign的结果顺序错误; - V8 符号与顺序 Bug:原生实现不复制
Symbol键、或复制后属性顺序不稳定。
只有Object.assign !== assign(即检测失败)时才注入自实现。自实现的关键点:
toObject(target)保证目标必须是对象(否则抛TypeError);- 对每个 source 使用
IndexedObject包装(兼容字符串等原始值); - 键列表由
objectKeys(S)与getOwnPropertySymbols(S)拼接而成,即同时复制字符串键与符号键; - 在无
DESCRIPTORS(不支持属性描述符的引擎)时直接赋值,否则仅复制可枚举属性(propertyIsEnumerable过滤),严格对齐规范的[[OwnPropertyKeys]]+EnumerableOwnProperties语义。
2.Object.groupBy:处理原始值输入与 WebKit Bug
Object.groupBy是较新的 ES 特性,实现见 es.object.group-by。源码先做特性检测:
var DOES_NOT_WORK_WITH_PRIMITIVES = !nativeGroupBy || fails(function () { return nativeGroupBy('ab', function (it) { return it; }).a.length !== 1; });该检测针对 WebKit bug #271524——某些 WebKit 版本无法正确处理字符串等原始值输入。若检测失败则注入 polyfill,其算法要点:
requireObjectCoercible(items)+aCallable(callbackfn)做入参校验;- 结果对象用
create(null)创建(无原型对象),因此内部可安全使用key in obj判断键是否存在,规避了部分 IE 版本在整数键上hasOwnProperty行为异常的问题; iterate遍历可迭代对象,toPropertyKey将回调返回值规范化为属性键,doesNotExceedSafeInteger防止索引溢出;- 新键用
createProperty(obj, key, [value])创建数组,已有键则push追加。
3.Object.hasOwn、Object.fromEntries等薄封装
- es.object.has-own 直接复用内部工具 internals/has-own-property,本质是规范化的
Object.prototype.hasOwnProperty调用,避免对象自带hasOwnProperty被覆盖导致的误判。 - es.object.from-entries 内部通过
iterate(iterable, fn, { AS_ENTRIES: true })按[key, value]二元组迭代任意可迭代对象(Map、数组、生成器等),再用createProperty写入,保证键始终被正确处理(含符号键、数字键的字符串化)。
4.Object.prototype.__proto__:面向 Deno 的访问器补丁
es.object.proto 的源码注释特别提到:"Deno 2.9+ patch this accessor, so we can't useinfor feature detection"——即 Deno 较新版本会自行修补该访问器,因此不能用'__proto__' in {}这类方式做特性探测,必须用({})[PROTO] !== ObjectPrototype判断实际值。补丁本身通过defineBuiltInAccessor在Object.prototype上安装configurable: true的 getter/setter,getter 返回getPrototypeOf(toObject(this)),setter 在原型合法(isPossiblePrototype)且目标为对象时才执行setPrototypeOf。
5.Object.prototype.toString:@@toStringTag修复
es.object.to-string 在TO_STRING_TAG_SUPPORT为 false 时,用 internals/object-to-string 覆盖原型上的toString。该实现会检查对象的Symbol.toStringTag,输出[object <Tag>]格式,同时保持对内置类型(Array、Function、RegExp等)标签的兼容。
五、完整示例:验证补丁后的行为
以下是原文档给出的全部运行示例,可直接在加载core-js后于任意支持环境执行验证:
let foo = { q: 1, w: 2 }; let bar = { e: 3, r: 4 }; let baz = { t: 5, y: 6 }; Object.assign(foo, bar, baz); // => foo = { q: 1, w: 2, e: 3, r: 4, t: 5, y: 6 } Object.is(NaN, NaN); // => true Object.is(0, -0); // => false Object.is(42, 42); // => true Object.is(42, '42'); // => false function Parent() { /* empty */ } function Child() { /* empty */ } Object.setPrototypeOf(Child.prototype, Parent.prototype); new Child() instanceof Child; // => true new Child() instanceof Parent; // => true ({ [Symbol.toStringTag]: 'Foo', }).toString(); // => '[object Foo]' Object.keys('qwe'); // => ['0', '1', '2'] Object.getPrototypeOf('qwe') === String.prototype; // => true Object.values({ a: 1, b: 2, c: 3 }); // => [1, 2, 3] Object.entries({ a: 1, b: 2, c: 3 }); // => [['a', 1], ['b', 2], ['c', 3]] for (let [key, value] of Object.entries({ a: 1, b: 2, c: 3 })) { console.log(key); // => 'a', 'b', 'c' console.log(value); // => 1, 2, 3 } const object = { a: "1" }; Object.defineProperty(object, 'notWritable', { value: true, writable: false }); // Shallow object cloning with prototype and descriptors: Object.create(Object.getPrototypeOf(object), Object.getOwnPropertyDescriptors(object)); // => {"a": "1", "notWritable": true} const target = { a: "9" }; const source = { b: "8" }; Object.defineProperty(source, 'notWritable', { value: true, writable: false }); // Mixin: Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); // => {"a": "9", "b": "8", "notWritable": true} const map = new Map([['a', 1], ['b', 2]]); Object.fromEntries(map); // => { a: 1, b: 2 } class Unit { constructor(id) { this.id = id; } toString() { return `unit${ this.id }`; } } const units = new Set([new Unit(101), new Unit(102)]); Object.fromEntries(units.entries()); // => { unit101: Unit { id: 101 }, unit102: Unit { id: 102 } } Object.hasOwn({ foo: 42 }, 'foo'); // => true Object.hasOwn({ foo: 42 }, 'bar'); // => false Object.hasOwn({}, 'toString'); // => false Object.groupBy([1, 2, 3, 4, 5], it => it % 2); // => { 1: [1, 3, 5], 0: [2, 4] }几个示例背后的实现佐证:
Object.keys('qwe')返回['0', '1', '2']、Object.getPrototypeOf('qwe')返回String.prototype,说明 core-js 的补丁遵循规范对原始值(primitive)的ToObject装箱处理;Object.create(Object.getPrototypeOf(object), Object.getOwnPropertyDescriptors(object))与Object.defineProperties(target, Object.getOwnPropertyDescriptors(source))两个组合分别演示了"带原型与描述符的浅克隆"和"保留writable: false的 mixin",它们的正确性依赖于getOwnPropertyDescriptors补丁如实返回描述符对象;Object.groupBy的返回对象没有原型(内部create(null)),因此示例中得到的{ 1: [...], 0: [...] }是一个普通对象但不会携带Object.prototype上的键,这在处理用户数据时更安全。
六、与 Babel 生态的配合
Object补丁同样深度集成于 Babel 工具链(详见 docs/web/docs/usage.md):
@babel/polyfill本质上就是core-js/stable与regenerator-runtime的组合,现已废弃;- 使用
@babel/preset-env时设置useBuiltIns: 'entry'与corejs: '3.50'(建议精确到次版本号),Babel 会按目标环境自动把import 'core-js/es'拆解为仅必要的模块。例如针对chrome 71目标,import 'core-js/stable'可能被替换为import 'core-js/modules/es.object.from-entries'等少量模块,这正是"入口点 + 模块拆分"设计价值的体现。
七、小结
core-js 对 ECMAScriptObject的覆盖遵循一套高度模块化的工程模式:每个方法一个es.object.*.js模块(源码见 packages/core-js/modules),通过特性检测决定是否注入 polyfill;对外提供es/stable/actual/full四级命名空间与按方法细分的入口点;对内依赖 packages/core-js/internals 中的原子工具函数保证语义严格对齐规范。无论是处理assign的 Edge/V8 顺序缺陷、groupBy的 WebKit 原始值缺陷,还是__proto__在 Deno 等现代引擎上的访问器修补,其核心思路都是"优先使用原生实现、仅在检测到缺陷时注入等价实现",这也是 core-js 能长期保持可靠性的根本原因。
【免费下载链接】core-jsStandard Library项目地址: https://gitcode.com/GitHub_Trending/co/core-js
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考