前言
- 在学习本章内容前,你需要一些前置知识,你可以移步到此专栏 你学了也不懂的JavaScript。
- 通过本篇文章的学习你将学习到
==
和===
操作符的区别,以及如何让a == 2 && a== 3
为true
。 - 在平时的面试中,我们可能会遇到一个问题,就是
==
和===
有啥区别,下面我们就来模拟一下场景:
- 面试官: 你知道宽松相等和严格相等有什么区别吗?
- 我的内心: 很好,这我知道,难不倒我,到我开始表现的时候了😊
- 我:这很简单啊,
==
不就是检查值是否相等嘛,===
检查值和类型是否相等嘛; - 面试官: ...... 你先回去等通知吧👋👋👋
- 此时门外一个人敲门进来了,我来我来,这个我会;
==
允许在相等比较中进行强制类型转换,而===
不允许;- 面试官: 很好,你面试通过了,这边呢,工资给你开到3000...
- 好了,水吹完了,那么接下来我们就开始去讲解一下
==
是怎么在比较中进行强制类型转化的,以及是怎么样的转换规则,通过这篇文章你将会学到很多奇奇怪怪的知识。
性能
==
似乎比===
做的事情更多,因为它还要对值的类型进行强制类型转换,但是实际上虽然强制类型转换确实要多花点时间,但仅仅是微秒级(百万分之一秒)的差别而已。- 如果两个值的类型不同,我们就需要考虑有没有强制类型转换的必要,有就用
==
,没有就用===
,冉在乎性能。
抽象相等
- ES5规范11.9.3 节的
抽象相等比较算法
定义了==
运算符的行为,在使用==
比较的时候,当使用==
对x和y进行比较时,会返回true
或者false
的值,它们主要遵循以下的规则:
- 如果
x
和y
的类型相同:- 如果
x
是undefined
,返回true
; - 如果
x
是null
,返回true
; - 如果
x
是Number
类型,则:- 如果
x
是NaN
,则返回false
; - 如果
y
是NaN
,则返回false
; - 如果
x
和y
相同,则返回true
; - 如果
x
是-0
和y
是+0
,则返回true
; - 如果
y
是-0
和x
是+0
,则返回true
; - 其他情况返回
false
;
- 如果
- 如果
x
是string
类型,并且x
和y
的完全相同的值(长度相同,对应位置的字符相同),则返回true
; - 如果
x
的Boolean
类型,并且x
和y
都是true
或者false
,则返回true
,否则返回false
; - 如果
x
和y
引用同一个对象,则返回true
,否则返回false
;
- 如果
- 如果
x
为null
且y
为undefined
,则返回true
; - 如果
y
为null
且x
为undefined
,则返回true
; - 如果
x
是number
类型且y
是string
类型,则返回x == ToNumber(y)
的比较结果; - 如果
y
是number
类型且x
是string
类型,则返回y == ToNumber(x)
的比较结果; - 如果
x
是boolean
类型,返回ToNumber(x) == y
的比较结果; - 如果
y
是boolean
类型,返回ToNumber(y) == x
的比较结果; - 如果
x
是string
类型或者number
类型,并且y
是object
类型,返回x == ToPrimitive(y)
的比较结果; - 如果
y
是string
类型或者number
类型,并且x
是object
类型,返回y == ToPrimitive(x)
的比较结果; - 否则返回
false
;
- 抽象相等的这些规则正是隐式强制类型转换不受人喜爱的原因,但是认真一看规则,其实简单明了。
字符串和数字之间的相等比较
const a = 77;
const b = "77";
console.log(a === b); // false
console.log(a == b); // true
- 因为没强制类型转换,所以
a === b
为false
,77
和"77"
不相等。 - 而
a == b
是宽松相等,即如果两个值的类型不同,则对其中之一或者两者都进行强制类型转换,具体怎么转换的,请看定义,它们是这样的:
- 如果
x
是number
类型且y
是string
类型,则返回x == ToNumber(y)
的比较结果; - 如果
y
是number
类型且x
是string
类型,则返回y == ToNumber(x)
的比较结果;
- 也就是说,
a == b
在代码中实际上是这样的行为:
const a = 77;
const b = "77";
console.log(a === Number(b)); // true
其他类型和布尔值之间的相等比较
==
最容易出错的是一个地方是true
和false
于其他类型之间的相等比较,例如:
const a = "77";
const b = true;
console.log(a == b); // false
- 我们都知道
"77"
是一个真值,为什么==
的结果不是true
呢?因为规范是这样定义的:
- 如果
x
是boolean
类型,返回ToNumber(x) == y
的比较结果; - 如果
y
是boolean
类型,返回ToNumber(y) == x
的比较结果;
- 首先
b
是boolean
类型,所以ToNumber(b)
将b
的类型强制转换为1
,变成1 == '77'
,二者的类型仍然不同,"77"
根据规则被强制类型转换为77
,最后变成1 == 77
,所以结果输出为false
;
null 和 undefined 之间的相等比较
null
和undefined
之间的==
也设计隐式强制类型转换,ES5规范
是这样规定的:
- 如果
x
为null
且y
为undefined
,则返回true
; - 如果
y
为null
且x
为undefined
,则返回true
;
- 在
==
中null
和undefined
相等(它们也与其自身相等),除此之外其他值都不和它们两个相等。 - 这也就是说,在
==
中null
和undefined
是一回事,可以相互进行隐式强制类型转换:
var a = null;
var b = undefined;
console.log(a == b); // true
console.log(a == null); // true
console.log(b == null); // true
console.log(a == false); // false
console.log(b == false); // false
console.log(a == ""); // false
console.log(b == ""); // false
console.log(a == 0); // false
console.log(b == 0); // false
null
heundefined
之间的强制类型转换是安全可靠的,上例中除null
和undefined
以外的其他值均为无法返回true
的结果。
对象和非对象之间的相等比较
- 对于对象和基本类型之间的相等比较,
ES5规范
是遵循这样的规则:
- 如果
x
是string
类型或者number
类型,并且y
是object
类型,返回x == ToPrimitive(y)
的比较结果; - 如果
y
是string
类型或者number
类型,并且x
是object
类型,返回y == ToPrimitive(x)
的比较结果;
- 例如:
var a = 77;
var b = [77];
console.log(a == b); // true
[77]
首先调用了ToPrimitive
抽象操作,返回"77"
,变成"77" == 77
,然后又变成77 == 77
,最后二者相等。其中代码的转变过程是以下的形式:
var a = 77;
var b = [77];
console.log(a === Number(b.toString())); // true
- 我们再来看一个
string
类型和object
类型的例子:
var a = "abc";
var b = Object(a);
console.log(a === b); // false
console.log(a == b); // b
-
a == b
结果为true
,因为b
通过TOPrimitive
进行强制类型转换(拆封),并返回基本数据类型值"abc"
,与a
相等。 -
但是规则总有特例,原因是
==
算法中其他优先级更高的原则。例如:
var foo = null;
var bar = Object(foo);
console.log(foo == bar); // false
var a = undefined;
var b = Object(a);
console.log(a == b); // false
var c = NaN;
var d = Object(NaN);
console.log(c == d); // false
- 因为没有对应的封装对象,所以
null
和undefined
不能够被封装和Object()
均返回一个空对象,你也可以理解成空对象调用toString()
方法返回的值是"[object Object]"
,不等于null
和undefined
。 NaN
能够被封装为数字封装对象,但拆封之后NaN == NaN
返回false,因为NaN
不等于NaN
。
希望你永远不会用到
- 在上面的讲解中,我们已经介绍了
==
中的隐式强制类型转换,现在来看一下那些需要特别注意和避免的比较少见的情况。
返回其他的数字
- 首先来看看更改内置原生模型会导致哪些奇怪的结果:
Number.prototype.valueOf = function () {
return 3;
};
console.log(new Number(77) == 3); // true
2 == 3
不会有这样的问题,因为2
和3
都是数字基本类型值,不会调用Number.prototype.valueOf()
方法。而Number(2)
涉及ToPrimitive
强制类型转换,因此会调用valueOf()
。- 再来看一种情况,这个你可能面试题里经常看到,
a == 2 && a == 5
在什么情况下为true
?
if (a == 2 && a == 3) {
// ...
}
- 你也许觉得不可能,因为
a
不会同时等于2
和3
。但 同时似乎说的不对,因为a == 2
在a == 3
之前执行。如果让a..valueOf()
每次调用都产生副作用,比如第一次返回2
,第二次返回3
,就会出现这样的情况。这实现起来很简单:
const a = {
value: 1,
};
a.valueOf = function () {
return this.value++;
};
if (a == 1 && a == 2 && a == 3) {
console.log("嗨,没想到吧,意不意外,惊不惊喜"); // 这里正常输出了
}
假值的相等比较
==
中的隐式强制类型转换最为让人不满的地方是假值的相等比较下面分别列出了常规和非常规的情况:
console.log("0" == null); // false
console.log("0" == undefined); // false
console.log("0" == false); // true
console.log("0" == NaN); // false
console.log("0" == 0); // true
console.log("0" == ""); // false
console.log(false == null); // false
console.log(false == undefined); // false
console.log(false == NaN); // false
console.log(false == 0); // true
console.log(false == ""); // true
console.log(false == []); // true
console.log(false == {}); // false
console.log("" == null); // false
console.log("" == undefined); // false
console.log("" == NaN); // false
console.log("" == 0); // true
console.log("" == []); // true
console.log("" == {}); // false
console.log(0 == null); // false
console.log(0 == undefined); // false
console.log(0 == NaN); // false
console.log(0 == []); // true
console.log(0 == {}); // false
- 接下来我们挑一些有代表性的例子来具体讲解一下:
- 其中这些例子中最好辨认的是在
==
比较中,NaN
与任何值相比都是false
,NaN
也是; - 在
"0" == false
中会对false
先转换成数字类型,即0
,变成了"0" == 0
,再对"0"
转换,最终变成了0 == 0
,所以输出为true
; - 在
false == []
中,会对[]
进行ToPrimitive
类型转换,转换成原始类型""
,这时候变成了false == ""
,所以输出结果为true
,至于为什么是true
,我想你应该懂了吧
- 接下来还有一些例子:
console.log([] == ![]); // true
console.log("" == [null]); // true
console.log("" == [undefined]); // true
-
喔喔喔,这是什么,怎么都输出了
true
,它们到底都干了啥? -
在第一个中,
!
运算符对[]
做了取反操作,因为[]
为真值,而![]
也就变成了false
了,所以[] == ![]
变成了[] == false
,前面我们有讲过这个比较的结果,所以最后的结果也很正常了。 -
在第二、三个例子中,
[null].toString()
最后返回""
,而难懂的是String(null)
返回的是"null"
,而String([Null])
去返回的是""
,这就是难懂的地方,这也是js
令人深入理解的原因。 -
所以
==
和===
选择哪一个取决于是否允许在相等比较中发生强制类型转换。 -
好了,本篇的内容讲解到此结束了,有什么不了解的可以在评论区留下你的疑问。
参考文章
- 书籍 `你不知道的JavaScript.
- ES5规范