摘要:於是其他的東西相加的時(shí)候?qū)?huì)被轉(zhuǎn)型成數(shù)字或者字串。整個(gè)過程即先依據(jù)轉(zhuǎn)換為原始型別,這裡要注意並不是最終結(jié)果,再來依據(jù)需要看是否要再將原生型別轉(zhuǎn)成數(shù)字或字串。這個(gè)結(jié)果等於只作的算。
這篇文章源自 What is {} + {} in JavaScript? 其實(shí)早在 2012 年就問世了。時(shí)至 2016 年末純粹是在聊天時(shí)重提這個(gè)問題,但由於年紀(jì)大了記憶力不佳,竟然記錯(cuò)了,所以才會(huì)有這一篇重新紀(jì)錄的筆記。
源頭是當(dāng)時(shí)由 Gary Bernhardt 在閃電秀中指出 Javascript 的詭異行為 - Wat
在開始之前我們先補(bǔ)充一下關(guān)於 Javascript 型別的整理
基礎(chǔ)型別(Primitive Type)
string
number
boolean
nudefined
null
symbol(ECMAScript 6)
物件型別(Object Type)
object
Function
Array
Date
其他
關(guān)於 Javascript 的加法其實(shí)是很簡(jiǎn)單的:原則上您只能夠?qū)?shù)字(Number)或字串(String)相加。
於是其他的東西相加的時(shí)候?qū)?huì)被轉(zhuǎn)型成數(shù)字或者字串。為了理解轉(zhuǎn)換的機(jī)制我們需要先釐清一些事情,我們得引用 ECMA-262 5.1 版規(guī)範(fàn)的 9.1 章節(jié)或新版 ECMA-262 7 的 7.1.1 的說明
讓我們來複習(xí)一下,在 Javascript 中關(guān)於型別的大分類 - 有兩種類型的值:
primitive 原生
object 物件
就像上面列出來的除了 undefined, null, boolean, number, string, symbol 之外的東西都是物件,當(dāng)然陣列和函式都是物件的一種。
轉(zhuǎn)換加法運(yùn)算子整體來說會(huì)執(zhí)行三種類型的轉(zhuǎn)換,結(jié)果就是它會(huì)將值轉(zhuǎn)成原生型別 primitive 中的 Number 或 String
1.1 使用 ToPrimitive() 將值先轉(zhuǎn)換成為原生型別在內(nèi)部 ToPrimitive() 的使用調(diào)用格式為 ToPrimitive(input, PreferredType?)
第二個(gè)為可選參數(shù) PreferredType 值可以是 Number, String,這只是一個(gè)轉(zhuǎn)型偏好的註記,最終的結(jié)果可以是任一原生型別。
假設(shè) PreferredType 是 Number 那麼執(zhí)行轉(zhuǎn)換的步驟如下
如果輸入是原生型別,直接回傳
否則調(diào)用 obj.valueOf() 如果值是原生型別就回傳
還不是原生型別的話則調(diào)用 obj.toString() 結(jié)果如果是原生型別就回傳
否則拋出例外
如果 PreferredType 是 String 則 2, 3 步驟交換。
如果沒有 PreferredType 則 Date 預(yù)設(shè)為 String,其他型別則預(yù)設(shè)是 Number。
下列說明 ToNumber() 是如何轉(zhuǎn)換原始型別為數(shù)字
undefined -> NaN
null -> +0
boolean
true -> 1
false -> +0
number -> 不轉(zhuǎn)換
string -> 將字串轉(zhuǎn)換成數(shù)字,不過這其中有些小細(xì)節(jié)下面整理給您,結(jié)果與 Number(input) 是一樣的
+"23.1" = 23.1
+"2e1" = 20
+"25px" = NaN
+"p23" = NaN
+"010" = 10
+"0xf" = 15
+[1] = 1
+[1, 2] = NaN
過程是這樣的:一個(gè)物件 obj 透過呼叫 ToPrimitive(obj, Number) 轉(zhuǎn)換成原始型別,接著在使用 ToNumber() 取得最後的結(jié)果
1.3 使用 ToString() 轉(zhuǎn)換為字串下面說明 ToString() 如何轉(zhuǎn)換原生型別為字串
undefined -> "undefined"
null -> "null"
boolean
true -> "true"
false -> "false"
number -> "1.234"
string -> 不轉(zhuǎn)換
一個(gè)物件 obj 透過調(diào)用 ToPrimitive(obj, String) 轉(zhuǎn)換為原始型別,然後 ToString() 取得最後結(jié)果
1.4 實(shí)作下面這個(gè)物件可以讓我們觀察轉(zhuǎn)換的過程
var obj = { valueOf: function () { console.log("valueOf") return {} }, toString: function () { console.log("toString") return {} } } Number(obj) +obj // 等價(jià)
> valueOf > toString > TypeError: can"t convert obj to number
當(dāng) Number() 作為 function 使用時(shí)內(nèi)部會(huì)執(zhí)行轉(zhuǎn)換 ToNumber() 的流程,根據(jù)上面的實(shí)作可以看出就如我們上面所敘述的規(guī)則流程一樣。
整個(gè)過程即先依據(jù) PreferredType 轉(zhuǎn)換為原始型別,這裡要注意並不是最終結(jié)果,再來依據(jù)需要看是否要再將原生型別轉(zhuǎn)成數(shù)字或字串。
舉例下面的例子
val1 + val2
要解析上面這個(gè) expression 德遵循 ECMA-262 5.1 規(guī)範(fàn)的 11.6.1 章節(jié)或新版 ECAM-262 7 版的 12.8.3 說明的步驟:
(1). 轉(zhuǎn)換兩邊的運(yùn)算元為原生型別 Primitive
prim1 = ToPrimitive(val1) prim2 = ToPrimitive(val2)
由於 PreferredType 被省略了,因此物件除了 Date 是代入 String 外,其他的是 Number。
(2). 兩數(shù)相加的情況下,如果 prim1 或 prim2 只有要一個(gè)是 String 那麼兩者都會(huì)被轉(zhuǎn)成字串,最終結(jié)果就是串接字串。
(3). 否則,兩者都會(huì)被轉(zhuǎn)成數(shù)字並加總。
到這一步可能造成困惑的地方:
+[] // Number("") = 0 [] + [] // "" + "" = ""2.1 如同預(yù)期的結(jié)果
當(dāng)您執(zhí)行下面的範(fàn)例,兩個(gè)陣列相加
> [] + [] ""
第一步使用 valueOf() 轉(zhuǎn)換兩個(gè)陣列 [] ,其會(huì)回傳陣列本身,因?yàn)檫€不是 Primitive 所以繼續(xù)使用 toString() 結(jié)果回傳一個(gè)空字串。
兩個(gè)空字串相加還是空字串。在只有單一 +[] 的狀況下 Javascript 會(huì)幫我們轉(zhuǎn)成數(shù)字 ToNumber(),一元運(yùn)算子和二元的行為有些差異。
第二個(gè)例子我們相加陣列和物件
> [] + {} "[object Object]"
空物件跟陣列一樣 valueOf() 還是物件,然後 toString() 物件會(huì)轉(zhuǎn)換成 [object Object] 相加就是上面的結(jié)果。
5 + new Number(7) // 12 6 + { valueOf: function () { return 2} } // 8 "abc" + { toString: function () { return "def"} } // "abcdef"2.2 非預(yù)期的詭異結(jié)果
到這一步我們覺得已經(jīng)掌握了 Javascript,但 Javascript 可怕的地方就是總是可以給您驚喜驚嚇。
當(dāng)我們?cè)囍鴮蓚€(gè)物件實(shí)字 {} 相加時(shí)
> {} + {} NaN
啥米鬼!? 造成這個(gè)問題的原因是 Javascript 把第一個(gè) {} 當(dāng)作是 code block 並忽略它。這個(gè)結(jié)果等於 Javascript 只作 +{} 的運(yùn)算。
上面有提過在 + 加號(hào)當(dāng)作一元運(yùn)算子的時(shí)候會(huì)嘗試把值轉(zhuǎn)成數(shù)字,下面就是等價(jià)執(zhí)行過程:
+{} Number({}) Number({}.valueOf()) // 依然不是 Primitive 所以要繼續(xù)轉(zhuǎn)型 Number({}.toString()) Number("[object Object]") NaN
那為什麼第一個(gè) {} 會(huì)被解析成程式片段而不是一個(gè)物件實(shí)字呢?因?yàn)?Javascript 將其解析為一個(gè) statement 。因此如果要處理這個(gè)問題我們可以透過 () 強(qiáng)迫 JS 將其視為 expression
({} + {}) // [object Object][object Object]" var o = {} + {} o // "[object Object][object Object]"
其他還有一些技巧,如果您想知道更多細(xì)節(jié)請(qǐng)參考重讀 Axel 的 Javascript 中的 Expression vs Statement 一文
經(jīng)過了上面的解釋,我想您就不會(huì)很驚訝下面這段程式的結(jié)果了
> {} + [] 0
+[] Number([]) Number([].valueOf()) // 依然不是 Primitive 所以要繼續(xù)轉(zhuǎn)型 Number([].toString()) Number("") 0
有趣的是 Node.js 的 REPL 解析輸入的方式和 Firefox, Chrome 等瀏覽器不同,它會(huì)將下面的輸入解析成 expression
> {} + {} "[object Object][object Object]" > {} + [] "[object Object]"
這個(gè)結(jié)果就好像把 input 放到 console.log() 的參數(shù)內(nèi)一樣。
總結(jié)在大多數(shù)的情況下,並不難理解 Javascript 加號(hào)的運(yùn)作,您只能相加數(shù)字或字串。物件會(huì)被轉(zhuǎn)換成數(shù)字或字串。當(dāng)然會(huì)造成混亂的還有一元運(yùn)算與二元運(yùn)算之間的行為差異這點(diǎn)值得注意一下,然後遵循上面談?wù)摰囊?guī)則,相信您應(yīng)該就能參透 Javascript 一些奇怪的行為。另外如果您想要合併陣列那您需要使用 Array.concat([3, 4])
> [1, 2].concat([3, 4]) [1, 2, 3, 4]
如果是合併物件在快到 2017 的今天,您可以使用 Object.assign 或者其他函式庫例如 Underscore 等
var o1 = { a: 1, b: 2} var o2 = { c: 3, d: 4} Object.assign(o1, o2) o1 /** {a: 1, b: 2, c: 3, d: 4} */參考
Fake operator overloading in Javascript
Javascript values: not everything is an object
object plus object
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/86662.html
摘要:中的執(zhí)行環(huán)境與堆疊在這篇筆記中我將會(huì)深入的探討底層中的一些觀念,其中最重要的就是執(zhí)行環(huán)境。其他執(zhí)行環(huán)境都可以存取全域的東西。在這個(gè)階段直譯器會(huì)建立,透過掃描函式傳入的參數(shù),內(nèi)部的函式宣告,變數(shù)宣告。 Javascript 中的執(zhí)行環(huán)境與堆疊 在這篇筆記中我將會(huì)深入的探討 JS 底層中的一些觀念,其中最重要的就是執(zhí)行環(huán)境(Execution Context)。當(dāng)您閱讀完這篇文章後您可能會(huì)...
摘要:接下來我們將會(huì)更具體的說明是什麼東西和這傢伙會(huì)怎麼解決這些問題,並且列出目前開發(fā)中一些令人興奮的功能。這個(gè)功能甚至還沒有一個(gè)瀏覽器支援。完整的清單請(qǐng)查閱目前還未被寫入規(guī)範(fàn),意思是這邊提到任何內(nèi)容極有可能會(huì)改變。 譯者:其實(shí)...我想說這可能是最令我感到興奮..但又害怕頭痛的功能... 附上原文連結(jié) 你曾經(jīng)想要使用某個(gè) CSS 的新功能,但是最後卻因?yàn)檫@個(gè)功能瀏覽器還未全面支援而放棄了嗎...
摘要:響應(yīng)式自適應(yīng)響應(yīng)式和自適應(yīng)設(shè)計(jì)共同點(diǎn)都是要處理在不同裝置下瀏覽網(wǎng)頁的問題,可讀性,版型等等。關(guān)於由於我們?nèi)詴?huì)在的設(shè)計(jì)中使用百分比,如此一來或多或少還是會(huì)受到進(jìn)位誤差的影響,因此並不是佈局的萬能藥。 showImg(https://segmentfault.com/img/remote/1460000006786975); 問題 為了要能夠解釋得更清楚我們需要實(shí)作一小段跟我們會(huì)遇到的問題...
摘要:整體來說網(wǎng)頁主要是由矩形所構(gòu)成的,另一方面印刷品則具備相對(duì)多樣性。即便我們?cè)O(shè)定的元素不再是矩形,但周圍的元素排列方式仍然維持原本矩形的佈局。為了達(dá)成周圍的元素跟著裁切的形狀,我們可以使用屬性。周圍的元素仍需要靠來修正。 整體來說網(wǎng)頁主要是由矩形所構(gòu)成的,另一方面印刷品則具備相對(duì)多樣性。造成這樣差異的原因有很多,不過其中一個(gè)即是缺少合適的工具。 這篇文章主要會(huì)介紹 clip-path 這...
摘要:確切位置因平臺(tái)而異。如果以編程方式使用,這個(gè)頁面也是一個(gè)強(qiáng)大的調(diào)試工具,能看到所有原始的協(xié)議命令通過連線,於瀏覽器進(jìn)行通信。警告協(xié)議可以做很多有趣的事,但作為入門選項(xiàng)他令人沮喪。目前,提供了比協(xié)議高級(jí)別的。 本文翻譯自:Getting Started with Headless Chrome原文更新時(shí)間:July 28,2017作者:Eric Bidelman(Engineer @ G...
閱讀 2701·2021-09-22 15:58
閱讀 2240·2019-08-29 16:06
閱讀 911·2019-08-29 14:14
閱讀 2815·2019-08-29 13:48
閱讀 2461·2019-08-28 18:01
閱讀 1509·2019-08-28 17:52
閱讀 3331·2019-08-26 14:05
閱讀 1626·2019-08-26 13:50