资讯专栏INFORMATION COLUMN

關於 Javascript {} + {}

charles_paul / 2018人阅读

摘要:於是其他的東西相加的時候將會被轉型成數字或者字串。整個過程即先依據轉換為原始型別,這裡要注意並不是最終結果,再來依據需要看是否要再將原生型別轉成數字或字串。這個結果等於只作的算。

這篇文章源自 What is {} + {} in JavaScript? 其實早在 2012 年就問世了。時至 2016 年末純粹是在聊天時重提這個問題,但由於年紀大了記憶力不佳,竟然記了,所以才會有這一篇重新紀錄的筆記。

源頭是當時由 Gary Bernhardt 在閃電秀中指出 Javascript 的詭異行為 - Wat

在開始之前我們先補充一下關於 Javascript 型別的整理

基礎型別(Primitive Type)

string

number

boolean

nudefined

null

symbol(ECMAScript 6)

物件型別(Object Type)

object

Function

Array

Date

其他

關於 Javascript 的加法其實是很簡單的:原則上您只能夠將數字(Number)或字串(String)相加。
於是其他的東西相加的時候將會被轉型成數字或者字串。為了理解轉換的機制我們需要先釐清一些事情,我們得引用 ECMA-262 5.1 版規範的 9.1 章節或新版 ECMA-262 7 的 7.1.1 的說明

讓我們來複習一下,在 Javascript 中關於型別的大分類 - 有兩種類型的

primitive 原生

object 物件

就像上面列出來的除了 undefined, null, boolean, number, string, symbol 之外的東西都是物件,當然陣列和函式都是物件的一種。

轉換

加法運算子整體來說會執行三種類型的轉換,結果就是它會將轉成原生型別 primitive 中的 Number 或 String

1.1 使用 ToPrimitive() 將值先轉換成為原生型別

在內部 ToPrimitive() 的使用調用格式為 ToPrimitive(input, PreferredType?)
第二個為可選參數 PreferredType 值可以是 Number, String,這只是一個轉型偏好的註記,最終的結果可以是任一原生型別。
假設 PreferredType 是 Number 那麼執行轉換的步驟如下

如果輸入是原生型別,直接回傳

否則調用 obj.valueOf() 如果值是原生型別就回傳

還不是原生型別的話則調用 obj.toString() 結果如果是原生型別就回傳

否則拋出例外

如果 PreferredType 是 String 則 2, 3 步驟交換。
如果沒有 PreferredType 則 Date 預設為 String,其他型別則預設是 Number。

1.2 使用 ToNumber() 轉換為數字

下列說明 ToNumber() 是如何轉換原始型別為數字

undefined -> NaN

null -> +0

boolean

true -> 1

false -> +0

number -> 不轉換

string -> 將字串轉換成數字,不過這其中有些小細節下面整理給您,結果與 Number(input) 是一樣的

+"23.1" = 23.1

+"2e1" = 20

+"25px" = NaN

+"p23" = NaN

+"010" = 10

+"0xf" = 15

+[1] = 1

+[1, 2] = NaN

過程是這樣的:一個物件 obj 透過呼叫 ToPrimitive(obj, Number) 轉換成原始型別,接著在使用 ToNumber() 取得最後的結果

1.3 使用 ToString() 轉換為字串

下面說明 ToString() 如何轉換原生型別為字串

undefined -> "undefined"

null -> "null"

boolean

true -> "true"

false -> "false"

number -> "1.234"

string -> 不轉換

一個物件 obj 透過調用 ToPrimitive(obj, String) 轉換為原始型別,然後 ToString() 取得最後結果

1.4 實作

下面這個物件可以讓我們觀察轉換的過程

var obj = {
  valueOf: function () {
    console.log("valueOf")
    return {}
  },
  toString: function () {
    console.log("toString")
    return {}
  }
}

Number(obj)
+obj // 等價
> valueOf
> toString
> TypeError: can"t convert obj to number

當 Number() 作為 function 使用時內部會執行轉換 ToNumber() 的流程,根據上面的實作可以看出就如我們上面所敘述的規則流程一樣。
整個過程即先依據 PreferredType 轉換為原始型別,這裡要注意並不是最終結果,再來依據需要看是否要再將原生型別轉成數字或字串。

加法

舉例下面的例子

val1 + val2

要解析上面這個 expression 德遵循 ECMA-262 5.1 規範的 11.6.1 章節或新版 ECAM-262 7 版的 12.8.3 說明的步驟:

(1). 轉換兩邊的運算元為原生型別 Primitive

prim1 = ToPrimitive(val1)
prim2 = ToPrimitive(val2)

由於 PreferredType 被省略了,因此物件除了 Date 是代入 String 外,其他的是 Number。

(2). 兩數相加的情況下,如果 prim1 或 prim2 只有要一個是 String 那麼兩者都會被轉成字串,最終結果就是串接字串。

(3). 否則,兩者都會被轉成數字並加總。

到這一步可能造成困惑的地方:

+[] // Number("") = 0
[] + [] // "" + "" = ""
2.1 如同預期的結果

當您執行下面的範例,兩個陣列相加

> [] + []
""

第一步使用 valueOf() 轉換兩個陣列 [] ,其會回傳陣列本身,因為還不是 Primitive 所以繼續使用 toString() 結果回傳一個空字串。
兩個空字串相加還是空字串。在只有單一 +[] 的狀況下 Javascript 會幫我們轉成數字 ToNumber(),一元運算子和二元的行為有些差異。

第二個例子我們相加陣列和物件

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

空物件跟陣列一樣 valueOf() 還是物件,然後 toString() 物件會轉換成 [object Object] 相加就是上面的結果。

5 + new Number(7) // 12
6 + { valueOf: function () { return 2} } // 8
"abc" + { toString: function () { return "def"} } // "abcdef"
2.2 非預期的詭異結果

到這一步我們覺得已經掌握了 Javascript,但 Javascript 可怕的地方就是總是可以給您驚喜驚嚇。
當我們試著將兩個物件實字 {} 相加時

> {} + {}
NaN

啥米鬼!? 造成這個問題的原因是 Javascript 把第一個 {} 當作是 code block 並忽略它。這個結果等於 Javascript 只作 +{} 的運算。
上面有提過在 + 加號當作一元運算子的時候會嘗試把值轉成數字,下面就是等價執行過程:

+{}
Number({})
Number({}.valueOf()) // 依然不是 Primitive 所以要繼續轉型
Number({}.toString())
Number("[object Object]")
NaN

那為什麼第一個 {} 會被解析成程式片段而不是一個物件實字呢?因為 Javascript 將其解析為一個 statement 。因此如果要處理這個問題我們可以透過 () 強迫 JS 將其視為 expression

({} + {})
// [object Object][object Object]"
var o = {} + {}
o // "[object Object][object Object]"

其他還有一些技巧,如果您想知道更多細節請參考重讀 Axel 的 Javascript 中的 Expression vs Statement 一文

經過了上面的解釋,我想您就不會很驚訝下面這段程式的結果了

> {} + []
0
+[]
Number([])
Number([].valueOf()) // 依然不是 Primitive 所以要繼續轉型
Number([].toString())
Number("")
0

有趣的是 Node.js 的 REPL 解析輸入的方式和 Firefox, Chrome 等瀏覽器不同,它會將下面的輸入解析成 expression

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

這個結果就好像把 input 放到 console.log() 的參數內一樣。

總結

在大多數的情況下,並不難理解 Javascript 加號的運作,您只能相加數字或字串。物件會被轉換成數字或字串。當然會造成混亂的還有一元運算與二元運算之間的行為差異這點值得注意一下,然後遵循上面談論的規則,相信您應該就能參透 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

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/86662.html

相关文章

  • 深入 Javascript 執行環境

    摘要:中的執行環境與堆疊在這篇筆記中我將會深入的探討底層中的一些觀念,其中最重要的就是執行環境。其他執行環境都可以存取全域的東西。在這個階段直譯器會建立,透過掃描函式傳入的參數,內部的函式宣告,變數宣告。 Javascript 中的執行環境與堆疊 在這篇筆記中我將會深入的探討 JS 底層中的一些觀念,其中最重要的就是執行環境(Execution Context)。當您閱讀完這篇文章後您可能會...

    levius 评论0 收藏0
  • [譯] Houdini: 你還沒聽說!這可能是 CSS 下一件最令人興奮的大事

    摘要:接下來我們將會更具體的說明是什麼東西和這傢伙會怎麼解決這些問題,並且列出目前開發中一些令人興奮的功能。這個功能甚至還沒有一個瀏覽器支援。完整的清單請查閱目前還未被寫入規範,意思是這邊提到任何內容極有可能會改變。 譯者:其實...我想說這可能是最令我感到興奮..但又害怕頭痛的功能... 附上原文連結 你曾經想要使用某個 CSS 的新功能,但是最後卻因為這個功能瀏覽器還未全面支援而放棄了嗎...

    bergwhite 评论0 收藏0
  • 響應式設計中百分比 % 的問題

    摘要:響應式自適應響應式和自適應設計共同點都是要處理在不同裝置下瀏覽網頁的問題,可讀性,版型等等。關於由於我們仍會在的設計中使用百分比,如此一來或多或少還是會受到進位誤差的影響,因此並不是佈局的萬能藥。 showImg(https://segmentfault.com/img/remote/1460000006786975); 問題 為了要能夠解釋得更清楚我們需要實作一小段跟我們會遇到的問題...

    wangym 评论0 收藏0
  • [譯] 學習 CSS clip-path 屬性

    摘要:整體來說網頁主要是由矩形所構成的,另一方面印刷品則具備相對多樣性。即便我們設定的元素不再是矩形,但周圍的元素排列方式仍然維持原本矩形的佈局。為了達成周圍的元素跟著裁切的形狀,我們可以使用屬性。周圍的元素仍需要靠來修正。 整體來說網頁主要是由矩形所構成的,另一方面印刷品則具備相對多樣性。造成這樣差異的原因有很多,不過其中一個即是缺少合適的工具。 這篇文章主要會介紹 clip-path 這...

    yuanxin 评论0 收藏0
  • 【譯】Headless Chrome 入門指南

    摘要:確切位置因平台而異。如果以編程方式使用,這個頁面也是一個強大的調試工具,能看到所有原始的協議命令通過連線,於瀏覽器進行通信。警告協議可以做很多有趣的事,但作為入門選項他令人沮喪。目前,提供了比協議高級別的。 本文翻譯自:Getting Started with Headless Chrome原文更新時間:July 28,2017作者:Eric Bidelman(Engineer @ G...

    toddmark 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<