你們知道,==是JavaScript中比較複雜的一個運算符。它的運算規則奇怪,容易讓人犯錯,從而成爲JavaScript中「最糟糕的特性」之一。javascript
在仔細閱讀了ECMAScript規範的基礎上,我畫了一張圖,我想經過它你會完全地搞清楚關於==的一切。同時,我也試圖經過此文向你們證實==並非那麼糟糕的東西,它很容易掌握,甚至看起來很合理。java
先上圖:git
圖1 ==運算規則的圖形化表示github
==
運算規則的精確描述在此:The Abstract Equality Comparison Algorithm。可是,這麼複雜的描述,你肯定看完後腦子不暈?肯定立馬就能拿它指導實踐?算法
確定不行,規範畢竟是給JavaScript運行環境的開發人員看的(好比V8引擎的開發人員們),而不是給語言的使用者看的。而上圖正是將規範中複雜的描述翻譯成了更容易看懂的形式。數組
在詳細介紹圖1中的每一個部分前,咱們來複習一下JS中關於類型的知識:函數
JS中的值有兩種類型:原始類型(Primitive)、對象類型(Object)。es5
基本類型包括:Undefined、Null、Boolean、Number和String等五種。spa
Undefined類型和Null類型的都只有一個值,即undefined
和null
;Boolean類型有兩個值:true
和false
;Number類型的值有不少不少;String類型的值理論上有無數個。翻譯
全部對象都有valueOf()
和toString()
方法,它們繼承自Object
,固然也可能被子類重寫。
如今考慮表達式:
x == y
其中x
和y
是上述六種類型中某一種類型的值。
當x
和y
的類型相同時,x == y
能夠轉化爲x === y
,然後者是很簡單的(惟一須要注意的多是NaN
),因此下面咱們只考慮x
和y
的類型不一樣的狀況。
在圖1中,JavaScript值的六種類型用藍底色的矩形表示。它們首先被分紅了兩組:
String、Number、Boolean和Object (對應左側的大矩形框)
Undefined和Null (對應右側的矩形框)
分組的依據是什麼?咱們來看一下,右側的Undefined和Null是用來表示不肯定、無或者空的,而右側的四種類型都是肯定的、有和非空。咱們能夠這樣說:
左側是一個存在的世界,右側是一個空的世界。
因此,左右兩個世界中的任意值作==比較的結果都是false
是很合理的。(見圖1中鏈接兩個矩形的水平線上標的false)
JavaScript中的undefined
和null
是另外一個常常讓咱們崩潰的地方。一般它被認爲是一個設計缺陷,這一點咱們不去深究。不過我曾據說,JavaScript的做者最初是這樣想的:
假如你打算把一個變量賦予對象類型的值,可是如今尚未賦值,那麼你能夠用
null
表示此時的狀態(證據之一就是typeof null
的結果是'object'
);相反,假如你打算把一個變量賦予原始類型的值,可是如今尚未賦值,那麼你能夠用undefined
表示此時的狀態。
無論這個傳聞是否可信,它們二者作==比較的結果是true
是很合理的。(見圖1中右側垂直線上標的true)
在進行下一步以前,咱們先來講一下圖1中的兩個符號:大寫字母N和P。這兩個符號並非PN結中正和負的意思。而是:
N表示ToNumber操做,即將操做數轉爲數字。它是規範中的抽象操做,但咱們能夠用JS中的Number()
函數來等價替代。
P表示ToPrimitive操做,即將操做數轉爲原始類型的值。它也是規範中的抽象操做,一樣也能夠翻譯成等價的JS代碼。不過稍微複雜一些,簡單說來,對於一個對象obj
:
ToPrimitive(obj)等價於:先計算
obj.valueOf()
,若是結果爲原始值,則返回此結果;不然,計算obj.toString()
,若是結果是原始值,則返回此結果;不然,拋出異常。
注:此處有個例外,即Date
類型的對象,它會先調用toString()
方法,後調用valueOf()
方法。
在圖1中,標有N或P的線表示:當它鏈接的兩種類型的數據作==運算時,標有N或P的那一邊的操做數要先執行ToNumber或ToPrimitive變換。
從圖1能夠看出,當布爾值與其餘類型的值做比較時,布爾值會轉化爲數字,具體來講
true -> 1 false -> 0
這一點也不需浪費過多口舌。想一下在C語言中,根本沒有布爾類型,一般用來表示邏輯真假的正是整數1和0。
在圖1中,咱們把String和Number類型分紅了一組。爲何呢?在六種類型中,String和Number都是字符的序列(至少在字面上如此)。字符串是全部合法的字符的序列,而數字能夠當作是符合特定條件的字符的序列。因此,數字能夠當作字符串的一個子集。
根據圖1,在字符串和數字作==運算時,須要使用ToNumber操做,把字符串轉化爲數字。假設x是字符串,y是數字,那麼:
x == y -> Number(x) == y
那麼字符串轉化爲數字的規則是怎樣的呢?規範中描述得很複雜,可是大體說來,就是把字符串兩邊的空白字符去掉,而後把兩邊的引號去掉,看它可否組成一個合法的數字。若是是,轉化結果就是這個數字;不然,結果是NaN
。例如:
Number('123') // 結果123 Number('1.2e3') // 結果1200 Number('123abc') // 結果NaN Number('\r\n\t123\v\f') // 結果123
固然也有例外,好比空白字符串轉化爲數字的結果是0
。即
Number('') // 結果0 Number('\r\n\t \v\f') // 結果0
原始類型是一種單純的類型,它們直接了當、容易理解。然而缺點是表達能力有限,難以擴展,因此就有了對象。對象是屬性的集合,而屬性自己又能夠是對象。因此對象能夠被構造得任意複雜,足以表示各類各樣的事物。
可是,有時候事情複雜了也不是好事。好比一篇冗長的論文,並非每一個人都有時間、有耐心或有必要從頭至尾讀一遍,一般只瞭解其中心思想就夠了。因而論文就有了關鍵字、概述。JavaScript中的對象也同樣,咱們須要有一種手段瞭解它的主要特徵,因而對象就有了toString()
和valueOf()
方法。
toString()
方法用來獲得對象的一段文字描述;而valueOf()
方法用來獲得對象的特徵值。
固然,這只是我本身的理解。顧名思義,toString()
方法傾向於返回一個字符串。那麼valueOf()
方法呢?根據規範中的描述,它傾向於返回一個數字——儘管內置類型中,valueOf()
方法返回數字的只有Number
和Date
。
根據圖1,當一個對象與一個非對象比較時,須要將對象轉化爲原始類型(雖然與布爾類型比較時,須要先將布爾類型變成數字類型,可是接下來仍是要將對象類型變成原始類型)。這也是合理的,畢竟==是不嚴格的相等比較,咱們只須要取出對象的主要特徵來參與運算,次要特徵放在一邊就好了。
咱們回過頭來看一下圖1。裏面標有N或P的那幾條連線是沒有方向的。假如咱們在這些線上標上箭頭,使得連線從標有N或P的那一端指向另外一端,那麼會獲得(不考慮undefined和null):
圖2 ==運算過程當中類型轉化的趨勢
發現什麼了嗎?對,在運算過程當中,全部類型的值都有一種向數字類型轉化的趨勢。畢竟曾經有名人說過:
萬物皆數。
前面廢話太多了,這裏仍是舉個例子,來證實圖1確實是方便有效能夠指導實踐的。
例,計算下面表達式的值:
[''] == false
首先,兩個操做數分別是對象類型、布爾類型。根據圖1,須要將布爾類型轉爲數字類型,而false
轉爲數字的結果是0,因此表達式變爲:
[''] == 0
兩個操做數變成了對象類型、數字類型。根據圖1,須要將對象類型轉爲原始類型:
首先調用[].valueOf()
,因爲數組的valueOf()
方法返回自身,因此結果不是原始類型,繼續調用[].toString()
。
對於數組來講,toString()
方法的算法,是將每一個元素都轉爲字符串類型,而後用逗號','依次鏈接起來,因此最終結果是空字符串'',它是一個原始類型的值。
此時,表達式變爲:
'' == 0
兩個操做數變成了字符串類型、數字類型。根據圖1,須要將字符串類型轉爲數字類型,前面說了空字符串變成數字是0。因而表達式變爲:
0 == 0
到此爲止,兩個操做數的類型終於相同了,結果明顯是true
。
從這個例子能夠看出,要想掌握==運算的規則,除了牢記圖1外,還須要記住那些內置對象的toString()
和valueOf()
方法的規則。包括Object、Array、Date、Number、String、Boolean等,幸虧這沒有什麼難度。
其實,圖一還不夠完美。爲何呢?由於對象與字符串/數字比較時都由對象來轉型,可是與一樣是原始類型的布爾類型比較時卻須要布爾類型轉型。實際上,只要稍稍分析一下,所有讓對象來轉爲原始類型也是等價的。因此咱們獲得了最終的更加完美的圖形:
圖3 更完美的==運算規則的圖形化表示
有一個地方可能讓你疑惑:爲何Boolean與String之間標了兩個N?雖然按照規則應該是由Boolean轉爲數字,可是下一步String就要轉爲數字了,因此乾脆不如兩邊同時轉成數字。
前面說得很亂,根據咱們獲得的最終的圖3,咱們總結一下==運算的規則:
undefined == null,結果是true
。且它倆與全部其餘值比較的結果都是false
。
String == Boolean,須要兩個操做數同時轉爲Number。
String/Boolean == Number,須要String/Boolean轉爲Number。
Object == Primitive,須要Object轉爲Primitive(具體經過valueOf和toString方法)。
瞧見沒有,一共只有4條規則!是否是很清晰、很簡單。
PS:最後,把圖改了一下,僅供娛樂 : )
OK,結束了。若是你以爲這篇文章對你有用,請點贊,讓更多的人看到。另外,文章中的謬誤,請不吝指出。