JavaScript 中提供了三种不同的值比较操作:
- 严格相等(”Strict Equality Comparison”, or “Identity”):===
- 宽松相等(”Abstract Equality Comparision”, or “Loose Equality”):==
- 同值判断(”SameValue”):Object.is(ECMAScript 2015 新特性)
而 ECMAScript 2015 中的相等算法则分为四种:
- 非严格相等比较(==)
- 严格相等比较(===),用于Array.prototype.indexOf,Array.prototype.lastIndexOf以及switch...case
- 同值(Object.is)
- 同值零(”SameValueZero”),用于 %TypedArray%和ArrayBuffer的构造函数,Map和Set操作,以及 ECMAScript 2016 中的String.prototype.includes中
简而言之,
- ==将进行类型转换并比较
- ===不会进行类型转换,比较两个值是否相同(如果类型不同,则总是返回- false)
- Object.is的行为与- ===相同,但是对于- NaN,- -0和- +0进行了特殊处理,- Object.is(+0, -0)为- false,而- Object.is(NaN, NaN)为- true(根据 IEEE 754, 使用- ==或- ===比较两个- NaN结果将为- false)
- “SameValueZero” 与 Object.is类似,但认为-0和+0是相等的
而以上这些比较,虽然结果有所区别,但均属于判断两个值是否相同。对于两个不同的非原始对象,以上判断的结果都是 false。
严格相等 ===
全等操作符(===)比较两个值是否相等,被比较的值不会进行隐式类型转换,判断逻辑如下:
- 如果两个值具有不同的类型,则不全等
- 如果两个值具有相同的类型和值,且不为 number 类型,则全等
- 如果两个值都是 number 类型,且不为 NaN,且数值相等,则全等
- 如果两个值都是 NaN,则不全等
- 如果两个值分别为 +0和-0,则全等
在日常使用中 === 几乎总是正确的。
对于数字之外的类型,全等操作符有明确的定义:一个值只与自身相等。
对于数字类型,定义稍加修改:
- 浮点数 0 是不分正负的,因为除了特定的数学问题,大部分情况都不关心 0 值的正负
- 浮点数包含 NaN 值,来表示定义不明确的数学问题的解(比如:正负无穷相加),===认为NaN和任何值都不相等,包括它自己
x !== x成立的唯一条件就是x为NaN,因此可以用来做NaN值判断- ECMA 规范中的定义:Section 11.9.6, The Strict Equality Algorithm
非严格相等 ==
相等操作符(==)比较两个值是否相等,比较前将被比较的值转换为相同类型(等式的一边或两边都可能进行转换),然后进行 === 比较。相等操作符满足交换律。判断逻辑如下:
具有不同类型时:
- 如果两个值为 null或undefined,则相等
- 如果一个值为 null或undefined,另一个值为number或string或boolean,则不相等
- 如果一个值为 null或undefined,另一个值为object,则对object进行 “IsFalsy” 判断
- 如果一个值为 object,另一个值为number或string,则对object进行 “ToPrimitive” 转换并判断是否全等
- 如果一个值为 object,另一个值为boolean,则将boolean转换为number并对object进行 “ToPrimitive” 转换并判断是否全等
- 如果一个值为 number,另一个值为string或boolean,则将另一个值转换为number并判断是否全等
- 如果一个值为 string,另一个值为boolean,则都转换为number来判断
注:
- “ToPrimitive” 通过尝试调用 toString()和valueOf()来将对象转换为原始值
- 转换为 number的逻辑与一元+运算符相同
- “IsFalsy” 判断:大部分浏览器允许非常窄的一类对象在某种情况下充当 undefined,仅当这种情况下,”IsFalsy” 判断为true
- 有些开发者认为,最好永远都不要使用
==,因为==的结果难以预测,且会进行隐式类型转换,===更加容易预测并更加快速。- ECMA 规范中的定义:Section 11.9.3, The Abstract Equality Algorithm
同值相等(”SameValue”)
同值相等(”SameValue”)用于判断两个对象是否在任何情况下功能上是相同的,判断逻辑如下:
- 如果两个值具有不同的类型,则不同值相等
- 如果两个值具有相同的类型和值,且不为 number 类型,则同值相等
- 如果两个值都是 number 类型,且不为 NaN,且数值相等,则同值相等
- 如果两个值都是 NaN,则同值相等(与===相反)
- 如果两个值分别为 +0和-0,则不同值相等(与===相反)
比如 Object.defineProperty 在试图修改不可变属性的时候,如果值发生变化就会抛出异常,而值没有变化的话则什么都不做。这时就是用同值相等来判断值是否发生了变化。
| 1 | Object.defineProperty(Number, 'NEGATIVE_ZERO', { | 
- 这个算法在 ES5 中仅用于 JS 引擎的内部,ES6 中通过
Object.is暴露了这个算法。- ECMA 规范中的定义:Section 9.12, The SameValue Algorithm
同值零相等(”SameValueZero”)
同值零相等(”SameValueZero”)与同值相等类似,只是它认为 +0 和 -0 是相等的。
什么时候使用 Object.is?
总的来说,除了对待 NaN 的方式不同,Object.is 与 === 的唯一区别就是对待 -0 和 +0 的不同。
下面这些方法和操作符会区别对待 -0 和 +0:
- 一元负号(Unary -)
一元负号在表达式的使用可能会无意识产生 -0,比如:obj.mass * - obj.velocity,如果 obj.mass 为 0,则会得到一个 -0
- Math.atan2
- Math.ceil
- Math.pow
- Math.round
这些函数即使参数中没有 -0,都有可能产生 -0 的结果
- Math.floor
- Math.max
- Math.min
- Math.sin
- Math.sqrt
- Math.tan
这些函数当参数中有 -0 时,有可能产生 -0 的结果
- ~
- <<
- >>
这些操作符内部都使用了 ToInt32 算法。因为内部的 Int32 类型不区分 0 的正负,-0 在进行了这些操作后,不会保留负号。
因此在未考虑到 0 的符号的情况下使用 Object.is 可能得不到预期的效果。