首頁 > web前端 > js教程 > 不要害怕邪惡的雙胞胎-Sitepoint

不要害怕邪惡的雙胞胎-Sitepoint

Jennifer Aniston
發布: 2025-02-22 08:58:11
原創
900 人瀏覽過

Don't Fear the Evil Twins - SitePoint

JavaScript開發者Douglas Crockford曾將JavaScript的==!=運算符稱為應該避免的“邪惡雙胞胎”。然而,一旦你理解了它們,這些運算符並沒有那麼糟糕,實際上可能很有用。本文將探討==!=,解釋它們的工作原理,並幫助你更好地了解它們。

關鍵要點

  • 理解基礎知識: JavaScript中的==!=運算符並非天生邪惡;它們在比較不同類型的值時執行類型強制轉換,這既有用又棘手。
  • 了解何時使用哪個: 使用===!==進行直接的類型和值比較,無需強制轉換,這更清晰,通常建議避免意外結果。當需要類型強制轉換或比較類型可能動態變化的值時,使用==!=
  • 學習強制轉換規則: 熟悉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節介紹了相等運算符。 ==!=運算符出現在語法產生式EqualityExpressionEqualityExpressionNoIn中。 (與第一個產生式不同,第二個產生式避免了in運算符。)讓我們檢查一下下面顯示的EqualityExpression產生式。

'' == 0   // true
0 == '0' // true
'' == '0' // false
登入後複製
登入後複製

根據此產生式,相等表達式要么是關係表達式,要么是通過==等於關係表達式的相等表達式,要么是通過!=不等於關係表達式的相等表達式,等等。 (我忽略了===!==,它們與本文無關。)第11.9.1節提供了關於==如何工作的以下信息:

產生式EqualityExpression : EqualityExpression == RelationalExpression的計算如下:

  1. lref為計算EqualityExpression的結果。
  2. lvalGetValue(lref)
  3. rref為計算RelationalExpression的結果。
  4. rvalGetValue(rref)
  5. 返回執行抽象相等比較rval == lval的結果。 (參見11.9.3。)

第11.9.2節提供了關於!=如何工作的類似信息:

產生式EqualityExpression : EqualityExpression != RelationalExpression的計算如下:

  1. lref為計算EqualityExpression的結果。
  2. lvalGetValue(lref)
  3. rref為計算RelationalExpression的結果。
  4. rvalGetValue(rref)
  5. r為執行抽象相等比較rval != lval的結果。 (參見11.9.3。)
  6. 如果rtrue,則返回false。否則,返回true

lrefrref==!=運算符左右兩側的引用。每個引用都傳遞給GetValue()內部函數以返回相應的值。 ==!=如何工作的核心由抽象相等比較算法指定,該算法在第11.9.3節中給出:

比較x == y,其中xy是值,產生truefalse。此類比較如下進行:

  1. 如果Type(x)Type(y)相同,則
    1. 如果Type(x)Undefined,則返回true
    2. 如果Type(x)Null,則返回true
    3. 如果Type(x)Number,則
      1. 如果xNaN,則返回false
      2. 如果yNaN,則返回false
      3. 如果xy是相同的數字值,則返回true
      4. 如果x是 0且y是-0,則返回true
      5. 如果x是-0且y是 0,則返回true
      6. 返回false
    4. 如果Type(x)String,則如果xy是完全相同的字符序列(長度相同且對應位置的字符相同),則返回true。否則,返回false
    5. 如果Type(x)Boolean,則如果xy都是true或都是false,則返回true。否則,返回false
    6. 如果xy引用同一個對象,則返回true。否則,返回false
  2. 如果xnullyundefined,則返回true
  3. 如果xundefinedynull,則返回true
  4. 如果Type(x)NumberType(y)String,則返回比較x == ToNumber(y)的結果。
  5. 如果Type(x)StringType(y)Number,則返回比較ToNumber(x) == y的結果。
  6. 如果Type(x)Boolean,則返回比較ToNumber(x) == y的結果。
  7. 如果Type(y)Boolean,則返回比較x == ToNumber(y)的結果。
  8. 如果Type(x)StringNumberType(y)Object,則返回比較x == ToPrimitive(y)的結果。
  9. 如果Type(x)ObjectType(y)StringNumber,則返回比較ToPrimitive(x) == y的結果。
  10. 返回false

步驟1在此算法中執行時操作數類型相同。它表明undefined等於undefinednull等於null。它還表明沒有任何東西等於NaN(非數字),兩個相同的數值相等, 0等於-0,兩個具有相同長度和字符序列的字符串相等,true等於truefalse等於false,以及對同一對象的兩個引用相等。步驟2和3顯示了為什麼null != undefined返回false。 JavaScript認為這些值相同。從步驟4開始,算法變得有趣。此步驟側重於NumberString值之間的相等性。當第一個操作數是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,則應用以下步驟:
    1. primValueToPrimitive(输入参数, 提示Number)
    2. 返回ToNumber(primValue)

ToPrimitive()函數接受一個輸入參數和一個可選的PreferredType參數。輸入參數轉換為非對像類型。如果對象能夠轉換為多個原始類型,則ToPrimitive()使用可選的PreferredType提示來偏向首選類型。轉換如下進行:

  1. 如果輸入參數是Undefined,則返回輸入參數(Undefined)——沒有轉換。
  2. 如果輸入參數是Null,則返回輸入參數(Null)——沒有轉換。
  3. 如果輸入參數的類型是Boolean,則返回輸入參數——沒有轉換。
  4. 如果輸入參數的類型是Number,則返回輸入參數——沒有轉換。
  5. 如果輸入參數的類型是String,則返回輸入參數——沒有轉換。
  6. 如果輸入參數的類型是Object,則返回與輸入參數相對應的默認值。通過調用對象的[[DefaultValue]]內部方法並傳遞可選的PreferredType提示來檢索對象的默認值。 [[DefaultValue]]的行為在第8.12.8節中為所有原生ECMAScript對象定義。

本節介紹了相當多的理論。在下一節中,我們將通過提供涉及==!=的各種表達式並逐步完成算法步驟來評估它們,從而轉向實踐。

了解邪惡雙胞胎

現在我們已經根據ECMAScript規範了解了==!=的工作原理,讓我們通過探索涉及這些運算符的各種表達式來充分利用這些知識。我們將逐步介紹如何評估這些表達式,並找出它們為什麼是truefalse。對於我的第一個示例,請考慮文章開頭附近介紹的以下表達式對:

"this_is_true" == false // false
"this_is_true" == true  // false
登入後複製
登入後複製
登入後複製
登入後複製
登入後複製
登入後複製

按照抽象相等比較算法,按照以下步驟評估這些表達式:

  1. 跳過步驟1,因為類型不同:typeof "this_is_true"返回“string”,而typeof falsetypeof true返回“boolean”。
  2. 跳過不適用的步驟2到6,因為它們與操作數類型不匹配。但是,步驟7適用,因為右參數的類型為Boolean。表達式轉換為"this_is_true" == ToNumber(false)"this_is_true" == ToNumber(true)
  3. ToNumber(false)返回 0,ToNumber(true)返回1,這將表達式分別簡化為"this_is_true" == 0"this_is_true" == 1。此時算法遞歸。
  4. 跳過不適用的步驟1到4,因為它們與操作數類型不匹配。但是,步驟5適用,因為左操作數的類型為String,右操作數的類型為Number。表達式轉換為ToNumber("this_is_true") == 0ToNumber("this_is_true") == 1
  5. ToNumber("this_is_true")返回NaN,這將表達式分別簡化為NaN == 0NaN == 1。此時算法遞歸。
  6. 進入步驟1,因為NaN、 0和1的類型都是Number。跳過不適用的步驟1.a和1.b。但是,步驟1.c.i適用,因為左操作數是NaN。算法現在返回falseNaN不等於任何東西,包括它本身)作為每個原始表達式的值,並回溯堆棧以完全退出遞歸。

我的第二個示例(基於《銀河系漫遊指南》中對生命意義的解釋)通過==將一個對象與一個數字進行比較,返回true

"this_is_true" == false // false
"this_is_true" == true  // false
登入後複製
登入後複製
登入後複製
登入後複製
登入後複製
登入後複製

以下步驟顯示了JavaScript如何使用抽象相等比較算法得出true作為表達式的值:

  1. 跳過不適用的步驟1到8,因為它們與操作數類型不匹配。但是,步驟9適用,因為左操作數的類型為Object,右操作數的類型為Number。表達式轉換為ToPrimitive(lifeAnswer) == 42
  2. ToPrimitive()調用lifeAnswer[[DefaultValue]]內部方法,沒有提示。根據ECMAScript 262規範中的第8.12.8節,[[DefaultValue]]調用toString()方法,該方法返回“42”。表達式轉換為"42" == 42,算法遞歸。
  3. 跳過不適用的步驟1到4,因為它們與操作數類型不匹配。但是,步驟5適用,因為左操作數的類型為String,右操作數的類型為Number。表達式轉換為ToNumber("42") == 42
  4. ToNumber("42")返回42,表達式轉換為42 == 42。算法遞歸併執行步驟1.c.iii。因為數字相同,所以返回true,並且遞歸展開。

對於我的最後一個示例,讓我們找出為什麼以下序列沒有顯示傳遞性,其中第三個比較將返回true而不是false

"this_is_true" == false // false
"this_is_true" == true  // false
登入後複製
登入後複製
登入後複製
登入後複製
登入後複製
登入後複製

以下步驟顯示了JavaScript如何使用抽象相等比較算法得出true作為'' == 0的值。

  1. 執行步驟5,導致ToNumber('') == 0,這轉換為0 == 0,算法遞歸。 (規範的第9.3.1節指出StringNumericLiteral ::: [empty]的MV[數學值]為0。換句話說,空字符串的數值為0。)
  2. 執行步驟1.c.iii,它將0與0進行比較並返回true(並展開遞歸)。

以下步驟顯示了JavaScript如何使用抽象相等比較算法得出true作為0 == '0'的值:

  1. 執行步驟4,導致0 == ToNumber('0'),這轉換為0 == 0,算法遞歸。
  2. 執行步驟1.c.iii,它將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中使用===而不是==,因為它提供更嚴格的比較,這意味著它不執行類型強制轉換並檢查值和類型。這可以幫助避免在比較不同類型的值時出現意外結果。例如,當使用==時,JavaScript會認為數字0和空字符串“”相等,因為它在比較之前轉換類型。但是,使用===,它們將被認為不相等,因為它們類型不同。

JavaScript中的類型強制轉換是什麼?

JavaScript中的類型強制轉換是指將值從一種數據類型自動或隱式轉換為另一種數據類型。當對不同類型的操作數使用運算符或需要某種類型時,就會發生這種情況。例如,當使用鬆散相等運算符(==)時,JavaScript將在進行比較之前嘗試將操作數轉換為通用類型。

JavaScript如何處理對象的比較?

在JavaScript中,對象按引用比較,而不是按值比較。這意味著即使兩個對象具有完全相同的屬性和值,它們也不被認為相等,因為它們引用內存中的不同對象。對像被認為相等的唯一情況是它們引用完全相同的對象。

==!=在JavaScript中的區別是什麼?

==!=都是JavaScript中的比較運算符。 ==運算符檢查兩個操作數的值是否相等,必要時執行類型強制轉換。另一方面,!=運算符檢查兩個操作數的值是否不相等,必要時也執行類型強制轉換。

===!==在JavaScript中的區別是什麼?

===!==都是JavaScript中的比較運算符。 ===運算符檢查兩個操作數的值是否相等,同時考慮值和類型。另一方面,!==運算符檢查兩個操作數的值是否不相等,同時考慮值和類型。

如何在JavaScript中比較兩個數組?

在JavaScript中,數組是對象,按引用比較,而不是按值比較。這意味著即使兩個數組按相同的順序包含相同的元素,它們也不被認為相等,因為它們引用內存中的不同對象。要按其內容比較兩個數組,你需要分別比較每個元素。

JavaScript如何處理nullundefined的比較?

在JavaScript中,nullundefined被認為是鬆散相等(==),因為它們都表示值的缺失。但是,它們不是嚴格相等(===),因為它們的類型不同。

JavaScript中比較運算符的優先級順序是什麼?

在JavaScript中,比較運算符具有相同的優先級級別。它們從左到右計算。但是,重要的是要注意,它們的優先級低於算術和按位運算符,但高於邏輯運算符。

我可以在JavaScript中將比較運算符與字符串一起使用嗎?

是的,你可以在JavaScript中將比較運算符與字符串一起使用。比較字符串時,JavaScript使用詞法(字典)順序。但是,重要的是要注意,大寫字母被認為比小寫字母“小”,因為它們的ASCII值較小。

以上是不要害怕邪惡的雙胞胎-Sitepoint的詳細內容。更多資訊請關注PHP中文網其他相關文章!

本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn
作者最新文章
熱門教學
更多>
最新下載
更多>
網站特效
網站源碼
網站素材
前端模板