前言

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

一、双等

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

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

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

另外:

  • 如果两个运算值都是引用类型时,判断引用是否一致。一致则认为相等
  • null 和 undefined 只互相相等并且等于自身。它们和其他类型相比均不等于
  • NaN 不等于任何,包括 NaN
  • 在这里强制转换时使用的是 String 和 Number 这两个构造函数(其实在MDN文档上原文写”如果操作数之一是对象,另一个是数字或字符串,会尝试使用对象的valueOf()和toString()方法将对象转换为原始值。“。但是如果是一个布尔值和一个对象进行比较时转换次数比较不确定或者麻烦,这里使用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