JS的{} + {}與{} + []的結果是什麼?

在JS中的運算符共同的狀況中,(+)符號是很常見的一種,它有如下的使用狀況:html

  • 數字的加法運算,二元運算數組

  • 字符串的鏈接運算,二元運算,最高優先瀏覽器

  • 正號,一元運算,可延伸爲強制轉換其餘類型的運算元爲數字類型函數

固然,若是考慮多個符號一塊兒使用時,(+=)與(++)又是另外的用途。prototype

另外一個常見的是花括號({}),它有兩個用途也很常見:設計

  • 對象的字面文字定義code

  • 區塊語句htm

因此,要能回答這個問題,要先搞清楚重點是什麼?對象

第一個重點是:教程

加號(+)運算在JS中在使用上的規定是什麼。

第二個重點則是:

對象在JS中是怎麼轉換爲原始數據類型的值的。

加號運算符(+)

除了上面說明的常見狀況外,在標準中轉換的規則還有如下幾個,要注意它的順序:

operand + operand = result

  1. 使用ToPrimitive運算轉換左與右運算元爲原始數據類型值(primitive)

  2. 在第1步轉換後,若是有運算元出現原始數據類型是"字符串"類型值時,則另外一運算元做強制轉換爲字符串,而後做字符串的鏈接運算(concatenation)

  3. 在其餘狀況時,全部運算元都會轉換爲原始數據類型的"數字"類型值,而後做數學的相加運算(addition)

ToPrimitive內部運算

所以,加號運算符只能使用於原始數據類型,那麼對於對象類型的值,要如何轉換爲原始數據類型?下面說明是如何轉換爲原始數據類型的。

ECMAScript 6th Edition #7.1.1,有一個抽象的ToPrimitive運算,它會用於對象轉換爲原始數據類型,這個運算不僅會用在加號運算符,也會用在關係比較或值相等比較的運算中。下面有關於ToPrimitive的說明語法:

ToPrimitive(input, PreferredType?)

input表明代入的值,而PreferredType能夠是數字(Number)或字符串(String)其中一種,這會表明"優先的"、"首選的"的要進行轉換到哪種原始類型,轉換的步驟會依這裏的值而有所不一樣。但若是沒有提供這個值也就是預設狀況,則會設置轉換的hint值爲"default"。這個首選的轉換原始類型的指示(hint值),是在做內部轉換時由JS視狀況自動加上的,通常狀況就是預設值。

而在JS的Object原型的設計中,都必定會有兩個valueOftoString方法,因此這兩個方法在全部對象裏面都會有,不過它們在轉換有可能會交換被調用的順序。

當PreferredType爲數字(Number)時

PreferredType爲數字(Number)時,input爲要被轉換的值,如下是轉換這個input值的步驟:

  1. 若是input是原始數據類型,則直接返回input

  2. 不然,若是input是個對象時,則調用對象的valueOf()方法,若是能獲得原始數據類型的值,則返回這個值。

  3. 不然,若是input是個對象時,調用對象的toString()方法,若是能獲得原始數據類型的值,則返回這個值。

  4. 不然,拋出TypeError錯誤。

當PreferredType爲字符串(String)時

上面的步驟2與3對調,如同下面所說:

  1. 若是input是原始數據類型,則直接返回input

  2. 不然,若是input是個對象時,調用對象的toString()方法,若是能獲得原始數據類型的值,則返回這個值。

  3. 不然,若是input是個對象時,則調用對象的valueOf()方法,若是能獲得原始數據類型的值,則返回這個值。

  4. 不然,拋出TypeError錯誤。

PreferredType沒提供時,也就是hint爲"default"時

PreferredType爲數字(Number)時的步驟相同。

數字實際上是預設的首選類型,也就是說在通常狀況下,加號運算中的對象要做轉型時,都是先調用valueOf再調用toString

但這有兩個異常,一個是Date對象,另外一是Symbol對象,它們覆蓋了原來的PreferredType行爲,Date對象的預設首選類型是字符串(String)。

所以你會看到在一些教程文件上會區分爲兩大類對象,一類是 Date 對象,另外一類叫 非Date(non-date) 對象。由於這兩大類的對象在進行轉換爲原始數據類型時,首選類型剛好相反。

模擬代碼說明

以簡單的模擬代碼來講明,加號運算符(+)的運行過程就是像下面這個模擬碼同樣,我想這會很容易理解:

a + b:
    pa = ToPrimitive(a)
    pb = ToPrimitive(b)

    if(pa is string || pb is string)
       return concat(ToString(pa), ToString(pb))
    else
       return add(ToNumber(pa), ToNumber(pb))

步驟簡單來講就是,運算元都用ToPrimitive先轉換爲原始數據類型,而後其一是字符串時,使用ToString強制轉換另外一個運算元,而後做字符串鏈接運算。要否則,就是都使用ToNumber強制轉換爲數字做加法運算。

ToPrimitive在遇到對象類型時,預設調用方式是先調用valueOf再調用toString,通常狀況數字類型是首選類型。

上面說的ToStringToNumber這兩個也是JS內部的抽象運算。

valueOf與toString方法

valueOfToString是在Object中的兩個必有的方法,位於Object.prototype上,它是對象要轉爲原始數據類型的兩個姐妹方法。從上面的內容已經能夠看到,ToPrimitive這個抽象的內部運算,會依照設置的首選的類型,決定要前後調用valueOftoString方法的順序,當數字爲首選類型時,優先使用valueOf,而後再調用toString。當字符串爲首選類型時,則是相反的順序。預設調用方式則是如數字首選類型同樣,是先調用valueOf再調用toString

JS對於Object與Array的設計

在JS中所設計的Object純對象類型的valueOftoString方法,它們的返回以下:

  • valueOf方法返回值: 對象自己。

  • toString方法返回值: "[object Object]"字符串值,不一樣的內建對象的返回值是"[object type]"字符串,"type"指的是對象自己的類型識別,例如Math對象是返回"[object Math]"字符串。但有些內建對象由於覆蓋了這個方法,因此直接調用時不是這種值。(注意: 這個返回字符串的前面的"object"開頭英文是小寫,後面開頭英文是大寫)

你有可能會看過,利用Object中的toString來進行各類不一樣對象的判斷語法,這在之前JS能用的函數庫或方法很少的年代常常看到,不過它須要配合使用函數中的call方法,才能輸出正確的對象類型值,例如:

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

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

因此,從上面的內容就能夠知道,下面的這段代碼的結果會是調用到toString方法(由於valueOf方法的返回並非原始的數據類型):

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

一元正號(+),具備讓首選類型(也就是hint)設置爲數字(Number)的功能,因此能夠強制讓對象轉爲數字類型,通常的對象會轉爲:

> +{} //至關於 +"[object Object]"
NaN

固然,對象的這兩個方法均可以被覆蓋,你能夠用下面的代碼來觀察這兩個方法的運行順序,下面這個都是先調用valueOf的狀況:

let obj = {
  valueOf: function () {
      console.log('valueOf');
      return {}; // object
  },
  toString: function () {
      console.log('toString');
      return 'obj'; // string
  }
}
console.log(1 + obj);  //valueOf -> toString -> '1obj'
console.log(+obj); //valueOf -> toString -> NaN
console.log('' + obj); //valueOf -> toString -> 'obj'

先調用toString的狀況比較少見,大概只有Date對象或強制要轉換爲字符串時纔會看到:

let obj = {
  valueOf: function () {
      console.log('valueOf');
      return 1; // number
  },
  toString: function () {
      console.log('toString');
      return {}; // object
  }
}
alert(obj); //toString -> valueOf -> alert("1");
String(obj); //toString -> valueOf -> "1";

而下面這個例子會形成錯誤,由於不論順序是如何都得不到原始數據類型的值,錯誤消息是"TypeError: Cannot convert object to primitive value",從這個消息中很明白的告訴你,它這裏面會須要轉換對象到原始數據類型:

let obj = {
  valueOf: function () {
      console.log('valueOf');
      return {}; // object
  },
  toString: function () {
      console.log('toString');
      return {}; // object
  }
}

console.log(obj + obj);  //valueOf -> toString -> error!

Array(數組)很經常使用到,雖然它是個對象類型,但它與Object的設計不一樣,它的toString有覆蓋,說明一下數組的valueOftoString的兩個方法的返回值:

  • valueOf方法返回值: 對象自己。(與Object同樣)

  • toString方法返回值: 至關於用數組值調用join(',')所返回的字符串。也就是[1,2,3].toString()會是"1,2,3",這點要特別注意。

Function對象不多會用到,它的toString也有被覆蓋,因此並非Object中的那個toString,Function對象的valueOftoString的兩個方法的返回值:

  • valueOf方法返回值: 對象自己。(與Object同樣)

  • toString方法返回值: 函數中包含的代碼轉爲字符串值

Number、String、Boolean三個包裝對象

包裝對象是JS爲原始數據類型數字、字符串、布爾專門設計的對象,全部的這三種原始數據類型所使用到的屬性與方法,都是在這上面所提供。

包裝對象的valueOftoString的兩個方法在原型上有通過覆蓋,因此它們的返回值與通常的Object的設計不一樣:

  • valueOf方法返回值: 對應的原始數據類型值

  • toString方法返回值: 對應的原始數據類型值,轉換爲字符串類型時的字符串值

toString方法會比較特別,這三個包裝對象裏的toString的細部說明以下:

  • Number包裝對象的toString方法: 能夠有一個傳參,能夠決定轉換爲字符串時的進位(二、八、16)

  • String包裝對象的toString方法: 與String包裝對象中的valueOf相同返回結果

  • Boolean包裝對象的toString方法: 返回"true"或"false"字符串

另外,常被搞混的是直接使用Number()String()Boolean()三個強制轉換函數的用法,這與包裝對象的用法不一樣,包裝對象是必須使用new關鍵字進行對象實例化的,例如new Number(123),而Number('123')則是強制轉換其餘類型爲數字類型的函數。

Number()String()Boolean()三個強制轉換函數,所對應的就是在ECMAScript標準中的ToNumberToStringToBoolean三個內部運算轉換的對照表。而當它們要轉換對象類型前,會先用上面說的ToPrimitive先轉換對象爲原始數據類型,再進行轉換到所要的類型值。

無論如何,包裝對象不多會被使用到,通常咱們只會直接使用原始數據類型的值。而強制轉換函數由於也有替換的語法,它們會被用到的機會也很少。

實例

字符串 + 其餘原始類型

字符串在加號運算有最高的優先運算,與字符串相加一定是字符串鏈接運算(concatenation)。全部的其餘原始數據類型轉爲字符串,能夠參考ECMAScript標準中的ToString對照表,如下爲一些簡單的例子:

> '1' + 123
"1123"

> '1' + false
"1false"

> '1' + null
"1null"

> '1' + undefined
"1undefined"

數字 + 其餘的非字符串的原始數據類型

數字與其餘類型做相加時,除了字符串會優先使用字符串鏈接運算(concatenation)的,其餘都要依照數字爲優先,因此除了字符串以外的其餘原始數據類型,都要轉換爲數字來進行數學的相加運算。若是明白這項規則,就會很容易的得出加法運算的結果。

全部轉爲數字類型能夠參考ECMAScript標準中的ToNumber對照表,如下爲一些簡單的例子:

> 1 + true //true轉爲1, false轉爲0
2

> 1 + null //null轉爲0
1

> 1 + undefined //undefined轉爲NaN
NaN

數字/字符串之外的原始數據類型做加法運算

當數字與字符串之外的,其餘原始數據類型直接使用加號運算時,就是轉爲數字再運算,這與字符串徹底無關。

> true + true
2

> true + null
1

> undefined + null
NaN

空數組 + 空數組

> [] + []
""

兩個數組相加,依然按照valueOf -> toString的順序,但由於valueOf是數組自己,因此會以toString的返回值纔是原始數據類型,也就是空字符串,因此這個運算至關於兩個空字符串在相加,依照加法運算規則第2步驟,是字符串鏈接運算(concatenation),兩個空字符串鏈接最後得出一個空字符串。

空對象 + 空對象

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

兩個空對象相加,依然按照valueOf -> toString的順序,但由於valueOf是對象自己,因此會以toString的返回值纔是原始數據類型,也就是"[object Object]"字符串,因此這個運算至關於兩個"[object Object]"字符串在相加,依照加法運算規則第2步驟,是字符串鏈接運算(concatenation),最後得出一個"object Object"字符串。

可是這個結果有異常,上面的結果只是在Chrome瀏覽器上的結果(v55),怎麼說呢?

有些瀏覽器例如Firefox、Edge瀏覽器會把{} + {}直譯爲至關於+{}語句,由於它們會認爲以花括號開頭({)的,是一個區塊語句的開頭,而不是一個對象字面量,因此會認爲略過第一個{},把整個語句認爲是個+{}的語句,也就是至關於強制求出數字值的Number({})函數調用運算,至關於Number("[object Object]")運算,最後得出的是NaN

特別注意: {} + {}在不一樣的瀏覽器有不一樣結果

若是在第一個(前面)的空對象加上圓括號(()),這樣JS就會認爲前面是個對象,就能夠得出一樣的結果:

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

或是分開來先聲明對象的變量值,也能夠得出一樣的結果,像下面這樣:

> let foo = {}, bar = {};
> foo + bar;

注: 上面說的行爲這與加號運算的第一個(前面)的對象字面值是否是個空對象無關,就算是裏面有值的對象字面,例如{a:1, b:2},也是一樣的結果。

注: 上面說的Chrome瀏覽器是在v55版本中的主控臺直接運行的結果。其它舊版本有可能並不是此結果。

空對象 + 空數組

上面一樣的把{}看成區塊語句的狀況又會發生,不過此次全部的瀏覽器都會有一致結果,若是{}(空對象)在前面,而[](空數組)在後面時,前面(左邊)那個運算元會被認爲是區塊語句而不是對象字面量。

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

> {} + []
0

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

特別注意: 因此若是第一個(前面)是{}時,後面加上其餘的像數組、數字或字符串,這時候加號運算會直接變爲一元正號運算,也就是強制轉爲數字的運算。這是個陷阱要當心。

Date對象

Date對象的valueOftoString的兩個方法的返回值:

  • valueOf方法返回值: 給定的時間轉爲UNIX時間(自1 January 1970 00:00:00 UTC起算),可是以微秒計算的數字值

  • toString方法返回值: 本地化的時間字符串

Date對象上面有說起是首選類型爲"字符串"的一種異常的對象,這與其餘的對象的行爲不一樣(通常對象會先調用valueOf再調用toString),在進行加號運算時時,它會優先使用toString來進行轉換,最後一定是字符串鏈接運算(concatenation),例如如下的結果:

> 1 + (new Date())
> "1Sun Nov 27 2016 01:09:03 GMT+0800 (CST)"

要得出Date對象中的valueOf返回值,須要使用一元加號(+),來強制轉換它爲數字類型,例如如下的代碼:

> +new Date()
1480180751492

Symbols類型

ES6中新加入的Symbols數據類型,它不算是通常的值也不是對象,它並無內部自動轉型的設計,因此徹底不能直接用於加法運算,使用時會報錯。

總結

{} + {}的結果是會因瀏覽器而有不一樣結果,Chrome(v55)中是[object Object][object Object]字符串鏈接,但其它的瀏覽器則是認爲至關於+{}運算,得出NaN數字類型。

{} + []的結果是至關於+[],結果是0數字類型。

參考文章

相關文章
相關標籤/搜索