JavaScript開發者Douglas Crockford曾將JavaScript的==
和!=
運算符稱為應該避免的“邪惡雙胞胎”。然而,一旦你理解了它們,這些運算符並沒有那麼糟糕,實際上可能很有用。本文將探討==
和!=
,解釋它們的工作原理,並幫助你更好地了解它們。
關鍵要點
==
和!=
運算符並非天生邪惡;它們在比較不同類型的值時執行類型強制轉換,這既有用又棘手。 ===
和!==
進行直接的類型和值比較,無需強制轉換,這更清晰,通常建議避免意外結果。當需要類型強制轉換或比較類型可能動態變化的值時,使用==
和!=
。 ==
和!=
比較期間如何強制轉換類型,以便更準確地預測結果並避免常見陷阱。 ==
和!=
如何在各種場景中運行,例如將字符串與數字或對象與原始值進行比較,以鞏固理解。 ==
和!=
並非可怕,但它們需要對JavaScript的類型強制轉換規則有很好的理解,才能在代碼中有效且安全地使用。 有問題的==
和!=
運算符
JavaScript語言包含兩組相等運算符:===
和!==
,以及==
和!=
。理解為什麼有兩組相等運算符以及在哪些情況下使用哪個運算符一直是許多人困惑的根源。 ===
和!==
運算符不難理解。當兩個操作數類型相同且值相同時,===
返回true
,而!==
返回false
。但是,當值或類型不同時,===
返回false
,!==
返回true
。 ==
和!=
運算符在兩個操作數類型相同時的行為相同。但是,當類型不同時,JavaScript會將一個操作數強制轉換為另一種類型,以使操作數在比較之前兼容。結果通常令人困惑,如下所示:
"this_is_true" == false // false "this_is_true" == true // false
因為只有兩個可能的布爾值,你可能會認為其中一個表達式應該計算為true
。但是,它們都計算為false
。當你假設傳遞關係(如果a等於b且b等於c,則a等於c)應該適用時,會出現額外的混淆:
"this_is_true" == false // false "this_is_true" == true // false
此示例表明==
缺乏傳遞性。如果空字符串等於數字0,並且如果數字0等於由字符0組成的字符串,則空字符串應該等於由0組成的字符串。但事實並非如此。當通過==
或!=
比較操作數時遇到不兼容的類型時,JavaScript會將一種類型強制轉換為另一種類型以使其可比較。相反,在使用===
和!==
時,它永遠不會執行類型強制轉換(這會導致性能略微提高)。由於類型不同,===
在第二個示例中總是返回false
。理解控制JavaScript如何將操作數強制轉換為不同類型以便在應用==
和!=
之前兩個操作數類型兼容的規則,可以幫助你確定何時更適合使用==
和!=
,並對使用這些運算符充滿信心。在下一節中,我們將探討與==
和!=
運算符一起使用的強制轉換規則。
==
和!=
如何工作?
學習==
和!=
如何工作的最佳方法是研究ECMAScript語言規範。本節重點介紹ECMAScript 262。規範的第11.9節介紹了相等運算符。 ==
和!=
運算符出現在語法產生式EqualityExpression
和EqualityExpressionNoIn
中。 (與第一個產生式不同,第二個產生式避免了in
運算符。)讓我們檢查一下下面顯示的EqualityExpression
產生式。
'' == 0 // true 0 == '0' // true '' == '0' // false
根據此產生式,相等表達式要么是關係表達式,要么是通過==
等於關係表達式的相等表達式,要么是通過!=
不等於關係表達式的相等表達式,等等。 (我忽略了===
和!==
,它們與本文無關。)第11.9.1節提供了關於==
如何工作的以下信息:
產生式
EqualityExpression : EqualityExpression == RelationalExpression
的計算如下:
- 令
lref
為計算EqualityExpression
的結果。- 令
lval
為GetValue(lref)
。- 令
rref
為計算RelationalExpression
的結果。- 令
rval
為GetValue(rref)
。- 返回執行抽象相等比較
rval == lval
的結果。 (參見11.9.3。)
第11.9.2節提供了關於!=
如何工作的類似信息:
產生式
EqualityExpression : EqualityExpression != RelationalExpression
的計算如下:
- 令
lref
為計算EqualityExpression
的結果。- 令
lval
為GetValue(lref)
。- 令
rref
為計算RelationalExpression
的結果。- 令
rval
為GetValue(rref)
。- 令
r
為執行抽象相等比較rval != lval
的結果。 (參見11.9.3。)- 如果
r
為true
,則返回false
。否則,返回true
。
lref
和rref
是==
和!=
運算符左右兩側的引用。每個引用都傳遞給GetValue()
內部函數以返回相應的值。 ==
和!=
如何工作的核心由抽象相等比較算法指定,該算法在第11.9.3節中給出:
比較
x == y
,其中x
和y
是值,產生true
或false
。此類比較如下進行:
- 如果
Type(x)
與Type(y)
相同,則
- 如果
Type(x)
是Undefined
,則返回true
。- 如果
Type(x)
是Null
,則返回true
。- 如果
Type(x)
是Number
,則
- 如果
x
是NaN
,則返回false
。- 如果
y
是NaN
,則返回false
。- 如果
x
與y
是相同的數字值,則返回true
。- 如果
x
是 0且y
是-0,則返回true
。- 如果
x
是-0且y
是 0,則返回true
。- 返回
false
。- 如果
Type(x)
是String
,則如果x
和y
是完全相同的字符序列(長度相同且對應位置的字符相同),則返回true
。否則,返回false
。- 如果
Type(x)
是Boolean
,則如果x
和y
都是true
或都是false
,則返回true
。否則,返回false
。- 如果
x
和y
引用同一個對象,則返回true
。否則,返回false
。- 如果
x
是null
且y
是undefined
,則返回true
。- 如果
x
是undefined
且y
是null
,則返回true
。- 如果
Type(x)
是Number
且Type(y)
是String
,則返回比較x == ToNumber(y)
的結果。- 如果
Type(x)
是String
且Type(y)
是Number
,則返回比較ToNumber(x) == y
的結果。- 如果
Type(x)
是Boolean
,則返回比較ToNumber(x) == y
的結果。- 如果
Type(y)
是Boolean
,則返回比較x == ToNumber(y)
的結果。- 如果
Type(x)
是String
或Number
且Type(y)
是Object
,則返回比較x == ToPrimitive(y)
的結果。- 如果
Type(x)
是Object
且Type(y)
是String
或Number
,則返回比較ToPrimitive(x) == y
的結果。- 返回
false
。
步驟1在此算法中執行時操作數類型相同。它表明undefined
等於undefined
,null
等於null
。它還表明沒有任何東西等於NaN
(非數字),兩個相同的數值相等, 0等於-0,兩個具有相同長度和字符序列的字符串相等,true
等於true
,false
等於false
,以及對同一對象的兩個引用相等。步驟2和3顯示了為什麼null != undefined
返回false
。 JavaScript認為這些值相同。從步驟4開始,算法變得有趣。此步驟側重於Number
和String
值之間的相等性。當第一個操作數是Number
而第二個操作數是String
時,第二個操作數通過ToNumber()
內部函數轉換為Number
。表達式x == ToNumber(y)
表示遞歸;從第11.9.1節開始的算法被重新應用。步驟5等效於步驟4,但第一個操作數的類型為String
,必須轉換為Number
類型。步驟6和7將布爾操作數轉換為Number
類型並遞歸。如果另一個操作數是布爾值,它將在下次執行此算法時轉換為Number
,這將再次遞歸。從性能的角度來看,你可能希望確保兩個操作數都是布爾類型以避免兩個遞歸步驟。步驟9顯示,如果任一操作數的類型為Object
,則此操作數通過ToPrimitive()
內部函數轉換為原始值,並且算法遞歸。最後,算法認為兩個操作數不相等並在步驟10中返回false
。雖然詳細,但抽象相等比較算法相當容易理解。但是,它引用了一對內部函數ToNumber()
和ToPrimitive()
,其內部工作需要公開才能完全理解該算法。 ToNumber()
函數將其參數轉換為Number
,並在第9.3節中進行了描述。以下列表總結了可能的非數字參數和等效返回值:
Undefined
,則返回NaN
。 Null
,則返回 0。 true
,則返回1。如果參數是布爾值false
,則返回 0。 Number
,則返回輸入參數——沒有轉換。 String
,則應用第9.3.1節“應用於字符串類型的ToNumber”。返回根據語法指示的與字符串參數相對應的數值。如果參數不符合指示的語法,則返回NaN
。例如,參數“xyz”導致返回NaN
。此外,參數“29”導致返回29。 Object
,則應用以下步驟:primValue
為ToPrimitive(输入参数, 提示Number)
。 ToNumber(primValue)
。 ToPrimitive()
函數接受一個輸入參數和一個可選的PreferredType
參數。輸入參數轉換為非對像類型。如果對象能夠轉換為多個原始類型,則ToPrimitive()
使用可選的PreferredType
提示來偏向首選類型。轉換如下進行:
Undefined
,則返回輸入參數(Undefined
)——沒有轉換。 Null
,則返回輸入參數(Null
)——沒有轉換。 Boolean
,則返回輸入參數——沒有轉換。 Number
,則返回輸入參數——沒有轉換。 String
,則返回輸入參數——沒有轉換。 Object
,則返回與輸入參數相對應的默認值。通過調用對象的[[DefaultValue]]
內部方法並傳遞可選的PreferredType
提示來檢索對象的默認值。 [[DefaultValue]]
的行為在第8.12.8節中為所有原生ECMAScript對象定義。 本節介紹了相當多的理論。在下一節中,我們將通過提供涉及==
和!=
的各種表達式並逐步完成算法步驟來評估它們,從而轉向實踐。
了解邪惡雙胞胎
現在我們已經根據ECMAScript規範了解了==
和!=
的工作原理,讓我們通過探索涉及這些運算符的各種表達式來充分利用這些知識。我們將逐步介紹如何評估這些表達式,並找出它們為什麼是true
或false
。對於我的第一個示例,請考慮文章開頭附近介紹的以下表達式對:
"this_is_true" == false // false "this_is_true" == true // false
按照抽象相等比較算法,按照以下步驟評估這些表達式:
typeof "this_is_true"
返回“string”,而typeof false
或typeof true
返回“boolean”。 Boolean
。表達式轉換為"this_is_true" == ToNumber(false)
和"this_is_true" == ToNumber(true)
。 ToNumber(false)
返回 0,ToNumber(true)
返回1,這將表達式分別簡化為"this_is_true" == 0
和"this_is_true" == 1
。此時算法遞歸。 String
,右操作數的類型為Number
。表達式轉換為ToNumber("this_is_true") == 0
和ToNumber("this_is_true") == 1
。 ToNumber("this_is_true")
返回NaN
,這將表達式分別簡化為NaN == 0
和NaN == 1
。此時算法遞歸。 NaN
、 0和1的類型都是Number
。跳過不適用的步驟1.a和1.b。但是,步驟1.c.i適用,因為左操作數是NaN
。算法現在返回false
(NaN
不等於任何東西,包括它本身)作為每個原始表達式的值,並回溯堆棧以完全退出遞歸。 我的第二個示例(基於《銀河系漫遊指南》中對生命意義的解釋)通過==
將一個對象與一個數字進行比較,返回true
:
"this_is_true" == false // false "this_is_true" == true // false
以下步驟顯示了JavaScript如何使用抽象相等比較算法得出true
作為表達式的值:
Object
,右操作數的類型為Number
。表達式轉換為ToPrimitive(lifeAnswer) == 42
。 ToPrimitive()
調用lifeAnswer
的[[DefaultValue]]
內部方法,沒有提示。根據ECMAScript 262規範中的第8.12.8節,[[DefaultValue]]
調用toString()
方法,該方法返回“42”。表達式轉換為"42" == 42
,算法遞歸。 String
,右操作數的類型為Number
。表達式轉換為ToNumber("42") == 42
。 ToNumber("42")
返回42,表達式轉換為42 == 42
。算法遞歸併執行步驟1.c.iii。因為數字相同,所以返回true
,並且遞歸展開。 對於我的最後一個示例,讓我們找出為什麼以下序列沒有顯示傳遞性,其中第三個比較將返回true
而不是false
:
"this_is_true" == false // false "this_is_true" == true // false
以下步驟顯示了JavaScript如何使用抽象相等比較算法得出true
作為'' == 0
的值。
ToNumber('') == 0
,這轉換為0 == 0
,算法遞歸。 (規範的第9.3.1節指出StringNumericLiteral ::: [empty]的MV[數學值]為0。換句話說,空字符串的數值為0。)true
(並展開遞歸)。 以下步驟顯示了JavaScript如何使用抽象相等比較算法得出true
作為0 == '0'
的值:
0 == ToNumber('0')
,這轉換為0 == 0
,算法遞歸。 true
(並展開遞歸)。 最後,JavaScript執行抽象相等比較算法中的步驟1.d以得出true
作為'' == '0'
的值。因為這兩個字符串的長度不同(0和1),所以返回false
。
結論
你可能想知道為什麼你應該費心使用==
和!=
。畢竟,之前的例子已經表明,由於類型強制轉換和遞歸,這些運算符可能比===
和!==
運算符慢。你可能希望使用==
和!=
,因為在某些情況下===
和!==
沒有優勢。考慮以下示例:
"this_is_true" == false // false "this_is_true" == true // false
typeof
運算符返回一個String
值。因為String
值與另一個String
值(“object”)進行比較,所以不會發生類型強制轉換,==
與===
一樣高效。也許從未遇到過===
的JavaScript新手會發現這樣的代碼更清晰。類似地,以下代碼片段不需要類型強制轉換(兩個操作數的類型都是Number
),因此!=
與!==
一樣高效:
'' == 0 // true 0 == '0' // true '' == '0' // false
這些示例表明,==
和!=
適用於不需要強制轉換的比較。當操作數類型不同時,===
和!==
是最佳選擇,因為它們返回false
而不是意外值(例如,false == ""
返回true
)。如果操作數類型相同,則沒有理由不使用==
和!=
。也許是時候停止害怕邪惡雙胞胎了,一旦你了解了它們,它們就不那麼邪惡了。
JavaScript相等和比較運算符的常見問題解答 (FAQs)
==
和===
在JavaScript中的區別是什麼? 在JavaScript中,==
和===
都是比較運算符。但是,它們在比較值的方式上有所不同。 ==
運算符(也稱為鬆散相等運算符)在比較之前執行類型強制轉換。這意味著如果你比較兩種不同類型的值,JavaScript將在執行比較之前嘗試將一種類型轉換為另一種類型。另一方面,===
運算符(稱為嚴格相等運算符)不執行類型強制轉換。它同時比較值和類型,這意味著如果兩個值類型不同,JavaScript會認為它們不相等。
===
而不是==
? 通常建議在JavaScript中使用===
而不是==
,因為它提供更嚴格的比較,這意味著它不執行類型強制轉換並檢查值和類型。這可以幫助避免在比較不同類型的值時出現意外結果。例如,當使用==
時,JavaScript會認為數字0和空字符串“”相等,因為它在比較之前轉換類型。但是,使用===
,它們將被認為不相等,因為它們類型不同。
JavaScript中的類型強制轉換是指將值從一種數據類型自動或隱式轉換為另一種數據類型。當對不同類型的操作數使用運算符或需要某種類型時,就會發生這種情況。例如,當使用鬆散相等運算符(==
)時,JavaScript將在進行比較之前嘗試將操作數轉換為通用類型。
在JavaScript中,對象按引用比較,而不是按值比較。這意味著即使兩個對象具有完全相同的屬性和值,它們也不被認為相等,因為它們引用內存中的不同對象。對像被認為相等的唯一情況是它們引用完全相同的對象。
==
和!=
在JavaScript中的區別是什麼? ==
和!=
都是JavaScript中的比較運算符。 ==
運算符檢查兩個操作數的值是否相等,必要時執行類型強制轉換。另一方面,!=
運算符檢查兩個操作數的值是否不相等,必要時也執行類型強制轉換。
===
和!==
在JavaScript中的區別是什麼? ===
和!==
都是JavaScript中的比較運算符。 ===
運算符檢查兩個操作數的值是否相等,同時考慮值和類型。另一方面,!==
運算符檢查兩個操作數的值是否不相等,同時考慮值和類型。
在JavaScript中,數組是對象,按引用比較,而不是按值比較。這意味著即使兩個數組按相同的順序包含相同的元素,它們也不被認為相等,因為它們引用內存中的不同對象。要按其內容比較兩個數組,你需要分別比較每個元素。
null
和undefined
的比較? 在JavaScript中,null
和undefined
被認為是鬆散相等(==
),因為它們都表示值的缺失。但是,它們不是嚴格相等(===
),因為它們的類型不同。
在JavaScript中,比較運算符具有相同的優先級級別。它們從左到右計算。但是,重要的是要注意,它們的優先級低於算術和按位運算符,但高於邏輯運算符。
是的,你可以在JavaScript中將比較運算符與字符串一起使用。比較字符串時,JavaScript使用詞法(字典)順序。但是,重要的是要注意,大寫字母被認為比小寫字母“小”,因為它們的ASCII值較小。
以上是不要害怕邪惡的雙胞胎-Sitepoint的詳細內容。更多資訊請關注PHP中文網其他相關文章!