經過一張簡單的圖,讓你完全地、永久地搞懂JS的==運算

你們知道,==是JavaScript中比較複雜的一個運算符。它的運算規則奇怪,容易讓人犯錯,從而成爲JavaScript中「最糟糕的特性」之一。javascript

在仔細閱讀了ECMAScript規範的基礎上,我畫了一張圖,我想經過它你會完全地搞清楚關於==的一切。同時,我也試圖經過此文向你們證實==並非那麼糟糕的東西,它很容易掌握,甚至看起來很合理。java

先上圖:git

==運算符的運算規則
圖1 ==運算規則的圖形化表示github

==運算規則的精確描述在此:The Abstract Equality Comparison Algorithm。可是,這麼複雜的描述,你肯定看完後腦子不暈?肯定立馬就能拿它指導實踐?算法

確定不行,規範畢竟是給JavaScript運行環境的開發人員看的(好比V8引擎的開發人員們),而不是給語言的使用者看的。而上圖正是將規範中複雜的描述翻譯成了更容易看懂的形式。數組

在詳細介紹圖1中的每一個部分前,咱們來複習一下JS中關於類型的知識:函數

  1. JS中的值有兩種類型:原始類型(Primitive)、對象類型(Object)。es5

  2. 基本類型包括:Undefined、Null、Boolean、Number和String等五種。spa

  3. Undefined類型和Null類型的都只有一個值,即undefinednull;Boolean類型有兩個值:truefalse;Number類型的值有不少不少;String類型的值理論上有無數個。翻譯

  4. 全部對象都有valueOf()toString()方法,它們繼承自Object,固然也可能被子類重寫。

如今考慮表達式:

x == y

其中xy是上述六種類型中某一種類型的值。

xy的類型相同時,x == y能夠轉化爲x === y,然後者是很簡單的(惟一須要注意的多是NaN),因此下面咱們只考慮xy的類型不一樣的狀況。

一. 有和無

在圖1中,JavaScript值的六種類型用藍底色的矩形表示。它們首先被分紅了兩組:

  • String、Number、Boolean和Object (對應左側的大矩形框)

  • Undefined和Null (對應右側的矩形框)

分組的依據是什麼?咱們來看一下,右側的Undefined和Null是用來表示不肯定或者的,而右側的四種類型都是肯定的非空。咱們能夠這樣說:

左側是一個存在的世界,右側是一個的世界。

因此,左右兩個世界中的任意值作==比較的結果都是false是很合理的。(見圖1中鏈接兩個矩形的水平線上標的false)

二. 空和空

JavaScript中的undefinednull是另外一個常常讓咱們崩潰的地方。一般它被認爲是一個設計缺陷,這一點咱們不去深究。不過我曾據說,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()方法返回數字的只有NumberDate

根據圖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,結束了。若是你以爲這篇文章對你有用,請點贊,讓更多的人看到。另外,文章中的謬誤,請不吝指出。

相關文章
相關標籤/搜索