在JS中的關係比較(Relational Comparison)運算,指的是像x < y
這種大小值的關係比較。算法
而相等比較,可區分爲標準相等(standard equality)比較x == y
與嚴格相等(strict equality)比較x === y
兩大種類。嚴格相等比較會比較左邊與右邊運算元的數據類型,值相等比較則只看值,簡單的來講是這樣解釋沒錯。segmentfault
ToPrimitive運算的詳細說明可參考: JS中的{} + {}與{} + []的結果是什麼?數組
不過,這兩種比較實際上依內部設計來講,並非那麼簡單。固然,在通常的使用狀況是不須要考量那麼多,本文的說明會涉及許多JS內部設計的部份,對於這兩種比較來做比較完全的理解,主要的參考數據是ECMAScript的標準文件。函數
嚴格相等比較的演算規則先理解,主要是由於在標準相等比較(只比較值不比較數據類型)時,它在演算時的某些狀況下會跳到嚴格相等比較的規則來。測試
嚴格相等比較的演算規則很容易理解,按照如下的步驟進行比較,出自ecma-262 11.9.6:編碼
如下假設爲比較 x === y
的狀況,Type(x)指的是x的數據類型,Type(y)指的是y的類型,最終返回值只有true或false,會按照下面的步驟進行比較,若是有返回時就中止以後的步驟:prototype
注: Type(x)在ECMAScript的標準中指的並非用
typeof
返回出來的結果,而是標準內部給定的各類數據類型,共有Undefined, Null, Boolean, String, Number 與 Object。例如typeof null
的結果是"object",但ECMAScript會認爲Null是個獨立的數據類型。設計
Type(x)與Type(y)不一樣,返回falsecode
Type(x)是Undefined,返回true(固然此時Type(y)也是Undefined)對象
Type(x)是Null,返回true(固然此時Type(y)也是Null)
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
Type(x)是String時,只有當x中的字符順序與y中徹底相同時(長度相同,字符所在位置也相同),返回true。其餘狀況就返回false。
Type(x)是Boolean時,只有當x與y是同時爲true或同時爲false時,返回true。其它狀況返回false。
只有當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,會按照下面的步驟進行比較,若是有返回時就中止以後的步驟:
Type(x)與Type(y)相同時,進行嚴格相等比較
x是undefined,而y是null時,返回true
x是null,而y是undefined時,返回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)是Number或String其中一種,而Type(y)是個Object時,進行x == ToPrimitive(y)
比較
Type(x)是個Object,而Type(y)是Number或String其中一種時,進行ToPrimitive(x) == y
比較
其餘狀況,返回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
上面這是由於空數組[]
,進行ToPrimitive
運算後,獲得的是空字符串,因此做值相等比較,至關於空字符串在進行比較。
> '[object Object]' == {} true > NaN == {} false
上面的空對象字面量,進行ToPrimitive
運算後,獲得的是'[object Object]'
字符串,這個值會若是與數字類型的NaN比較,會跳到同類型相等的嚴格相等比較中,NaN不論與任何數字做相等比較,必定是返回false。
> 1 == new Number(1) true > 1 === new Number(1) false > 1 === Number(1) true
上面說明了,包裝對象在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。
(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
備註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
雖然在標準中的抽象關係比較演算中,有存在一種返回值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
字符串與空字符串相比,都是套用前綴(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
'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
簡單地說明在ToNumber運算時,這些其餘的原始數據類型值的轉換結果以下:
Undefined -> NaN
Null -> +0
Boolean -> (true -> 1, false -> 0)
注: JS認爲+0與-0是徹底相同的值,在嚴格相等比較中是相等的。
注: 字符串比較其實是拆爲字符在詞典表中的編輯整數值來比較,對於非英語系的語言,JS另外有提供String.prototype.localeCompare的方法來進行局部語言的比較工做。
本章延伸了以前的加法運算文章中的ToPrimitive
運算解說的部份,較爲仔細的來研究JS中的相等比較(包含標準的與嚴格的)與關係比較的部份。至於沒提到的,不相等(==)與嚴格不相等(!==),或是大於等於(>=)或小於等於(<=)只是這些演算規劃的再組合結果而已。
標準的值相等比較(==),是一種有不經意的反作用的運算,無論如何,開發者一定要儘可能避免,比較前能夠自行轉換類型的方式,再做嚴格的相等比較,本章也有說明爲什麼要避免使用它的理由。