如果 x 是 String 类型,那么
a. 如果 x 和 y 是完全相同的代码单元序列(在相应的索引处具有相同的长度和相同的代码单元),则返回 true;否则返回 false。
如果 x 是 Boolean 类型,那么
a. 如果 x 和 y 同时为 true,或同时为 false,则返回 true,否则返回 false。
如果 x 是 Symbol 类型,那么
a. 如果 x 和 y 都是相同的 Symbol 值,则返回 true,否则返回 false。
如果 x 和 y 是同一个引用值,则返回 true,否则返回 false。
几个点注意一下:
Assert 是断言。若以 Assert: 为开头的步骤(Step),明确了其算法的不变条件。例如上面的 SameValueNonNumeric 算法,第 1、2 步骤明确了 x 和 y 的具有相同的数据类型,且不为 Number 或 BigInt 类型。可以理解为,这两句断言是该算法的前提条件。
细心的同学可能会发现,类似第 5 条步骤:if Type(x) is String, then...,接着下一条不是 if Type(y) is String, then...,因为它没必要了,前面的步骤已经明确了它的类型是一致的,如果有反而多余了。回头再看 Number::equal(x, y) 算法,其中第 1、2 步骤分别说明了 x 为 NaN,y 为 NaN 的情况,因为它前置条件没有断言。下文的算法也有类似的情况。
如果第 3、4 步骤有人不理解的话,看这里。由于 Undefined、Null 类型对应的值分别只有 undefined、null。如果 x 为 Undefined 类型,则可以推断出 x 和 y 的值,只能是 undefined,因此返回 true。Null 同理。
function objectIs(x, y) {
if (x === y) {
// x === 0 => compare via infinity trick
return x !== 0 || (1 / x === 1 / y)
}
// x !== y => return true only if both x and y are NaN
return x !== x && y !== y
}
记住一句话:
一、概念
在 JavaScript 中我们经常会使用
===
(Strict equality,全等运算符)和==
(Equality,相等运算符)来比较两个操作数是否相等,它俩都返回一个布尔值的结果(否定形式分别是!==
和!=
)。两者的区别:
===
总是认为不同类型的操作数是不相等的。==
与前者不同,它会尝试强制类型转换且比较不同类型的操作数。(即如果操作数的类型不同,相等运算符会在比较之前尝试将它们转换为相同的类型)放一张很经典的图,相信很多小伙伴都看过了。
二、全等运算符
全等运算符(
===
和!==
)使用全等比较算法来比较两个操作数。从 MDN 可以看到,大致概括如下:
列举几个示例:
以上这些结果相信是毫无疑问的。但是,从写下这篇文章的时候 MDN 上关于
===
的描述并未涉及 Symbol、BigInt 类型。那么,我们直接看 ECMAScript 最新标准吧。(#sec-7.2.16)
看起来“好像”只是简单的几句话对吧,翻译一下:
需要注意的是,
Type(x)
是 ECMAScript 标准定义的抽象操作(Abstract Operation),并非是 JavaScript 的某个语法,它返回的是 8 种数据类型。如果你多读 ECMAScript 标准,就会发现很多抽象操作里都有引用到Type(x)
,假设每处都对Type(x)
进行描述显得多余重复,何不将它抽出来定义成一个抽象操作。(我猜 ECMAScript 标准修订者也是这么想的)原来它是这样的三句话,哎~
1. Number::equal(x, y)
#sec-6.1.6.1.13
翻译过来就是:
2. BigInt::equal(x, y)
#sec-6.1.6.2.13
如果
x
和y
均为 BigInt 类型,且具有相同的数学整数值(mathematical integer),则返回true
,否则返回false
。3. SameValueNonNumeric(x, y)
#sec-7.2.13
翻译过来就是:
4. 总结一下
三、全等运算符的“坑”
根据以上的比较算法,感觉
===
也并不是总“靠谱”。例如以下“反直觉”的判断:1. NaN
因此,
NaN !== NaN
结果为true
似乎没毛病,只是反人类、反直觉罢了。下面总结了几种方法,来判断一个值是否为
NaN
。因此,无法通过
Array.prototype.indexOf()
来确定NaN
在数组中的索引值。可使用 ES6 的
Array.prototype.includes
方法判断2. +0 与 -0
在 JavaScript 中,数字类型包括浮点数、
+Infinity
(正无穷)、-Infinity
(负无穷)和NaN
(not-a-number,非数字)。还有 ES2021 标准中增加了一种
BigInt
(原始)类型,表示极大的数字(非本文范围,不展开叙述)。其实,
+0
与-0
是不相等的,为什么?3. 处理以上两种特殊的情况
在 ES6 标准中,提出来一个方法
Object.is()
,对以上两种情况都做了“正确”的处理。而 ES5 可以这样去处理:
四、相等运算符
相等运算符(
==
和!=
)使用抽象相等比较算法比较两个操作数。1. ES5 相等比较算法
在 MDN 可以看到
x == y
比较的描述,如下:截止发文日期,我们可以看到它并没有关于 Symbol 和 BigInt 类型的描述,因此以上相等比较并不是最新的。
2. ES6+ 相等比较算法
目前 ECMAScript 标准最新的抽象相等比较算法如下:
翻译一下:
相等运算符与全等运算符(
===
)运算符之间最显着的区别在于,后者不尝试类型转换。相反,前者始终将不同类型的操作数视为不同。列举几个示例:
相信很多人在没有完全弄清楚相等运算符比较的【套路】之前,会很让人抓狂...
针对以上 13 条规则,再提炼总结一下(但是标准描述真的是非常地严谨 👍):
x === y
的结果。null
,另一个操作数是undefined
,则返回true
。x
是 BigInt 类型,另一个是操作数y
是 String 类型,那么会将 String 类型的操作数先转换为 BigInt 类型(假设转换结果为n
)。n
为NaN
,则返回false
。x == n
的结果。x == ToNumber(y)
的结果。ToPrimitive(x) == y
。NaN
、Infinity
或-Infinity
中的任何一个,则返回false
。x
和y
的数学值相等,则返回true
,否则返回false
。false
(包括以上转换过程出现TypeError
都会在这里返回false
)。五、相等比较常见示例
其实熟悉以上算法之后,遇到一些看似很“奇葩”的比较也不足为惧了。
1. 0 == false
根据
Type(x)
抽象操作规则,Type(0)
结果为Number
,Type(null)
结果为Null
,再结合比较算法,可以清楚地知道它按照第 13 点返回false
。2. [] == ![]
看到这个式子,千万别急眼,一步一步来......
我们来分析一下,根据运算符优先级,
!
(逻辑非)的优先级高于==
,因此优先执行![]
。而[]
属于真值(Truthy) ,所以![]
结果为false
。根据第 9 条规则,先将 Boolean 类型的值转换为 Number 类型,所以变成了:
再根据第 11 条规则,先将引用类型转换为原始类型,故进行的操作是
ToPrimitive([])
。(这步操作可能稍微复杂一点点,但别急)由于数组实例本身没有
@@toPrimitive
方法,且此时ToPrimitive
操作的hint
值为"default"
(根据ToPrimitive
规则,里面的其中一个步骤会将hint
设为"number"
),然后进行OrdinaryToPrimitive
操作,因此会先调用valueOf
方法,再调用toString
方法。根据第 5 条规则,将
""
转换为数值0
,即严格来说,其实还有一步的。根据第 1 条规则,返回
0 === 0
的结果。整个转换过程如下,因此
[] == ![]
比较结果为true
。3. {} == !{}
它看着跟前面一个例子很相似,转换过程同理,但结果是...
首先,根据操作符优先级顺序,先将
!{}
转换为false
。即变成了{} == false
的比较,然后将false
转换为0
,所以变成了。然后将
{}
转换为原始值,即执行ToPrimitive({})
操作。其中hint
为"default"
。所以先执行{}.toString()
方法,得到"[object Object]"
结果,由于结果已经是原始值,不再调用valueOf()
方法了。根据第 5 条规则,将
"[object Object]"
转换为数值,进行的操作是ToNumber("[object Object]")
,即执行方法Number("[object Object]")
,得到的结果是NaN
。由于
NaN
、0
都是 Number 类型,根据第 1 条规则,返回NaN === 0
的结果。而NaN
与任何操作数(包括其本身)进行全等比较,均返回false
。因此
{} == !{}
的结果为false
。整个过程如下:4. 10 == 10n
来看看 Number 类型与 BigInt 类型的相等比较。
根据规则第 12 条,且两个操作数并非是
NaN
、Infinity
、-Infinity
,因此比较两者的数学值(mathematical value),其数学值是相等的,所以结果为true
。五、其他
1. +、-、*、/、% 的隐式类型转换
除了
+
、-
既可以作为一元运算符、也可以是算术运算符,其余的均为算术运算符。当
+
、-
、*
、/
、%
均为算术运算符时,会将运算符两边的操作数先转换为 Number 类型(若已经是 Number 类型则无需再转换),再进行相应的算术运算。当
+
、-
作为一元运算符时,即只有一个运算符和操作数。前者将操作数转换为 Number 类型并返回。后者将操作数转换为 Number 类型,然后取反再返回。未完待续,拼命更新中...
六、参考