一文搞懂JavaScript 之 == 与 === 的区别?

691 阅读6分钟

一文搞懂一文搞懂JavaScript ==与===的区别 .png

「这是我参与11月更文挑战的第1天,活动详情查看:2021最后一次更文挑战」。

宽松相等(==)与严格相等(===)二者都是用于判断两个值是否相等,但它们在判断条件上有很大的区别,== 允许在相等比较中进行强制类型转换,而===不允许。==和===都会检测操作数的类型,区别在于操作数类型不同时,它们的处理方式不同。

1. 性能方面

  • 错误的解释:“==检查值是否相等,===检查值和类型是否相等。” 在此解释中,===似乎比==做的事情更多,因为它还要检查值的类型。

  • 正确的解释:“==允许在相等比较中进行隐式强制类型转换,而===不允许。” 在此解释中,==的工作量更大一些,因为如果值的类型不同,还需要进行强制类型转换。

  • 有人会觉得==比===慢,然而事实上虽然强制类型转换确实需要多花点时间,但仅仅只是微秒级(百万分之一)的差别而已。

  • 如果进行比较的两个值类型相同,则==和===使用相同的算法,所以除了JS引擎实现上的细微差别之外,它们之间并没有什么不同。

  • 如果两个值的类型不同,我们就需要考虑是否有强制类型转换的必要,若有就用==,没有就用===,不用在乎性能。

2. == 中的隐式强制类型转换

2.1. 常规情况

2.1.1. 两个值的类型相同
  • 基本类型:比较它们的值是否相等即可,但是需要注意以下情况:

    • NaN != NaN
    • +0 == -0
  • 引用类型:当两个对象指向同一个值时即视为相等,不发生强制类型转换。

    • 实际上在比较两个对象的时候,== 和 === 的工作原理是一样的。
2.1.2. 字符串与数字
  • ES5 规范 11.9.3.4-5 定义:
如果Type(x)为数字,Type(y)为字符串,则返回x==ToNumber(y)的结果。
如果Type(x)为字符串,Type(y)为数字,则返回ToNumber(x)==y的结果。
  • 例如:

    (42 == '42') → (42 == 42) → true

2.1.3. 其他类型与布尔类型
  • ES5 规范 11.9.3.6-7 定义:
1. 如果Type(x)为布尔类型,则返回 ToNumber(x) == y 的结果。
2. 如果Type(y)为布尔类型,则返回 x == ToNumber(y) 的结果。
  • ToNumber的结果:

    • Number(true) = 1
    • Number(false) = 0
  • 举个栗子

    • 例1:
    var x = true;
    var y = '42';
    // (x == y) → (1 == '42') → (1 == 42) → false
    x == y; // false
    
    • 例2:
    var x = '42';
    var y = false;
    // (x == y) → ('42' == 0) → (42 == 0) → false
    x == y; // false
    

    从以上两个例子,我们可以发现,字符串'42'既不等于true,也不等于false,这个值既非真值也非假值?Amazing!然而,事实上是我们被自己的大脑欺骗了,'42'是一个真值,但在 '42' == true 这个对比中并没有发生布尔值的比较,这里是true转换为1,'42'转换为42,这里并不涉及ToBoolean,因此'42'是否为真值与==本身并无关系!

    因此,建议无论在什么情况下,都应避免使用 == true 与 == false(布尔值的宽松相等)。

    注意:由于 === true 和 === false 不允许强制类型转换,所以并不涉及ToNumber。

    • 🌰3:
    1 == true;	  // true
    0 == false;   // true
    
  • 用法

    • 不建议的用法:
    if(a == true){ ... }  // 不要这样用,条件判断不成立
    if(a === true){ ... } // 不要这样用,条件判断不成立
    
    • 建议的用法:
    if(a){ ... } 				  	// 这样的显示用法没问题
    if(!!a){ ... }				  // 这样的显示用法更好
    if(Boolean(a)){ ... } 	// 这样的显示用法也很好
    
2.1.4. null与undefined
  • ES5 规范 11.9.3.2-3 定义:
如果x为null,y为undefined,则结果为true。
如果x为undefined,y为null,则结果为true
  • 在 == 中 null 和 undefined 相等(它们与其自身相等),除此之外其他值都不存在这种情况。即,在 == 中 null 与 undefined 是一回事,可以相互进行隐式强制类型转换。
2.1.5. 对象与非对象
  • ES5 规范 11.9.3.8-9 定义:
如果Type(x)为字符串或数字,Type(y)为对象,则返回 x == ToPrimitive(y) 的结果。
如果Type(x)为对象,Type(y)为字符串或数字,则返回 ToPrimitive(x) == y 的结果。
  • 这里只提到了字符串和数字,没有布尔值,原因是之前介绍过的,布尔值会先被强制类型转换为数字

  • 举些栗子:

    • 例1:
    var a = 42;
    var b = ['42'];
    
    a == b; // true
    
    • 例2
    0 == []; // true
    0 == [0]; // true
    0 == ['']; // true
    0 == [null]; // true
    0 == [undefined]; // true
    0 == [false]; // false
    
    • 例3
    1 == [1]; // true
    1 == [true]; // false
    1 == function(){return 1;}; // false
    1 == function(){return 1;}(); // true
    
    • 例4
    var a = 'abc';
    var b = Object('a'); // 和new String(a)一样
    
    a === b; // false
    a == b;  // true,因为b通过ToPromitive进行强制类型转换(也称为“拆封”),并返回标量基本类型值“abc”,与a相等。
    
    // 但有些情况并不是这样,原因是 == 算法中其他优先级更高的规则。
    
    • 例5
    var a = null;
    var b = Object(a); // 和Object()一样
    a == b; // false
    
    var c = undefined;
    var d = Object(c); // 和Object()一样
    c == d; // false
    
    // 因为没有对应的封装对象,所以 null 和 undefined 不能被封装(boxed),Object(null) 和 Object()均返回一个常规对象。
    
    var e = NaN;
    var f = Object(e); // 和new Number(e)一样
    e == f; // false
    
    // NaN能够被封装为数字封装对象,但拆封之后 NaN == NaN 返回false,因此返回false。
    

2.2. 比较少见的情况

2.2.1 返回其他数字
例1:
Number.prototype.valueOf = function(){
    return 3;
}
new Number(2) == 3; // true
  • 2 == 3 不会有这种问题,因为2和3都是数字基本类型值,不会调用Number.prototype.valueOf()方法。

  • 而Number(2)涉及ToPrimitive强制类型转换,因此会调用valueOf()。

  • 此为人为原因造成。

例2:
var i = 2;
Number.prototype.valueOf = function() {
    return i++;
};
var a = new Number( 42 );
if (a == 2 && a == 3) {
    console.log( "Yep, this happened." );
}
  • 我们应该正确合理地运用强制类型转换,避免这些极端的情况。
2.2.2. 假值相等比较
  • 如图所示(绿色表示true,橙色表示false):

  • 其中几个比较容易错的假值相等比较

    '0' == false; // true,'0'先强制类型转换为了0 → 0 == false → true
    false == 0; // true
    false == ''; // true
    false == []; // true
    '' == 0; // true
    '' == [] // true
    0 == []; // true
    
2.2.3. 极端情况
例1
[] == ![] // true
  • 根据 ToBoolean 规则,它会进行布尔值的显式强制类型转换(同时反转奇偶校验位)。

  • 所以 [] == ![] 变成了 [] == false。

  • 前面我们讲过 false == [],最后的结果就顺理成章了。

例2
2 == [2]; // true
  • [2] 会通过行 ToPrimitive 转换为 "2",然后通过 ToNumber 转换为 2
例3
'' == [null]; // true
  • [null] 会直接转换为 ""。
例4
0 == '\n'; // true

3. 参考资料

  • 《你不知道的JavaScript(中卷)》 第4章