前言

判断两个变量是否相等是程序中非常重要的运算。在处理原始值时,这种运算相当简单,但涉及复杂对象,就稍有点复杂。JS 提供了两类等性运算符:等和非等用于处理原始值,全等和非全等用于处理对象。它们都返回布尔值

一、双等

双等使用 == 两个等号表示,当两个值进行比较时,可能会将它们的数据类型进行强制转换。比较规则如下(摘自w3c):

  • 如果一个运算数是 Boolean 值,在检查相等性之前,把它转换成数字值。false 转换成 0,true 为 1
  • 如果一个运算数是字符串,另一个是数字,在检查相等性之前,要尝试把字符串转换成数字
  • 如果一个运算数是对象,另一个是字符串,在检查相等性之前,要尝试把对象转换成字符串
  • 如果一个运算数是对象,另一个是数字,在检查相等性之前,要尝试把对象转换成数字

上面规则的对象这里指引用类型

另外:

  • 如果两个运算值都是引用类型时,判断引用是否一致。一致则认为相等
  • null 和 undefined 只互相相等并且等于自身。它们和其他类型相比均不等于
  • NaN 不等于任何,包括 NaN
  • 在强制转换时使用的是 String 和 Number 这两个构造函数

总结:

  • 从比较规则可以发现将谁转换成什么类型是有一个优先级顺序的,即:引用类型 -> 字符串 -> 数值。当这三种类型其中的两种进行比较时,转换顺序自左向右转换
  • Boolean 类型和任何比较都是双方先转成数值在进行比较
  • 特殊 nullundefinedNaN

验证:

  • Boolean类型:一个运算数是布尔值,都会转成数值进行比较
true == 1     // true  | Number(true) -> 1  1 -> 1
true == 2     // false | Number(true) -> 1  2 -> 2
true == "a"   // false | Number(true) -> 1  Number('a') -> NaN
true == "1"   // true  | Number(true) -> 1  Number('1') -> 1
true == [1]   // true  | Number(true) -> 1  Number([1]) -> 1
true == ({})  // false | Number(true) -> 1  Number({}) -> NaN
true == ([])  // false | Number(true) -> 1  Number([]) -> 0
false == ({}) // false | Number(false) -> 0  Number({}) -> NaN
...
  • null、undefined:双方互等和等于自身,其他均不相等
null == undefined       // true
null == null            // true
undefined == undefined  // true
null == 1               // false
null == 'a'               // false
null == true               // false
null == false               // false
  • NaN:不等于任何、包括自身
NaN == 1          // false
NaN == 'a'        // false
NaN == true       // false
NaN == undefined  // false
NaN == NaN        // false
  • 引用类型、字符串、数值的比较:遵守优先级顺序
// 引用类型和字符串:把引用类型转成字符串比较∂∂
({}) == "a"                    // false | String({}) -> '[object Object]'
({}) == "[object Object]"     // true | String({}) -> '[object Object]'
([]) == 'a'                   // false | String([]) -> ''
([]) == ''                    // true |  String([]) -> ''
([1,2]) == '1,2'              // true | String([1,2]) -> '1,2'
/a/ == '/a/'                  // true | String(/a/) -> '/a/'
new Map() == 'a'              // false | String(new Map()) -> '[object Map]'
new Set() == '[object Set]'              // true | String(new Set()) -> '[object Set]'

// 引用类型和数值:把引用类型转成数值比较
({}) == 1                     // false | Number({}) -> NaN
([]) == 0                     // true | Number([]) -> 0
([]) == 1                     // false | Number([]) -> 0
[2] == 2                      // true | Number([2]) -> 2
/2/ == 2                      // false | Number(/2/) -> NaN
new Set() == 0                // false | Number(new Set()) -> NaN

// 字符串和数值:把字符串转成数值比较
'a' == 0          // false | Number('a') -> NaN
'1' == 1          // true | Number('1') -> 1
'2' == 1          // false | Number('2') -> 2

二、全等

全等使用 === 三个等号表示,当两个值进行比较时,不会进行数据类型的转换

规则如下:

  • 不进行数据类型转换,即数据类型和值都相等才相等
  • 两个引用类型相比较,依然判断引用是否一致
  • NaN 仍然不等于自身和任何
  • -0 等于 +0

注意:其实 NaN 应该等于 NaN,-0 不应该等于 +0。在ES6使用 Object.is 即可解决

'a' === 'a'  // true
1 === 1      // true
[] === []    // false
/1/ === /1/  // false

let obj = {}
let obj2 = obj
obj === obj2 // true

NaN === NaN // false
Object.is(NaN, NaN) // true

-0 === 0   // true
-0 === +0  // true
Object.is(-0, 0) // false
Object.is(-0, +0) // false