JavaScript Object.is 判断值相等详解
Object.is()
ES5 比较两个值是否相等,只有两个运算符:相等运算符(==)和严格相等运算符(===)。 它们都有缺点,前者会自动转换数据类型,后者的 NaN 不等于自身,以及 +0 等于 -0。JavaScript 缺乏一种运算,在所有环境中,只要两个值是一样的,它们就应该相等。
Object.is 用来比较两个值是否严格相等,与严格比较运算符(===)的行为基本一致,不同之处只有两个:一是 +0 不等于 -0,二是 NaN 等于自身。
+0 === -0; //true
NaN === NaN; // false
Object.is(+0, -0); // false
Object.is(NaN, NaN); // true
三套相等语义
规范里其实定义了三种不同的相等判断,搞清它们的分工就不容易记混:
- 宽松相等(
==),规范里叫 Abstract Equality。类型不同时会按规则做隐式转换再比,'1' == 1、null == undefined、[] == false都是true。转换规则细节多,所以日常代码里我们基本不用它。 - 严格相等(
===),Strict Equality。类型不同直接返回false,不做转换。但它对NaN和零值有两个特例。 - 同值相等(
Object.is),SameValue。可以理解成「修好了那两个特例的===」。
Object.is 与 === 的差异只有前面提到的两处,其余行为完全一致,包括对象仍然按引用比较:
Object.is({}, {}); // false,两个不同的对象
Object.is('1', 1); // false,不做类型转换
Object.is(null, undefined); // false
const o = {};
Object.is(o, o); // true
NaN 为什么不等于自身
NaN !== NaN 不是 JavaScript 的设计失误,而是照抄了 IEEE 754 浮点标准:NaN 表示「不是一个数」,它可能来自 0/0、Math.sqrt(-1)、parseInt('abc') 等各种失败的运算,两个都失败了的结果之间并没有理由相等。
代价是判断一个值是不是 NaN 变得别扭。历史上的做法是全局 isNaN,但它会先做类型转换,isNaN('abc') 会返回 true,这基本不是我们想问的问题。ES6 之后有三个更准确的选择:
isNaN('abc'); // true,先转成数字再判断,容易误判
Number.isNaN('abc'); // false,只有真正的 NaN 才是 true
Number.isNaN(NaN); // true
Object.is(NaN, NaN); // true
[NaN].includes(NaN); // true,includes 用的也是 SameValueZero
+0 与 -0 的区别在哪
浮点数用一个独立的符号位表示正负,所以 0 有两个:+0 和 -0。它们在几乎所有运算里表现相同,+0 === -0 也是 true,因此平时感觉不到差别。会露出来的地方主要是除法的符号和 Math 的取整结果:
1 / +0; // Infinity
1 / -0; // -Infinity
Math.round(-0.4); // -0
(-0).toString(); // "0",字符串化看不出区别
Object.is(-0, 0); // false,这是唯一直接区分它们的内建方法
真正需要区分正负零的场景不多,做几何、动画方向或者物理计算时可能会碰到——比如一个速度值降到 -0,我们想知道它原本朝哪个方向。这时候 Object.is(v, -0) 是最直接的判断。
SameValueZero:数组和 Map 用的第四套规则
还有一个规范内部使用、没有暴露成方法的比较算法叫 SameValueZero,它和 Object.is 只差一点:认为 +0 等于 -0,但 NaN 等于自身。Array.prototype.includes、Map 和 Set 的键去重用的都是它,这解释了为什么 Set 里放两个 NaN 只会留下一个:
new Set([NaN, NaN]).size; // 1,NaN 被当成同一个键
new Set([0, -0]).size; // 1,+0 与 -0 也被当成同一个
[NaN].indexOf(NaN); // -1,indexOf 用的是 ===,所以找不到
[NaN].includes(NaN); // true,includes 用的是 SameValueZero
indexOf 和 includes 的这个差异是很实际的坑:数组里可能出现 NaN 时,判断存在性一定要用 includes。
该用哪个
日常写业务代码,=== 依然是默认选择,可读性最好也最符合直觉。只有在下面这些情况才需要换:
- 判断是不是
NaN→ 用Number.isNaN,语义最清楚。 - 写通用的深比较、
memo缓存键、状态是否变化的判断 → 用Object.is,因为这类逻辑不希望NaN和自己不相等导致误判「变了」。React的useState判断是否需要重渲染用的就是Object.is。 - 需要区分正负零 → 只有
Object.is能做到。
顺带说一句,Object.is 名字里带 Object 只是因为它挂在 Object 上作命名空间,它和对象没有特别关系,比较原始值同样适用。

还没有留言,来发表第一条吧~