js == 運算規則解析

孤舟蓑翁發表於2017-04-17

1.先了解一下基本型別和複雜型別劃分的依據

JS中的值有兩種型別:原始型別(Primitive)、物件型別(Object)。原始型別包括:Undefined、Null、Boolean、Number和String等五種。這兩大類別的資料儲存方式是不一樣的。

儲存空間的時空關係可以概括為空間大,訪問起來就慢,反之亦然。堆比棧大,棧比堆的運算速度快,

物件是一個複雜的結構,並且可以自由擴充套件,如:陣列可以無限擴充,物件可以自由新增屬性。將它們放在堆中是為了不影響棧的效率。而是通過引用的方式查詢到堆中的實際物件再進行操作。

相對於物件型別而言,原始型別就比較穩定,並且它只佔據很小的記憶體。不將原始型別放在堆是因為通過引用到堆中查詢實際物件是要花費時間的,而這個綜合成本遠大於直接從棧中取得實際值的成本。所以簡單資料型別的值直接存放在棧中。`

順帶說一下undefined和null的使用區別:

假如你打算把一個變數賦予物件型別的值,但是現在還沒有賦值,那麼你可以用null表示此時的狀態(證據之一就是typeof null 的結果是'object');相反,假如你打算把一個變數賦予原始型別的值,但是現在還沒有賦值,那麼你可以用undefined表示此時的狀態。

 

2.進行==比較時資料的轉換規則

 ==運算規則的準備描述如下:

看完之後有沒有感覺有點腦殼疼,這樣的描述很難讓人在實踐中使用,所以很有必要對上述規則進行簡化概括:

  • undefined == null,結果是true。且它倆與所有其他值比較的結果都是false。

  • String == Boolean,需要兩個運算元同時轉為Number。

  • String/Boolean == Number,需要String/Boolean轉為Number。

  • Object == Primitive,需要Object轉為Primitive(具體通過valueOf和toString方法)。

 

 

 

 那麼Boolean,Number,Primitive的轉換規則是:

(1)其它型別轉換成Boolean的規則是:

(2)其它型別轉換成Number型別的規則

object型別一般要先轉換成string型別,接著才進行Number型別的轉換。字串轉化為數字的規則規範中描述得很複雜,但是大致說來,就是把字串兩邊的空白字元去掉,然後把兩邊的引號去掉,看它能否組成一個合法的數字。如果是,轉化結果就是這個數字;否則,結果是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
需要補充說明的是:
(3)物件型別向primitive型別轉換的規則是:

     為什麼要區分原始型別和複雜型別?

     原始型別是一種單純的型別,它們直接了當、容易理解。然而缺點是表達能力有限,難以擴充套件,所以就有了物件。物件是屬性的集合,而屬性本身又可以是物件。所以物件可以被構造得任意複雜,足以表示各種各樣的事物。

  但是,有時候事情複雜了也不是好事。比如一篇冗長的論文,並不是每個人都有時間、有耐心或有必要從頭到尾讀一遍,通常只瞭解其中心思想就夠了。於是論文就有了關鍵字、概述。JavaScript中的物件也一樣,我們需要有一種手段瞭解它的主要特徵,於是物件就有了toString()和valueOf()方法。

     這兩種方法的區別是:

toString()方法用來得到物件的一段文字描述;而valueOf()方法用來得到物件的特徵值。

   toString()方法傾向於返回一個字串。valueOf()方法傾向於返回一個數字——儘管內建型別中,valueOf()方法返回數字的只有Number和Date。


對於所有非日期物件來說,物件到原始值的轉換基本上是物件到數字的轉換

  一般來說,物件到數字的轉換經過了如下過程:

  1.如果物件具有valueOf()方法,後者返回一個原始值,則js將這個原始值轉換成數字,並返回這個數字。

  2.否則,如果物件具有toString()方法,後者返回一個原始值,則js將轉換並返回。(首先js轉換成相應的字串原始值,再繼續將這個原始值轉換成相應的數字型別,再返回數字)

  3.否則,js丟擲一個型別錯誤異常。

  一般來說,物件到字串的轉換經過了如下步驟:

  1.如果物件具有toString()方法,則呼叫這個方法。如果它返回一個原始值,js將這個值轉換成字串,並返還這個字串結果。

  2.如果物件沒有toString()方法,或者toString並不返回一個原始值,那麼js將呼叫valueOf()方法。

  3.否則,js無法從toString()或者valueOf()獲得一個原始值,因此這時它將丟擲一個型別錯誤異常。

   
下圖第一列和第二列是其它型別呼叫toString方法時的轉換規則

    需要說明的是: toString方法會將{}轉換成"[object Object]",將function(){}轉換成"function(){}"

 

    其它型別呼叫valueOf方法的轉換規則是:

  

    經過層層深入剖析,現在你應該理解前面各種資料型別進行 == 運算時的隱式運作規則了吧。現在再碰到如下的問題,是不是感覺思路很清晰

 

[]==[]             //false
[]==![]            //true
{}==!{}            //false  實際上被解析成 { ' } == !{ ' }
{}==![]            //Uncaught SyntaxError: Unexpected token ==   表示式不能以{開頭 {}是個語句塊,後面跟== ![]就變成了一種語法錯誤的語句塊
// 在語法解析的時候,如果一個語句以「{」開頭,就只把它解釋成語句塊。換用形式語法的說法,就是「表示式語句不能以『{』開頭」。對錶達式語句開頭的另一個限制——限制「function」出現在開頭——同理。 ![]=={} //false []==!{} //true undefined==null //true

[]  == []  // false 因為它們的引用地址不一樣

由上面的比較可以得出:
對任何一種型別進行取反 會得到一個boolean型別的值
[]在與不同的型別對比的時候,會轉換成0
{}在與不同的型別對比的時候,會轉換成NaN,
不同型別的比較最終都被轉換成number型別的比較

參考文章

[1]
一張圖徹底搞懂JavaScript的==運算

    [2] 為什麼控制檯列印{}+[]===[]+{}為false?