关于 Javascript {} + {}

这篇文章源自 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 那么执行转换的步骤以下

  1. 假如输入是原生型别,直接回传

  2. 否则调用 obj.valueOf() 假如值是原生型别就回传

  3. 还不是原生型别的话则调用 obj.toString() 结果假如是原生型别就回传

  4. 否则抛出破例

假如 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}
 */

参考

    原文作者:andyyu0920
    原文地址: https://segmentfault.com/a/1190000007929295
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞