JS 中 == 不檢查類(lèi)型?不,你錯(cuò)了!
Hello,大家好,我是 Sunday。
我們知道在 JS 中有兩種判斷相等的方式,即:== 和 ===。并且很多同學(xué)會(huì)認(rèn)為“==”運(yùn)算符(通常稱(chēng)為“松散相等”或簡(jiǎn)稱(chēng)“相等”)是不精準(zhǔn)的。
“==”不關(guān)心數(shù)據(jù)類(lèi)型,只關(guān)心值。
例如如下代碼:
1 == '1' // true
而“===”運(yùn)算符,即“嚴(yán)格相等”,可以有效地驗(yàn)證操作數(shù)的值和類(lèi)型:
1 === '1' // false
但是這種理解真的是完全正確的嗎?最近有一個(gè)同學(xué)在面試的時(shí)候被深入問(wèn)到了這個(gè)問(wèn)題,咱們來(lái)看一下:
“==”和“===” 的執(zhí)行邏輯
先說(shuō)結(jié)論,其實(shí):“==”和“===”這兩個(gè)運(yùn)算符都優(yōu)先考慮數(shù)據(jù)類(lèi)型,并在執(zhí)行各自的算法之前執(zhí)行類(lèi)型檢查。事實(shí)上,即使是“==”運(yùn)算符在評(píng)估值之前也會(huì)驗(yàn)證類(lèi)型。
根據(jù) JavaScript 規(guī)范參考 ECMAScript 262 文檔,概述了 IsLooselyEqual算法 ,負(fù)責(zé)處理“==”操作。這里說(shuō)明了涉及 14 個(gè)步驟的綜合過(guò)程。值得注意的是,這個(gè)過(guò)程大量涉及類(lèi)型檢查,如涉及Type(x)的初始步驟所證明的那樣,并引入強(qiáng)制作為整個(gè)操作的重要方式。
圖片
下方描述為翻譯后的結(jié)果:
// 在線(xiàn)地址:https://262.ecma-international.org/14.0/?_gl=1*1ddd25w*_ga*MTAzMTk1MjUwNS4xNzA0MTgwNDk0*_ga_TDCK4DWEPP*MTcwNDE4MDQ5NC4xLjAuMTcwNDE4MDQ5NC4wLjAuMA..&_ga=2.72864531.1838071013.1704180495-1031952505.1704180494#sec-islooselyequal
7.2.14 松散相等 ( x, y )
抽象操作 IsLooselyEqual 接受參數(shù) x(ECMAScript 語(yǔ)言值)和 y(ECMAScript 語(yǔ)言值),并返回包含布爾值的正常完成或拋出完成。 它提供 == 運(yùn)算符的語(yǔ)義。 調(diào)用時(shí)它執(zhí)行以下步驟:
1. 如果 Type(x) 是 Type(y),則
A。 A。 返回 IsStrictlyEqual(x, y)。
2. 如果 x 為 null 并且 y 未定義,則返回 true。
3. 如果 x 未定義且 y 為 null,則返回 true。
4. 注意:此步驟已在 B.3.6.2 節(jié)中替換。
5. 如果 x 是數(shù)字且 y 是字符串,則返回! IsLooselyEqual(x, !ToNumber(y))。
6. 如果 x 是字符串且 y 是數(shù)字,則返回! IsLooselyEqual(!ToNumber(x), y)。
7. 如果 x 是 BigInt 并且 y 是 String,則
A。 令 n 為 StringToBigInt(y)。
b. 如果 n 未定義,則返回 false。
C。 返回 ! IsLooselyEqual(x, n)。
8. 如果 x 是 String 并且 y 是 BigInt,則返回! IsLooselyEqual(y, x)。
9. 如果 x 是布爾值,則返回! IsLooselyEqual(!ToNumber(x), y)。
10. 如果 y 是布爾值,則返回! IsLooselyEqual(x, !ToNumber(y))。
11. 如果 x 是字符串、數(shù)字、BigInt 或符號(hào)并且 y 是對(duì)象,則返回! IsLooselyEqual(x, ? ToPrimitive(y))。
12. 如果 x 是對(duì)象并且 y 是字符串、數(shù)字、BigInt 或符號(hào),則返回! IsLooselyEqual(? ToPrimitive(x), y)。
13. 如果 x 是 BigInt 并且 y 是 Number,或者如果 x 是 Number 并且 y 是 BigInt,則
A。 A。 如果 x 不是有限的或 y 不是有限的,則返回 false。
b. b. 如果 ?(x) = ?(y),則返回 true; 否則返回 false。
14. 返回 false。
強(qiáng)制轉(zhuǎn)換涉及將一種類(lèi)型的值轉(zhuǎn)換為另一種類(lèi)型,可以通過(guò)有意操作顯式轉(zhuǎn)換,也可以通過(guò) JavaScript 機(jī)制隱式轉(zhuǎn)換,無(wú)需任何用戶(hù)干預(yù)。
另外一個(gè)有趣的方法,當(dāng)兩種類(lèi)型匹配時(shí),將使用 IsStrictlyEqual 算法(步驟 1.a),該算法與“===”運(yùn)算符使用的算法完全相同。
圖片
翻譯之后為:
// 在線(xiàn)地址:https://262.ecma-international.org/14.0/?_gl=1*1ddd25w*_ga*MTAzMTk1MjUwNS4xNzA0MTgwNDk0*_ga_TDCK4DWEPP*MTcwNDE4MDQ5NC4xLjAuMTcwNDE4MDQ5NC4wLjAuMA..&_ga=2.72864531.1838071013.1704180495-1031952505.1704180494#sec-isstrictlyequal
7.2.15 IsStrictlyEqual(x,y)
抽象操作 IsStrictlyEqual 接受參數(shù) x(ECMAScript 語(yǔ)言值)和 y(ECMAScript 語(yǔ)言值)并返回布爾值。 它提供 === 運(yùn)算符的語(yǔ)義。 調(diào)用時(shí)它執(zhí)行以下步驟:
1. 如果 Type(x) 不是 Type(y),則返回 false。
2. 如果 x 是一個(gè)數(shù)字,那么
A。 返回 Number::equal(x, y)。
3. 返回 SameValueNonNumber(x, y)。
讓我們深入研究 IsLooselyEqual的第五步(如果 x 是數(shù)字且 y 是字符串,則返回! IsLooselyEqual(x, !ToNumber(y))):
圖片
根據(jù)提供的代碼片段:
1 == "1" // true
// x 是數(shù)字 (1)
// y 是字符串 ("1")
此處,調(diào)用 ToNumber (y)函數(shù)時(shí)會(huì)發(fā)生隱式 強(qiáng)制轉(zhuǎn)換。此強(qiáng)制過(guò)程將值(y,即“1”)從其原始類(lèi)型(在本例中為String)轉(zhuǎn)換為不同的類(lèi)型(在本例中為Number)。在比較值之前,此步驟涉及類(lèi)型檢查和強(qiáng)制,將兩個(gè)值對(duì)齊到同一類(lèi)型(Number),從而實(shí)現(xiàn)兩個(gè)數(shù)字之間的直接比較。
1 == "1" // true
//步驟 1。
// -> 檢查兩種類(lèi)型
// -> 兩種類(lèi)型不同
// -> 執(zhí)行 IsLooselyEqual 的后續(xù)步驟
//步驟 2。
// -> "1 " (String) 將變成 1 (Number) - 強(qiáng)制機(jī)制
//Step 3.
// -> 將值 1(數(shù)字,從左側(cè))與值 1(數(shù)字,在右側(cè))進(jìn)行比較
在這種情況下,嚴(yán)格相等運(yùn)算符將返回 false,如前面提供的內(nèi)容所示。由于兩種類(lèi)型不同,因此 === 會(huì)判定為 false:
1 === "1" // false
//步驟 1.
// -> 檢查兩種類(lèi)型
// -> 兩種類(lèi)型不同
// -> 結(jié)束進(jìn)程,返回 false
總結(jié)
兩個(gè)比較運(yùn)算符都會(huì)檢查類(lèi)型,但松散的相等運(yùn)算符(==) 需要執(zhí)行額外的步驟。
- 它首先檢查比較值的類(lèi)型,如果它們不同,則將它們對(duì)齊到相同類(lèi)型(使用隱式強(qiáng)制)。
- 然后繼續(xù)進(jìn)行值比較。 使用嚴(yán)格相等運(yùn)算符(===)時(shí),不涉及該額外步驟。在值不同的情況下,它直接返回 false。