JS中的關系比較與相等比較運算

yupx1113 7年前發布 | 9K 次閱讀 JavaScript開發 JavaScript

JS中的關系比較與相等比較運算

在JS中的關系比較(Relational Comparison)運算,指的是像 x < y 這種大小值的關系比較。

而相等比較,可區分為標準相等(standard equality)比較 x == y 與嚴格相等(strict equality)比較 x === y 兩大種類。嚴格相等比較會比較左邊與右邊運算元的數據類型,值相等比較則只看值,簡單的來說是這樣解釋沒錯。

ToPrimitive運算的詳細說明可參考: JS中的{} + {}與{} + []的結果是什么?

不過,這兩種比較實際上依內部設計來說,并不是那么簡單。當然,在一般的使用情況是不需要考量那么多,本文的說明會涉及許多JS內部設計的部份,對于這兩種比較來作比較徹底的理解,主要的參考數據是ECMAScript的標準文件。

嚴格相等比較(嚴格相等比較演算)

嚴格相等比較的演算規則先理解,主要是因為在標準相等比較(只比較值不比較數據類型)時,它在演算時的某些情況下會跳到嚴格相等比較的規則來。

嚴格相等比較的演算規則很容易理解,按照以下的步驟進行比較,出自 ecma-262 11.9.6 :

以下假設為比較 x === y 的情況,Type(x)指的是x的數據類型,Type(y)指的是y的類型,最終返回值只有true或false,會按照下面的步驟進行比較,如果有返回時就停止之后的步驟:

注: Type(x)在ECMAScript的標準中指的并不是用 typeof 返回出來的結果,而是標準內部給定的各種數據類型,共有Undefined, Null, Boolean, String, Number 與 Object。例如 typeof null 的結果是"object",但ECMAScript會認為Null是個獨立的數據類型。

  1. Type(x)與Type(y)不同,返回false

  2. Type(x)是Undefined,返回true(當然此時Type(y)也是Undefined)

  3. Type(x)是Null,返回true(當然此時Type(y)也是Null)

  4. Type(x)是Number時

    • (a.) x是NaN,返回false

    • (b.) y是NaN,返回false

    • (c.) x與y是同樣的數字,返回true

    • (d.) x是+0,y是-0,返回true

    • (e.) x是-0,y是+0,返回true

    • (f.) 其他情況,返回false

  5. Type(x)是String時,只有當x中的字符順序與y中完全相同時(長度相同,字符所在位置也相同),返回true。其他情況就返回false。

  6. Type(x)是Boolean時,只有當x與y是同時為true或同時為false時,返回true。其它情況返回false。

  7. 只有當x與y同時參照到同一對象時,返回true。其它情況返回false。

備注: 這個演算與the SameValue Algorithm (9.12)不同之處在于,對于有號的0與NaN處理方式不同。

注: 同值演算(the SameValue Algorithm)是標準中的另一個內部演算法,只會用在很特別的地方,可以先略過不看。

從上述的嚴格相等比較中,可以很清楚的看到數字、字符串、布爾與null、undefined或對象是如何比較的。

標準相等比較(抽象相等比較演算)

標準相等比較的演算規則按照以下的步驟進行比較,出自 ecma-262 11.9.3 :

以下假設為比較 x == y 的情況,Type(x)指的是x的數據類型,Type(y)指的是y的類型,最終返回值只有true或false,會按照下面的步驟進行比較,如果有返回時就停止之后的步驟:

  1. Type(x)與Type(y)相同時,進行嚴格相等比較

  2. x是undefined,而y是null時,返回true

  3. x是null,而y是undefined時,返回true

  4. Type(x)是Number而Type(y)是String時,進行 x == ToNumber(y) 比較

  5. Type(x)是String而Type(y)是Number時,進行 ToNumber(x) == y 比較

  6. Type(x)是Boolean時,進行 ToNumber(x) == y

  7. Type(y)是Boolean時,進行 x == ToNumber(y)

  8. Type(x)是Number或String其中一種,而Type(y)是個Object時,進行 x == ToPrimitive(y) 比較

  9. Type(x)是個Object,而Type(y)是Number或String其中一種時,進行 ToPrimitive(x) == y 比較

  10. 其他情況,返回false

備注1: 以下的是三種強制轉換的標準比較情況:

  • 字符串比較: "" + a == "" + b.

  • 數字比較: +a == +b.

  • 布爾比較: !a == !b

備注2: 標準相等比較有以下的不變式(invariants):

  • A != B 相當于 !(A == B)

  • A == B 相當于 B == A

備注3: 相等比較運算不一定總是可以轉變(transitive),例如:

  • new String("a") == "a" 與 "a" == new String("a") 的結果都是true

  • new String("a") == new String("a") 結果是false.

備注4: 字符串比較使用的是簡單的字符測試。并非使用復雜的、語義導向的字符定義或是Unicode所定義的字符串相等或校對順序。

注: 上述的ToNumber與ToPrimitive都是標準內部運算時使用的方法,并不是讓開發者使用的。

由標準相等比較的演算得知,它的運算是以"數字為最優先",任何其它的類型如果與數字作相等比較,必定要先強制轉為數字再比較。但這是一個相當具有隱藏作用的運算,在一般實作時,會很容易造成誤解,例如以下的例子:

> 0 == []
true

> '' == [] true</code></pre>

上面這是因為空數組 [] ,進行 ToPrimitive 運算后,得到的是空字符串,所以作值相等比較,相當于空字符串在進行比較。

> '[object Object]' == {}
true

> NaN == {} false</code></pre>

上面的空對象字面量,進行 ToPrimitive 運算后,得到的是 '[object Object]' 字符串,這個值會如果與數字類型的NaN比較,會跳到同類型相等的嚴格相等比較中,NaN不論與任何數字作相等比較,一定是返回false。

> 1 == new Number(1)
true

> 1 === new Number(1) false

> 1 === Number(1) true</code></pre>

上面說明了,包裝對象在JS中的內部設計中,標準的值相等比較是相同的,但嚴格相等比較是不同的值,包裝對象仍然是個對象,只是里面的 valueOf 方法是返回這個對象里面帶的原始數據類型值,經過 ToPrimitive 方法運算后,會返回原始數據的值。 Number() 函數調用只是轉數字類型用的函數,這個用法經常會與包裝對象的用法混在一起。

這個小節的結論是,在JS中沒有必要的情況下,使用嚴格的相等比較為最佳的值相等比較方式,標準的相等容易產生不經意的副作用,有的時候你可能會得到不預期的結果。

關系比較(抽象關系比較演算)

關系比較的演算規則主要是按照以下的步驟進行比較,出自 ecma-262 11.8.5 :

以下假設為比較 x < y 的情況,因為在標準中的抽象關系比較演算的說明比較復雜,有涉及布爾標記的以左方優先或右方優先,而且最終返回值有true、false與undefined,實際上最終不會有undefined值出現,即是得到 false 而已,以下為只考慮左方優先(LeftFirst)的簡化過的步驟。會按照下面的步驟進行比較,如果有返回時就停止之后的步驟:

  • (1. & 2.) x經過ToPrimitive(x, hint Number)運算為px值,y經過ToPrimitive(y, hint Number)運算為py值

  • (3.) 如果Type(px)與Type(py)不同時為String時

    • (a.b.) px作ToNumber(px)運算,得到nx值,與py作ToNumber(py)值,得到ny值

    • (c.d.) nx或ny中有其一為NaN時,返回undefined

    • (e.) nx與ny是同樣的Number值,返回false

    • (f.) nx是+0,而且ny是?0,返回false

    • (g.) nx是?0,而且ny是+0,返回false.

    • (h.) nx是+∞,返回false

    • (i.) ny是+∞,返回true

    • (j.) ny是?∞,返回false

    • (k.) nx是?∞,返回true

    • (l.) 如果在數學上的值,nx小于ny,而且nx與ny是有限值(finite),而且不同時為0時,返回true。否則返回false。

    </li>
  • (4.) 如果Type(px)與Type(py)同時為String時

    • (a.) 如果py是px的前綴(prefix)時,返回false (前綴代表px字符串中是由py字符串組成的,py只是px的子字符串的情況)

    • (b.) 如果px是py的前綴(prefix)時,返回true

    • (c.d.e.f) 以字符串中的按順序的字符,用字符的編碼整數的大小來比較。k是可得到的一個最小非負整數,在px與py中的k位置有不同的字符(從左邊算過來)。在px中某個位置k的字符編碼整數為m,在py某個位置k的字符編輯為n,如果m < n,則返回true,否則返回false

    • </ul> </li> </ul>

      備注2: 字符串比較使用的是簡單的詞典順序測試。并非使用復雜的、語義導向的字符定義或是Unicode所定義的字符串相等或校對順序。

      注: +∞ 相當于全局屬性 Infinity 或 Number.POSITIVE_INFINITY , ?∞ 相當于全局屬性 -Infinity 或 Number.NEGATIVE_INFINITY 。

      關系比較基本上要區分為數字類型與字符串類型,但依然是以"數字"為最優先的比較,只要有其他類型與數字相比較,一定會先被強制轉換為數字。但在這之前,需要先用 ToPrimitive 而且是hint為數字來轉換為原始數據類型。

      以下為一些與對象、數組、Date對象的關系比較例子:

      > 1 < (new Date())
      true

      > 1 > (new Date()) false

      > [] < 1 true

      > [] > 1 false

      > ({}) < 1 false

      > ({}) > 1 false</code></pre>

      雖然在標準中的抽象關系比較演算中,有存在一種返回值 undefined ,但在真實的情況并沒有這種返回值,相當不論怎么比較都是得到 false 的值。上面的例子中,空對象({})的ToPrimitive運算得出的是 '[object Object]' 字符串值,經過 ToNumber 運算會得到NaN數字類型的值,這個值不論與數字1作大于小于的關系運算,都是false。

      Date() 對象因為 ToPrimitive 運算的hint為數字,所以也是會作轉換為數字類型的值為優先(也就是調用valueOf為優先),所以并不是正常情況的以輸出字符串為優先(也就是調用toString方法為優先)的預設情況。

      以下為一些字符串關系比較的例子:

      > 'a' > ''
      true

      > 'a' < '' false

      > 'a' > [] true

      > 'a' < [] false

      > 'a' > ({}) true

      > 'a' < ({}) false</code></pre>

      字符串與空字符串相比,都是套用前綴(prefix)的規則步驟,因為空字符串算是所有字符串的前綴(組成的子字符串之一),所以必然地所有有值的字符串值一定是大于空字符串。

      空數組經過ToPrimitive運算出來的是空字符串,所以與空字符串相比較的結果相同。

      空對象經過ToPrimitive運算出來的是 '[object Object]' 字符串值,以 'a'.charCodeAt(0) 計算出的值是字符編碼是97數字,而 '['.charCodeAt(0) 則是91數字,所以 'a' > ({}) 會是得到true。

      如果開始混用數字與字符串比較,可能是有陷阱的比較例子:

      > '11' > '3'
      false

      > '11' > 3 true

      > 'one' < 3 false

      > 'one' > 3 false</code></pre>

      '11'與'3'相比較,其實都是字符串比較,要依照可比較的字符位置來比較,也就是'1'與'3'字符的比較,它們的字符編碼數字分別是49與51,所以 '1' < '3' ,這里的運算的結果必然是返回false。

      '11'與3數字比較,是會強制都轉為數字來比較,'11'會轉為11數字值,所以大于3。

      'one'這個字符串轉為數字后,是NaN這個數字值,NaN與任何數字比較,既不大于也不小于,不論作大于或小于,都是返回false。(實際上在標準中它這種返回值叫undefined)

      字符串與數字之外其他的原始數據類型的比較,只要記得原則就是強制轉為數字來比較就是了,以下為例子:

      > true > null
      true

      > false > undefined false</code></pre>

      簡單地說明在ToNumber運算時,這些其他的原始數據類型值的轉換結果如下:

      • Undefined -> NaN

      • Null -> +0

      • Boolean -> (true -> 1, false -> 0)

      注: JS認為+0與-0是完全相同的值,在嚴格相等比較中是相等的。

      注: 字符串比較實際上是拆為字符在詞典表中的編輯整數值來比較,對于非英語系的語言,JS另外有提供 String.prototype.localeCompare 的方法來進行局部語言的比較工作。

      總結

      本章延伸了之前的加法運算文章中的 ToPrimitive 運算解說的部份,較為仔細的來研究JS中的相等比較(包含標準的與嚴格的)與關系比較的部份。至于沒提到的,不相等(==)與嚴格不相等(!==),或是大于等于(>=)或小于等于(<=)只是這些演算規劃的再組合結果而已。

      標準的值相等比較(==),是一種有不經意的副作用的運算,不管如何,開發者必定要盡量避免,比較前可以自行轉換類型的方式,再作嚴格的相等比較,本章也有說明為何要避免使用它的理由。

       

      來自:https://segmentfault.com/a/1190000008038751

       

 本文由用戶 yupx1113 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!