Object.create(null)与设置__proto__为null的{}是否存在功能与性能差异?
Object.create(null) vs. {} with proto set to null: Differences & Hash Table Use Cases
这是个非常实用的问题——尤其是当你想把对象当作纯哈希表(键值对存储)来用的时候,我之前在项目里踩过相关的坑,现在来详细拆解:
核心区别:创建过程与初始状态
表面上看,两种方式最终都会得到一个[[Prototype]]为null的对象,但它们的创建逻辑和潜在行为有明显差异:
1. Object.create(null) 的特性
- 直接创建一个完全无原型链的对象,它不会继承任何
Object.prototype上的方法(比如toString、hasOwnProperty、__proto__访问器)。 - 从诞生起就是“干净”的,没有任何预置属性或隐藏的访问器,引擎可以直接针对这种无原型对象做特定优化。
2. let obj = {}; obj.__proto__ = null; 的特性
- 第一步创建的
{}是一个正常继承Object.prototype的对象,拥有所有原型链上的方法和__proto__访问器。 - 第二步设置
__proto__ = null本质上是调用Object.setPrototypeOf(obj, null),动态修改对象的原型。这个操作会打破引擎对该对象的初始优化(比如隐藏类绑定),因为引擎原本认为这个对象的原型是固定的Object.prototype。
用作哈希表时:功能与性能差异
如果把对象当作哈希表使用,两种方式的差异会被放大,尤其是在键名操作和性能上:
功能差异:__proto__ 键的处理
这是最容易踩坑的点:
- 对于
Object.create(null)创建的对象,obj['__proto__'] = 'value'会正常添加一个名为__proto__的普通键值对——因为对象没有原型,不存在__proto__访问器,所以这个键名和其他普通键完全一样。 - 对于先创建
{}再修改__proto__的对象,如果在修改原型之前尝试添加__proto__键:
这种情况下,你完全无法将let obj = {}; obj['__proto__'] = 'my hash key'; // 这里根本不是添加属性!而是触发Object.prototype上的__proto__访问器,把obj的原型设为字符串'my hash key' obj.__proto__ = null; // 现在原型被重置为null,但之前的'__proto__'键根本没被添加__proto__作为哈希表的键使用,除非先把原型设为null再添加键,但这又回到了性能问题。
性能差异:引擎优化与原型修改开销
Object.create(null)更高效:引擎在创建时就知道这个对象没有原型,会直接应用针对无原型对象的优化(比如更快的属性查找、更少的原型链检查),后续操作的性能更稳定。- 修改
__proto__的开销:动态修改对象原型是一个昂贵的操作——JavaScript引擎依赖对象的原型和结构来生成隐藏类(优化属性访问速度),修改原型会导致引擎废弃之前生成的隐藏类,后续操作需要重新计算,整体性能会下降。 - 遍历效率:当用
for...in遍历哈希表时,Object.create(null)的对象不需要额外调用hasOwnProperty来过滤继承属性(因为根本没有继承属性),而修改过原型的对象虽然最终原型是null,但引擎可能仍会保留一些原型检查的逻辑,遍历速度略慢。
总结
如果你需要一个纯哈希表(只用来存储键值对,不需要继承任何Object方法),优先使用Object.create(null):
- 它避免了
__proto__键名的意外行为,功能更安全。 - 引擎优化更充分,性能更稳定。
而先创建{}再修改__proto__的方式,除非是在特定的兼容场景下(非常少见),否则没有理由优先选择——既存在功能隐患,又有性能损耗。
内容的提问来源于stack exchange,提问作者Sawyer Bergeron
相关产品推荐
相关产品推荐

