在JS中的運算符共同的狀況中,(+)符號是很常見的一種,它有如下的使用狀況:html
數字的加法運算,二元運算數組
字符串的鏈接運算,二元運算,最高優先瀏覽器
正號,一元運算,可延伸爲強制轉換其餘類型的運算元爲數字類型函數
固然,若是考慮多個符號一塊兒使用時,(+=)與(++)又是另外的用途。prototype
另外一個常見的是花括號({}),它有兩個用途也很常見:設計
對象的字面文字定義code
區塊語句htm
因此,要能回答這個問題,要先搞清楚重點是什麼?對象
第一個重點是:教程
加號(+)運算在JS中在使用上的規定是什麼。
第二個重點則是:
對象在JS中是怎麼轉換爲原始數據類型的值的。
除了上面說明的常見狀況外,在標準中轉換的規則還有如下幾個,要注意它的順序:
operand + operand = result
使用ToPrimitive
運算轉換左與右運算元爲原始數據類型值(primitive)
在第1步轉換後,若是有運算元出現原始數據類型是"字符串"類型值時,則另外一運算元做強制轉換爲字符串,而後做字符串的鏈接運算(concatenation)
在其餘狀況時,全部運算元都會轉換爲原始數據類型的"數字"類型值,而後做數學的相加運算(addition)
所以,加號運算符只能使用於原始數據類型,那麼對於對象類型的值,要如何轉換爲原始數據類型?下面說明是如何轉換爲原始數據類型的。
在ECMAScript 6th Edition #7.1.1,有一個抽象的ToPrimitive
運算,它會用於對象轉換爲原始數據類型,這個運算不僅會用在加號運算符,也會用在關係比較或值相等比較的運算中。下面有關於ToPrimitive
的說明語法:
ToPrimitive(input, PreferredType?)
input
表明代入的值,而PreferredType
能夠是數字(Number)或字符串(String)其中一種,這會表明"優先的"、"首選的"的要進行轉換到哪種原始類型,轉換的步驟會依這裏的值而有所不一樣。但若是沒有提供這個值也就是預設狀況,則會設置轉換的hint
值爲"default"
。這個首選的轉換原始類型的指示(hint
值),是在做內部轉換時由JS視狀況自動加上的,通常狀況就是預設值。
而在JS的Object
原型的設計中,都必定會有兩個valueOf
與toString
方法,因此這兩個方法在全部對象裏面都會有,不過它們在轉換有可能會交換被調用的順序。
當PreferredType
爲數字(Number)時,input
爲要被轉換的值,如下是轉換這個input
值的步驟:
若是input
是原始數據類型,則直接返回input
。
不然,若是input
是個對象時,則調用對象的valueOf()
方法,若是能獲得原始數據類型的值,則返回這個值。
不然,若是input
是個對象時,調用對象的toString()
方法,若是能獲得原始數據類型的值,則返回這個值。
不然,拋出TypeError錯誤。
上面的步驟2與3對調,如同下面所說:
若是input
是原始數據類型,則直接返回input
。
不然,若是input
是個對象時,調用對象的toString()
方法,若是能獲得原始數據類型的值,則返回這個值。
不然,若是input
是個對象時,則調用對象的valueOf()
方法,若是能獲得原始數據類型的值,則返回這個值。
不然,拋出TypeError錯誤。
與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
,通常狀況數字類型是首選類型。
上面說的ToString
與ToNumber
這兩個也是JS內部的抽象運算。
valueOf
與ToString
是在Object中的兩個必有的方法,位於Object.prototype上,它是對象要轉爲原始數據類型的兩個姐妹方法。從上面的內容已經能夠看到,ToPrimitive
這個抽象的內部運算,會依照設置的首選的類型,決定要前後調用valueOf
與toString
方法的順序,當數字爲首選類型時,優先使用valueOf
,而後再調用toString
。當字符串爲首選類型時,則是相反的順序。預設調用方式則是如數字首選類型同樣,是先調用valueOf
再調用toString
。
在JS中所設計的Object
純對象類型的valueOf
與toString
方法,它們的返回以下:
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
有覆蓋,說明一下數組的valueOf
與toString
的兩個方法的返回值:
valueOf
方法返回值: 對象自己。(與Object同樣)
toString
方法返回值: 至關於用數組值調用join(',')
所返回的字符串。也就是[1,2,3].toString()
會是"1,2,3"
,這點要特別注意。
Function對象不多會用到,它的toString
也有被覆蓋,因此並非Object中的那個toString
,Function對象的valueOf
與toString
的兩個方法的返回值:
valueOf
方法返回值: 對象自己。(與Object同樣)
toString
方法返回值: 函數中包含的代碼轉爲字符串值
包裝對象是JS爲原始數據類型數字、字符串、布爾專門設計的對象,全部的這三種原始數據類型所使用到的屬性與方法,都是在這上面所提供。
包裝對象的valueOf
與toString
的兩個方法在原型上有通過覆蓋,因此它們的返回值與通常的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標準中的ToNumber
、ToString
、ToBoolean
三個內部運算轉換的對照表。而當它們要轉換對象類型前,會先用上面說的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對象的valueOf
與toString
的兩個方法的返回值:
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
ES6中新加入的Symbols數據類型,它不算是通常的值也不是對象,它並無內部自動轉型的設計,因此徹底不能直接用於加法運算,使用時會報錯。
{} + {}
的結果是會因瀏覽器而有不一樣結果,Chrome(v55)中是[object Object][object Object]
字符串鏈接,但其它的瀏覽器則是認爲至關於+{}
運算,得出NaN
數字類型。
{} + []
的結果是至關於+[]
,結果是0
數字類型。