core-js 中的Object.hasOwn:从"可访问的 hasOwnProperty"提案到 ES2022 标准实现
【免费下载链接】core-jsStandard Library项目地址: https://gitcode.com/GitHub_Trending/co/core-js
本文以 core-js 文档中 AccessibleObject.prototype.hasOwnProperty页面为核心线索,系统讲解Object.hasOwn静态方法的提案动机、标准语义、在 core-js 中的模块化实现与分层入口,并结合仓库源码与单元测试给出可直接运行的接入示例。读完本文,你将掌握Object.hasOwn与hasOwnProperty、in的本质差异,知道在 core-js 中按需引入该特性的准确入口,并能从源码层面理解其 polyfill 的兜底逻辑与边界行为。
提案背景:为什么需要一个"可访问"的hasOwnProperty
长久以来,判断一个对象是否"自身拥有"某个属性,最可靠的写法是借用原型上的方法:
Object.prototype.hasOwnProperty.call(obj, 'key');这个写法虽然正确,但存在三个实际痛点:
- 冗长且易错:每次都要写
Object.prototype.hasOwnProperty.call,拼写长、容易把prototype写错或漏掉.call; - 原型链污染风险:如果直接调用
obj.hasOwnProperty(key),一旦obj上恰好定义了hasOwnProperty属性(或被第三方库覆盖),就会调用到错误实现甚至抛出TypeError; - 对
null/undefined不安全:Object.prototype.hasOwnProperty.call(null, key)在严格模式下会抛错,行为不统一。
为此,TC39 提出了 AccessibleObject.prototype.hasOwnProperty提案,核心思路是提供一个挂载在Object构造函数上的静态方法Object.hasOwn,从而彻底绕开原型调用路径。该提案已合入 ECMAScript 标准,Object.hasOwn成为 ES2022(对应 ECMA-262 第 13 版)的正式内置方法。
标准签名与语义
根据关联文档中给出的签名,Object.hasOwn是Object上的一个静态方法:
class Object { static hasOwn(object: object, key: PropertyKey): boolean; }其语义要点:
Object.hasOwn(object, key)返回布尔值,表示key是否为object的自身属性(own property),不沿原型链查找;- 与实例方法不同,它不要求
object是对象:当object为null或undefined时,直接抛出TypeError,而不会先尝试把参数隐式转换为对象再继续(这是所谓的"现代行为",下文测试部分会验证); key可以是字符串或 Symbol 等PropertyKey,内部与HasOwnProperty抽象操作一致。
与相关 API 的对比
| API | 查找范围 | 对 null/undefined 的行为 | 是否需要借助.call |
|---|---|---|---|
Object.hasOwn(obj, key) | 仅自身属性 | 抛TypeError(不做隐式转换) | 否 |
Object.prototype.hasOwnProperty.call(obj, key) | 仅自身属性 | 在严格模式下抛错 / 行为不统一 | 是 |
obj.hasOwnProperty(key) | 仅自身属性 | 不安全:可被实例属性遮蔽 | 否,但有污染风险 |
key in obj | 自身属性 + 原型链 | 不抛错,但语义是"可访问"而非"拥有" | 否 |
core-js 中的模块实现
入口模块es.object.has-own
Object.hasOwn属于 ES 稳定特性,core-js 将其实现为独立模块 packages/core-js/modules/es.object.has-own.js:
'use strict'; var $ = require('../internals/export'); var hasOwn = require('../internals/has-own-property'); // `Object.hasOwn` method // https://tc39.es/ecma262/#sec-object.hasown $({ target: 'Object', stat: true }, { hasOwn: hasOwn });它通过 core-js 内部的export工具,以target: 'Object', stat: true的方式把hasOwn挂载为Object的静态方法。
底层兜底实现internals/has-own-property.js
真正的实现位于 packages/core-js/internals/has-own-property.js:
'use strict'; var uncurryThis = require('../internals/function-uncurry-this'); var toObject = require('../internals/to-object'); var hasOwnProperty = uncurryThis({}.hasOwnProperty); // `HasOwnProperty` abstract operation // https://tc39.es/ecma262/#sec-hasownproperty module.exports = Object.hasOwn || function hasOwn(it, key) { return hasOwnProperty(toObject(it), key); };这里体现了 core-js 的标准做法——原生优先,缺失才兜底:
uncurryThis({}.hasOwnProperty)先把原生hasOwnProperty提取为可直接调用的函数(核心实现采用同样的uncurryThis技巧,参见 packages/core-js/internals/function-uncurry-this.js);- 若运行环境已原生支持
Object.hasOwn,则直接返回原生方法,不重复注入; - 否则退回 polyfill:先经
toObject(it)把非对象参数做 ToObject 转换(对null/undefined抛TypeError),再调用解绑后的hasOwnProperty完成判断。
值得注意:internals/has-own-property.js中该内部工具本身也被其他模块复用(如集合类、元数据相关实现),说明Object.hasOwn底层逻辑同时承担着 core-js 内部大量"判断自身属性"的公共职责。
分层入口与引用方式
按命名空间按需引入
关联文档指明该特性的 entry point 为:
core-js/proposals/accessible-object-hasownproperty对应文件 packages/core-js/proposals/accessible-object-hasownproperty.js,其内部仅一行:
require('../modules/esnext.object.has-own');由于该特性已标准化,core-js 提供了从"纯 ES"到"全量"的完整入口体系(详见 docs/web/docs/usage.md),你可以按需选择:
// 仅稳定 ES 特性(推荐按需引入) import "core-js/es/object/has-own"; // 稳定 + Web 标准 + stage 3 提案 import "core-js/actual/object/has-own"; // 包含早期提案的全量入口 import "core-js/full/object/has-own"; // 一次性引入某命名空间下的全部特性 import "core-js/es/object"; // 使用 core-js-pure,避免污染全局命名空间 import "core-js-pure/es/object/has-own";其中:
- packages/core-js/es/object/has-own.js 等 "es" 层入口直接 require 对应模块;
es/object/index.js中已显式包含require('../../modules/es.object.has-own')(见 packages/core-js/es/object/index.js);- packages/core-js/full/object/has-own.js 会在实际入口之外再
require('../../modules/esnext.object.has-own'),并带有TODO: Remove from core-js@4注释——因为它在 core-js 4 中将被移除。
esnext.object.has-own:提案期的兼容别名
packages/core-js/modules/esnext.object.has-own.js 是提案期的遗留入口,现在仅作为兼容别名存在,内容同样是转发到标准模块:
'use strict'; // TODO: Remove from `core-js@4` require('../modules/es.object.has-own');也就是说:当前仓库版本下,无论走proposals/accessible-object-hasownproperty还是esnext.object.has-own,最终注入的始终是标准的es.object.has-own模块。从源码结构可以推断,这是 core-js 在特性从 stage 3 提案晋级为标准后保留的一层平滑过渡,帮助早期按提案名引入的用户无痛升级。
单元测试:行为契约的完整验证
core-js 为Object.hasOwn提供了专门的 QUnit 测试 tests/unit-global/es.object.has-own.js,可作为该 API 的"行为契约"文档:
QUnit.test('Object.hasOwn', assert => { const { create, hasOwn } = Object; assert.isFunction(hasOwn); assert.arity(hasOwn, 2); assert.name(hasOwn, 'hasOwn'); assert.looksNative(hasOwn); assert.nonEnumerable(Object, 'hasOwn'); assert.true(hasOwn({ q: 42 }, 'q')); assert.false(hasOwn({ q: 42 }, 'w')); assert.false(hasOwn(create({ q: 42 }), 'q')); assert.true(hasOwn(Object.prototype, 'hasOwnProperty')); let called = false; try { hasOwn(null, { toString() { called = true; } }); } catch { /* empty */ } assert.false(called, 'modern behaviour'); assert.throws(() => hasOwn(null, 'foo'), TypeError, 'throws on null'); assert.throws(() => hasOwn(undefined, 'foo'), TypeError, 'throws on undefined'); });测试断言覆盖了核心契约:
- 形态:是函数、参数长度为 2、名为
hasOwn、looksNative(不被误判为第三方注入)、在Object上不可枚举; - 语义:自身属性返回
true;不存在的键返回false;Object.create({ q: 42 })的原型链属性返回false(不沿原型链查找);Object.prototype自身拥有hasOwnProperty因此返回true; - 边界(modern behaviour):传入
null时不会先对第二个参数做属性键转换(自定义toString未被调用),而是直接抛TypeError——这正是Object.hasOwn与旧式借用写法最大的行为差异,也印证了 polyfill 中"先toObject再判断"的顺序。
实战建议
- 新代码统一使用
Object.hasOwn,替代Object.prototype.hasOwnProperty.call(...)与obj.hasOwnProperty(key)两种旧写法,彻底规避原型污染问题; - 按需引入:如果你的构建目标环境较老(如 IE11 或旧版 Node),引入
core-js/es/object/has-own(或core-js/stable中的对应入口)即可获得与原生一致的实现,polyfill 会优先使用引擎原生方法,性能与行为均无损失; - 关注命名空间差异:
/es/仅覆盖稳定标准特性;若项目同时依赖早期提案,推荐用文档建议的/actual/命名空间(包含全部正式特性与 stage 3 提案);full与esnext别名入口保留提案期命名,在 core-js 4 中会被移除,不应作为长期依赖; - 测试即文档:接入后可直接参考 tests/unit-global/es.object.has-own.js 中的断言,用相同用例校验你的运行环境行为,尤其是对
null/undefined抛TypeError且不触发隐式转换的"现代行为"。
【免费下载链接】core-jsStandard Library项目地址: https://gitcode.com/GitHub_Trending/co/core-js
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考