在JavaScript的编程实践中,判断两个值的“相等”关系看似简单,实则隐藏着不少令人困惑的细节。开发者们熟知的严格相等运算符 === 虽然足够可靠,却存在两个广为人知的“小脾气”:它无法区分 +0 和 -0,并且认为 NaN 不等于自身。为了解决这些边界场景,ES6 正式引入了 Object.is() 方法,专门修复了这两个反直觉的行为。

然而,这里有一个关键误区需要澄清:Object.is() 并不能用来判断两个对象的“内容”是否完全一致。 它的本质是 === 运算符的一个“增强补丁版”,其核心逻辑依然聚焦于严格比较引用地址。对于对象、数组这类引用类型,它不会递归地检查内部属性和结构。
Object.is() 的真实行为:精准但范围有限
简单来说,Object.is() 在绝大多数场景下与 === 的行为保持一致,仅修正了两个特例:
Object.is(+0, -0)返回false,而===会返回true。Object.is(NaN, NaN)返回true,而===会返回false。- 对于对象、数组、函数等引用类型,
Object.is(a, b)只有在a和b指向同一内存地址(即同一个引用)时才返回true,这一结果与a === b完全一致。
为什么它无法胜任“对象内容一致”的判断?
根本原因在于它“不深入内部”进行递归比较。来看几个典型例子:
Object.is({a:1}, {a:1})返回false。虽然内容相同,但两者是独立创建的对象,内存地址不同。const a = {x: 1}; Object.is(a, a)返回true,因为这是同一个对象的同一引用。Object.is([1,2], [1,2])同样返回false。两个独立的数组,即使元素完全相同,地址也不同。
因此,如果你需要判断两个对象的结构和属性值是否完全一致,Object.is() 很快会让你失望。
若需判断对象“内容一致”,有何替代方案?
不必担心,针对不同场景和需求,JavaScript 社区已提供了多种成熟方案:
- 快速浅层比较(仅第一层属性):可以使用
JSON.stringify(obj1) === JSON.stringify(obj2)。但需注意,该方法对属性顺序敏感,且会忽略函数、undefined、Symbol类型的值,也无法处理循环引用的对象。 - 可靠的浅比较:借助 Lodash 库的
_.isEqual()(它默认进行深比较,但可通过迭代次数控制),或者手动遍历对象自身属性,结合Object.is()或===逐个比较属性值。后者适用于结构已知的简单对象。 - 深层全等比较(推荐):对于复杂嵌套对象,建议直接使用社区成熟工具库,例如
fast-deep-equal、lodash.isequal或 Node.js 内置的util.isDeepStrictEqual。它们已妥善处理了各种边界情况。 - 自定义比较逻辑:在特定业务场景下,你可能需要明确比较哪些字段、忽略哪些字段,或特殊处理
Date、RegExp、Map、Set等类型。此时,编写一个自定义的比较函数是最灵活的选择。
小结:明确 Object.is() 的清晰定位
总而言之,Object.is() 的定位非常明确:它是一把为修复 === 在 NaN 和 ±0 上反直觉行为而设计的底层工具。它适用于需要精确原始值相等语义的场景,比如编写 Polyfill、处理算法中的边界条件。但它绝非用于对象内容比对的方案。若要判断两个对象是否“内容一致”,我们应当跳出引用比较的思维,转向值层面的深度分析。
