JavaScript 中的相等和全等之谜

你搞明白 JS 中的 == 和 === 了吗?

Posted by MurphyChen on October 9, 2021

前言

我们都知道 JS 有两种判断是否相等的运算符,一种是相等 ==,还有一种是全等 ===。理清楚两者的区别,对日后程序的开发和维护都有很重要的作用。

什么?你已经懂了,觉得这很简单?那么先来看一道题吧。

下面的 a 要怎样赋值,才能使 if 判断为真,打印出 a ?

1
2
3
4
// var a = ? 
if (a == 'red' && a == 'green' && a == 'blue') {
    console.log(a);
}

你或许会惊讶,这怎么可能,判断条件里面的 a 怎么可能等于不同的值…看完后面的内容,你将豁然开朗。

全等 ===

对于全等 ===,这是我们开发最常使用的判断相等方式。

  • 第一步,判断比较对象的类型是否相同,若类型不同则返回 false
  • 若类型相同,则进行第二步,判断其值是否相等,若相等则返回 true,并且不调用任何该对象的方法。

全等 === 比较具体规则如下:

比较的对象 是否相等
Number 若都为数值,且值相同,则相等;只要出现了 NaN ,则不相等
String 若都是字符串,且各个字符都相同,则相等,否则不相等
Boolean 若均为 true 或均为 false ,则相等,否则不相等
Function 当引用同一个函数的时候才会相等
Object 当引用同一个对象的时候才会相等
null 若两个值都是 null则相等
undefined 若均为 undefined 则相等
数组 当两数组引用指向同一地址则相等
函数 当两函数引用指向同一地址则相等

双等号 ==

对于双等号 ==,涉及到隐式转换,至于为什么,这就要问 JS 之父 Brendan Eich 了。

转换规则如下:对于两个比较对象

  • 若有一个是数字型字符串,有一个是数字,则将字符串转换为相应的数值再比较,两者数值相等则相等。
    1
    
      console.log('123' == 123); // true
    
  • 若有一个是布尔型,则将布尔型转换为相应的数值再比较:true1false0
    1
    2
    
      false == 0 // true
      true == '1' // true
    
  • 若有一个是一个对象,而另一个不是对象,则该对象要先执行内部的 valueOftoString 方法进行转换,然后再比较。(即转换为对象的原始类型)
    1
    2
    3
    4
    5
    6
    
      const obj = {
      valueOf: function () {
          return 123;
          }
      }
      console.log(obj == 123); // true
    
  • 若两个都是对象,则不进行转换,而是判断其引用是否指向同一地址,若指向相同则相等。
    1
    2
    3
    4
    
      let obj1 = {age: 18};
      let obj2 = obj1;
      obj2.age = 20;
      console.log(obj1 == obj2); // true
    
  • 若有一个为对象(String()Number()Symbol()),则将该对象转换为相应的原始类型再比较。
    1
    2
    
      let numObj = new Number(123);
      console.log(numObj == 123); // true
    
  • 只要有一个操作数为 NaN,则不相等,返回 false
    1
    
      console.log(NaN == NaN); // false
    
  • undefinednull 是相等的,且比较的时候不进行转换。
    1
    
      undefined == null // true
    
  • 若两个均为数组/函数,则仅当两者引用指向同一地址时相等。
    1
    2
    3
    4
    
      let a1 = [1,2,3];
      let a2 = a1;
      a2.push(4);
      a1 == a2 // true
    
  • 若一个对象为只有一个元素的数组,而另一个不是数组,则比较的时候取该数组的原始类型(当数组只有一个元素的时候),即拿该数组的第一个元素来直接比较。经验证,比较的过程中该数组会执行 toString()valueOf 方法。
    1
    
      ['apple'] == 'apple' // true
    

案例

回到本文开头的那个问题,是不是有思路了?

1
2
3
4
// var a = ? 
if (a == 'red' && a == 'green' && a == 'blue') {
    console.log(a);
}

没错,a 肯定是一种复杂数据类型,这里我们假设 a 为对象。那么 a 肯定有一个属性为数组,存着 redgreenblue 三个字符串。然后,按照上节讲的转换原理,每次进行 == 比较的时候,都改变 toStringvalueOf 方法的返回值,问题迎刃而解。

一个参考答案:

1
2
3
4
5
6
7
var a = {
    i: 0,
    colors: [`red`, `green`, `blue`],
    valueOf: function () {
        return this.colors[this.i++];
    }
}

思考:数组也是一种复杂数据类型,那么 a 有没有可能是数组呢?