先记住这个答案
Object.is 和 === 大部分情况下结果一样,只有两个例外:NaN === NaN 是 false,而 Object.is(NaN, NaN) 是 true;+0 === -0 是 true,而 Object.is(+0, -0) 是 false。比较两个对象时,两者都只看是不是同一个引用,内容一样但不是同一个对象,都返回 false。Object.is 用的规则叫「同值相等」,可以理解成「修掉了两个怪癖的 ===」。
Object.is(NaN, NaN)为trueObject.is(+0, -0)为false- 比较对象时两者都只看引用,不比内容
includes使用 SameValueZero
先看两者不一样的地方
把所有差异放在一段代码里,一次看全。
NaN、+0 和对象的对比JavaScriptconst n = NaN
console.log(n === n) // false
console.log(Object.is(n, n)) // true
console.log(+0 === -0) // true
console.log(Object.is(+0, -0)) // false
const a = { id: 1 }
const b = { id: 1 }
console.log(a === b, Object.is(a, b)) // 内容一样,但不是同一个对象
const c = a
console.log(a === c, Object.is(a, c)) // 同一个引用查看输出与解释
false
true
true
false
false false
true true前四行就是两者全部的差别。后两行说明对象比较时它们行为一致:a 和 b 是两块独立的内存,哪怕字段一模一样也不相等;c 只是 a 的另一个名字,所以相等。
为什么 === 会有这两个怪癖
这两处都来自 IEEE 754 浮点数标准,JavaScript 的数字就是按它实现的。
NaN 表示「不是一个有效数字」,比如 0 / 0、Number('abc') 的结果。标准规定 NaN 和任何值比较都不相等,包括它自己。所以判断一个值是不是 NaN,不能写 x === NaN,要用 Number.isNaN(x),或者 Object.is(x, NaN)。
-0 在浮点数里有独立的符号位,和 +0 是两个不同的值,但标准规定它们比较时相等。大多数时候这没问题,可有些计算会暴露差别:1 / +0 是 Infinity,1 / -0 是 -Infinity。比如做一个带方向的速度动画,速度从负数减到 0 时得到的是 -0,符号代表方向,这时就要用 Object.is(v, -0) 才能区分出来。
在项目里哪儿会用到 Object.is
日常写业务判断,=== 足够用了。Object.is 更多出现在框架内部,用来回答「这个值到底变了没有」。
- React 用它判断状态有没有变化
- React 的
useState在setState时,会用Object.is比较新旧值,相同就跳过这次更新;useEffect、useMemo的依赖数组也是逐项用Object.is比较。所以状态是NaN时,再setState(NaN)不会触发重复渲染,用===的话会认为变了。反过来,直接修改对象再setState同一个引用,React 会认为没变,页面不更新,这就是为什么要写setUser({ ...user, name })生成新对象。 - 第三种规则:SameValueZero
Array.prototype.includes、Map的键、Set的成员用的是 SameValueZero,它介于两者之间:NaN等于自己,但+0和-0也相等。所以[NaN].includes(NaN)是 true,而老的[NaN].indexOf(NaN)返回 -1,因为indexOf用的是===。
容易答错的地方
- 以为
Object.is能比较对象内容 Object.is不是深比较。Object.is({ a: 1 }, { a: 1 })是 false,和===一样。要比较内容得自己递归比较字段,或者用 lodash 的isEqual。- 以为
Object.is能完全替代=== - 它能替代,但没必要。业务代码里几乎不会遇到
-0,判断NaN也有更直白的Number.isNaN。满篇Object.is反而让读代码的人以为这里有什么特殊的边界要处理。 - 把
isNaN和Number.isNaN混着用 - 全局的
isNaN会先把参数转成数字,所以isNaN('abc')是 true。Number.isNaN不做转换,只有真的是NaN才返回 true,结果和Object.is(x, NaN)一致。
面试官还会怎么问?
== 在这两个值上表现如何?
和 === 一样:NaN == NaN 是 false,+0 == -0 是 true。== 只是多了类型转换,对这两个特殊值没有额外处理。
不用 Object.is,怎么手写一个等价的函数?
先用 === 比较。相等时再排除 +0 和 -0 的情况,可以用 1 / x === 1 / y 区分正负零;不相等时再看两个值是不是都是 NaN,利用 x !== x 只对 NaN 成立这一点。这也是早期 polyfill 的写法。
为什么 React 选 Object.is 而不是 ===?
因为 React 要的是「值是否真的一样」。NaN 状态用 === 永远判为变化,会导致每次都重新渲染;+0 和 -0 在某些计算里结果不同,也应该算作变化。Object.is 在这两点上都更贴近「同一个值」的直觉。
参考资料
示例用于理解所注明的运行环境与边界;延伸学习可结合原文中的更多案例。