Algorithms within this specification manipulate values each of which has an associated type.
—— ECMA262
众所周知 JS 是动态弱类型的,当使用不严格的 ==
时会存在隐式的类型转换。在比较两个值时必然会先确定值的类型,在 ecma262
中经常会出现的 Type(x)
是 “the type of x “ 的缩写 ,其中 type
通常指的是 ECMAScript language type
也就是我们直接使用的语言类型,主要有以下几种:
- undefined
- null
- boolean
- string
- number
- symbol
- object
类型及算法过程
在 ECMA262 的 Test and Comparison Operations 这节里介绍了以下几种等值比较操作:
- Strict Equality Comparison
- SameValueNonNumber(x, y)
- SameValue(x, y)
- SameValueZero(x, y)
那么每种有什么区别,具体是怎么执行的呢。
Strict Equality Comparison
这个就是严格相等,x === y
, 它的执行过程是这样的:
如果 x 和 y 的类型不一样,则返回
false
。如果类型相同,当类型是
number
时:- 如果 x 或者 y 是
NaN
(它的类型是 number) 则返回false
。注意,只要 x、y 有一个为NaN
都会返回false
。所以在严格相等中NaN === NaN
返回false
。 - 如果
x
和y
是同样的数值,则返回true
。 x
是+0
,y
是-0
或者x
是-0
,y
是+0
都会返回true
。- 否则返回
false
。
- 如果 x 或者 y 是
这里算法步骤首先排除 NaN 这种特殊情况,因为它不等于自身,然后比较两个值是否相同,是的话就返回 true,接着因为 +0(也就是0) 和 -0 是两个不同的值,但是它们是相等的,所以这种情况下也返回 true 。其余剩下的就是不同的值,返回 false 了。
- 如果值类型相同但是非数值类型的话则执行
SameValueNonNumber(x, y)
。
SameValueNonNumber(x, y)
当 x、y 的类型相同但是都不为 number
类型时执行该算法。
如果
x, y
都是undefined
或者null
类型的话返回true
,即undefined === undefined null === null
。如果
x, y
都是string
,当它们长度相等且每个索引的(chatAt(index)
)的字符相同时返回true
否则返回false
。如果
x, y
都是boolean
,当它们都是true
或者都会false
时返回true
否则返回false
。如果
x, y
是Symbol
类型,只有当它们都指向同一个Symbol
对象时才会回true
否则返回false
。因为 Symbol 是全局唯一的。Symbol(1) === Symbol(1) // false const a = b = Symbol(1) a === b // true
如果
x, y
指向同样的Object
值,返回 true ,否则返回false
。
SameValue(x, y)
上面介绍了严格相等 ===
所用到的两种算法,在 ES6
中新增了一种比较方法 Object.is(x, y)
,它使用的就是内部的 SameValue
算法:
- 如果
x, y
的类型不同,返回false
。 - 如果
x, y
都是number
类型:- 如果 x 是
NaN
并且 y 是NaN
,返回true
。 - 如果 x 是
+0
,y 是-0
或者 x 是-0
,y 是+0
,都返回false
。 - 如果
x, y
为同样的数值,返回true
。 - 不然返回
false
。
- 如果 x 是
- 如果类型相同但是不为
number
类型,执行SameValueNonNumber(x, y)
操作。
总的来说就是,Object.is(x, y)
采用的是 SameValue
算法,而 ===
采用的是 Strict Equality Comparison
,它们之间的区别是对待 NaN
和 +0 -0
不同。
Object.is(NaN, NaN) // true
Object.is(+0, -0) // false
NaN === NaN // false
+0 === -0 // true
SameValueZero(x, y)
除了上面的三种,还有一种叫 SameValueZero(x, y)
。顾名思义,它跟 SameValue
一样,除了 0
之外。在该算法中,+0 === -0
。简单来说就是,在比较数值的时候,只要它们是相同的数值那就相等,只要是 0 不管是 +0
还是 -0
也都相等,NaN === NaN
。
那么这种算法有什么意义呢?我全局搜索 SameValue
发现以下几个地方需要用到它。
- Array.prototype.includes(searchElement, [, fromIndex])
该方法在数组中查找指定的元素,如果找到则返回 true
,否则返回 true
。跟 indexOf
使用了 Strict Equality Comparison
不一样,它使用的是 SameValueZero
算法, 还有一个不同点就是它不会跳过数组的空值,而是把它们当作 undefined
:
const arr = [1, -0, NaN, 4]
arr.includes(0) //true
arr.includes(+0) //true
arr.includes(NaN) //true
// indexOf 使用的是 === 。
arr.indexOf(+0) // 1, +0 === -0
arr.indexOf(NaN) // -1, NaN !== NaN
// arr.length = 5; ==> [1, -0, NaN, 4, empty]
arr.includes(undefined) // true
arr.indexOf(undefined) // -1
- Map Objects
ES6
新增的 Map
对象也是使用该算法来识别不同的 key
。
const map = new Map()
undefined
map.set(+0, 'same') // Map(1) {0 => "same"}
map.get(-0) // "same"
map.set(NaN, 'NaN') // Map(2) {0 => "same", NaN => "NaN"}
map.get(NaN) // "NaN"
map.delete(-0) // true
console.log(map) // Map(1) {NaN => "NaN"}
- Set
因为 Set
中的值总是唯一的,所以需要判断两个值是否相等。在 ES6
规范中,不同值就是使用 SameValueZero
算法来区分的。
const set = new Set()
console.log(set) // Set(0) {}
set.has(-0) // false
set.add(+0) // Set(1) {0}
set.has(-0) // true
set.add(NaN) // Set(2) {0, NaN}
set.has(NaN) // true
总结
在 JavaScript
进行比较时,尽量使用严格的操作,避免不必要的类型转换。严格比较主要有 ===
Object.is()
两种,它们的不同之处是对待 NaN
和 -0
+0
的区别。还有一种语言内部使用的 SameValueZero
算法,NaN === NaN, +0 === -0
它主要用于确定值的唯一性。