「这是我参与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) = 1Number(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章