JS加法運算全解析

weixin_34253539發表於2018-08-14

終極命題:
在JS中:[]+[]、[]+{}、{}+[]、{}+{}的結果分別是什麼?

一、JS中的型別
  • 基本型別
    JS的基本型別包括Undefined、Null、Boolean、Number和String五種。Undefined型別和Null型別的都只有一個值,即undefined和null;Boolean型別有兩個值:true和false;Number型別的值有很多很多;String型別的值理論上有無數個。
  • 值型別
    JS中的值有原始型別(Primitive)和物件型別(Object)。在做相加等操作時,不是原始型別的要先轉換為原始型別,再執行相關的操作。
二、JS中的加法運算

1、使用ToPrimitive運算轉換左右運算元為原始資料型別(primitive)。
2、在轉換後,如果其中一個運算元出現原始資料型別是“字串”型別值時,則另一運算元強制轉換為字串,然後做字串的連線運算。
3、在其他情況時,所有運算元都會轉換為原始資料型別的“數字”型別值,然後作數字的相加。

三、ToPrimitive內部運算

加號運算子只能用於原始資料型別,對於物件型別的值,需要進行資料轉換。在ECMAScript中,有一個抽象的ToPrimitive運算,用於將物件轉換為原始資料型別,在物件的加法、關係比較或值相等比較的運算中,都會用到。

關於ToPrimitive的說明語法:

ToPrimitive(input, PreferredType?)

input代表代入的值,PreferredType可以是數字(Number)或字串(String)其中一種,表示需要優先轉換的原始型別。但如果沒有提供這個值也就是預設情況,則會設定轉換的hint值為"default"。這個首選的轉換原始型別的指示(hint值),是在作內部轉換時由JS視情況自動加上的,一般情況就是預設值。

而在JS的Object原型的設計中,有兩個方法,valueOf與toString,在物件的資料型別轉換過程中會根據傳入的值調整被呼叫的順序。

  • PreferredType為數字(Number)時
    當PreferredType為數字(Number)時,input為要被轉換的值,轉換input值的步驟:
    1.如果input為原始資料型別,直接返回input。
    2.否則,input是物件,呼叫valueOf()方法,如果能得到原始資料型別的值,返回這個值。
    3.否則,input是物件,呼叫toString()方法,如果能得到原始資料型別的值,返回這個值。
    4.否則,丟擲TypeError錯誤。

  • PreferredType為字串(String)時
    1.如果input是原始資料型別,直接返回input。
    2.否則,input是物件,呼叫toString()方法,如果能得到原始資料型別的值,返回這個值。
    3.否則,input是物件,呼叫valueOf()方法,如果能得到原始資料型別的值,返回這個值。
    4.否則,丟擲TypeError錯誤。

  • PreferredType未提供(default)時
    PreferredType預設型別為Number,所以先呼叫valueOf(),再呼叫toString()。

其中比較特殊的是Date物件和Symbol物件,他們覆蓋了原來的PreferredType行為,Date物件預設首選型別是String。

四、valueOf()和toString()

valueOf()和toString()是Object上的兩個方法,但是在JS中,可能會根據Object之間的差異,返回值有所不同。

  • 普通的Object物件
    valueOf():返回物件本身。
    toString():"[object Object]"字串值,不同的內建物件的返回值是"[object type]"字串,"type"指的是物件本身的型別識別,例如Math物件是返回"[object Math]"字串。但有些內建物件因為覆蓋了這個方法,所以直接呼叫時不是這種值。(注意: 這個返回字串的前面的"object"開頭英文是小寫,後面開頭英文是大寫)。

利用Object中的toString來進行各種不同物件的判斷語法:

Object.prototype.toString.call([])
"[object Array]"

Object.prototype.toString.call(new Date)
"[object Date]"

需要配合call,才能得到正確的物件型別值。

  • Array(陣列)
    Array(陣列)雖然是個物件型別,但它與Object的設計不同,它的toString有覆蓋,說明一下陣列的valueOf與toString的兩個方法的返回值:
    valueOf(): 返回物件本身
    toString(): 相當於用陣列值呼叫join(',')所返回的字串。也就是[1,2,3].toString()會是"1,2,3",這點要特別注意。

  • Function物件
    Function物件很少會用到,它的toString也有被覆蓋,所以並不是Object中的那個toString,Function物件的valueOf與toString的兩個方法的返回值:
    valueOf(): 返回物件本身
    toString(): 返回函式中包含的程式碼轉為字串值。

  • Date物件
    valueOf(): 返回給定的時間轉為UNIX時間(自1 January 1970 00:00:00 UTC起算),但是以微秒計算的數字值
    toString(): 返回本地化的時間字串

五、Number、String、Boolean包裝物件

JS的包裝物件是必須使用new關鍵字進行物件例項化的,直接使用Number()、String()與Boolean()三個強制轉換函式的用法。例如new Number(123),而Number('123')則是強制轉換其他型別為數字型別的函式。

  • 包裝物件
    包裝物件是JS為原始資料型別數字、字串、布林專門設計的物件,包裝物件的valueOf與toString的兩個方法在原型上有經過覆蓋,所以它們的返回值與一般的 Object的設計不同:
    valueOf方法返回值: 對應的原始資料型別值
    toString方法返回值: 對應的原始資料型別值,轉換為字串型別時的字串值
    toString方法會比較特別,這三個包裝物件裡的toString的細部說明如下:
    1、Number包裝物件的toString方法: 可以有一個傳參,可以決定轉換為字串時的進位(2/8/16)
    2、 String包裝物件的toString方法: 與String包裝物件中的valueOf相同返回結果
    3、Boolean包裝物件的toString方法: 返回"true"或"false"字串

  • 強制轉換
    Number()、String()與Boolean()三個強制轉換函式,所對應的就是在ECMAScript標準中的ToNumber、ToString、ToBoolean三個內部運算轉換的對照表。

    通過ToNumber()把值轉換成Number:

    引數 結果
    undefined NaN
    null +0
    boolean true被轉換為1,false轉換為+0
    number 無需轉換
    string 由字串解析為數字。例如,”324″被轉換為324

    通過ToString()把值轉化成字串:

    引數 結果
    undefined “undefined”
    null “null”
    boolean “true” 或者 “false”
    number 數字作為字串。比如,”1.765″
    string 無需轉換
六、從例項中理解
  • 運算元其一為字串(String)
/**
 * 運算元其一為字串(String)
 */
console.log('12'+1);            // 121
console.log('abc'+'def');       // abcdef
console.log('1'+true);          //1true
console.log('1'+undefined);     //1undefined
console.log('1'+null);          //1null

運算元其一為字串,字串的拼接運算。

  • 運算元其一為數字(Number)
/**
 * 運算元其一為數字(Number)
 */
console.log(1+1);            // 2
console.log(1+'def');       // 1def
console.log(1+true);          //2
console.log(1+undefined);     //NaN
console.log(1+null);          //1

1+'def'為運算元其一為字串情況,其餘為在沒有字串情況下,運算元其一為數字,做型別轉換後相加。

  • 數字(Number)/字串(String)以外的原始型別相加
/**
 * 數字(Number)/字串(String)以外的原始型別相加
 */
console.log(true+true);             // 2
console.log(true+null);             // 1
console.log(true+undefined);        //NaN
console.log(undefined+null);        //NaN
console.log(undefined+undefined);   //NaN
console.log(null+null);            //0

當數字與字串以外的,其他原始資料型別直接使用加號運算時,就是轉為數字再運算,這與字串完全無關。

  • 空陣列 + 空陣列
console.log([] + []);        //""

兩個陣列相加,左右運算元先呼叫valueOf(),返回陣列本身,呼叫toString(),返回原始資料型別,即空字串,作連線操作,得到一個空字串。

  • 空物件 + 空物件
console.log({} + {});        //"[object Object][object Object]"

兩個物件相加,左右運算元先呼叫valueOf(),返回物件本身,呼叫toString(),返回原始資料型別,即物件字串[object Object],作連線操作,得到字串[object Object][object Object]

console.log({}+{})得到的這樣的結果,但是,在有些瀏覽器例如Firefox、Edge控制檯直接輸入{}+{},會得到NaN,因為瀏覽器會把{} + {}直譯為相當於+{}語句,因為它們會認為以花括號開頭({)的,是一個區塊語句的開頭,而不是一個物件字面量,所以會認為略過第一個{},把整個語句認為是個+{}的語句,也就是相當於強制求出數字值的Number({})函式呼叫運算,相當於Number("[object Object]")運算,最後得出的是NaN。如果加上圓括號({}) + {},則可以避免這樣的問題。

  • 空物件 + 空陣列
console.log({} + []);        //"[object Object]"

空物件和空陣列相加,左右運算元先呼叫valueOf(),返回物件陣列本身,呼叫toString(),返回原始資料型別,即物件字串[object Object]和"",作連線操作,得到字串[object Object]

直接console.log得到的都是一樣的值,但是直接在瀏覽器控制檯輸入:

> {} + []
0

> [] + {}
"[object Object]"

{} + []相當於+[]語句,也就是相當於強制求出數字值的Number([])運算,相當於Number("")運算,最後得出的是0數字。

  • Date物件
console.log(1 + (new Date()));       //"1Tue Aug 14 2018 21:18:24 GMT+0800 (中國標準時間)"

Date物件首選型別String,先呼叫toString(),得到字串做字串連線運算。

要得出Date物件中的valueOf返回值,需要使用一元加號(+),來強制轉換它為數字型別,例如以下的程式碼:

console.log(+new Date());
1534298171747
  • Symbols型別
    ES6中新加入的Symbols資料型別,它不算是一般的值也不是物件,它並沒有內部自動轉型的設計,所以完全不能直接用於加法運算,使用時會報錯。

  • +[]/+{}

console.log(+[]);     // 0
console.log(+{});     // NaN
console.log(+null);     //0
console.log(+true);     //1
console.log(+undefined);     //NaN

一元加號運算時,唯一的運算元相當於強制求出數字值的Number([])運算。

相關文章