Skip to content

JS_[译] 在 JS 运行 {} {} 是什么结果

zen edited this page Jan 3, 2020 · 1 revision

[译] 在 JS 运行 {} + {} 是什么结果

目录

最近在写一个 react 项目的时候,不小心在 render 函数运行了 ‘’ + {}, 出现了奇怪的结果 [object Object], 成功的引起了我的注意。 在 JS 规范中加法运算的规则非常简单:你只能加数字和字符串,其他类型会被转成数字或者字符串。为了能清晰明白转化的过程是怎么样的,我们首先要知道几件事。无论何时当段落提到这个(例如 §9.1),这个都是引用 ES 5.1 标准规范来的。 让我们从一个菜鸟开始学习。在 JS 里面值有两种类型:原始值和对象。原始值有:undefined, null, booleans, numbers, and strings。其他都是对象,包括数组和函数。

Converting values 转换值

加法运算符有三种转换类型:他会转换成原始值,数字和字符串

通过 ToPrimitive() 把值转成原始值

ToPrimitive() 有下面的特征:

ToPrimitive(input, PreferredType?)

可选的形参 PreferredType 可以是数字或者字符串。他只表示一个预设值,结果可以是任何原始值。如果 PreferredType 是数字类型,那么转换步骤就是下面这样的:

  1. 如果输入的是原始值,原地返回输入值
  2. 否则输入的是对象,调用 obj.valueOf()。如果结果是原始值就原地返回。
  3. 否则调用 obj.toString()。如果结果是原始值就原地返回。
  4. 否则抛出错误

如果 PreferredType 是字符串。2 和 3 交换。如果 PreferredType 没有的话,如果是日期类型它会变成字符串,其他就会变成数字。

通过 ToNumber() 把值转成数字

下面的表格展示了 ToNumber() 是怎么将原始值转成数字类型的

参数 结果
undefined NaN
null +0
boolean value true 变成 1, false 变成 +0
number value --
string value 解析字符串变成数字,类似于JS方法里面的Number()。 For example, "324" is converted to 324

一个对象通过ToPrimitive(obj, Number) 被转成数字之后调用 ToNumber() 转成原始值

通过 ToString() 把值转成字符串

下面的表格展示了 ToString() 是怎么将原始值转成字符串类型的

参数 结果
undefined "undefined"
null "null"
boolean value "true" 或者 "false"
number value 直接变成字符串, e.g. "1.765"
string value --

对象通过ToPrimitive(obj,String) 被转成数字之后调用 ToString() 转成原始值

测试下

下面定义的这个对象可以让你观察转换的过程:

 var obj = {
 	valueOf: function () {
        console.log("valueOf");
        return {}; // not a primitive
    },
    toString: function () {
        console.log("toString");
        return {}; // not a primitive
    }
}

Number作为一个方法被调用的时候会主动触发 ToNumber 方法:

> Number(obj)
  valueOf
  toString
  TypeError: Cannot convert object to primitive value

Addition 加法

给出以下的加法表达式:

 value1 + value2

为了执行这个表达式,会执行以下这些步骤:

  1. 先把所有值转成原始值
    prim1 := ToPrimitive(value1)
    prim2 := ToPrimitive(value2)
    
  2. 省略了 PreferredType 参数,所有如果是日期就会是字符串,其他则是数字
  3. 如果有一个是字符串则将所有转换为字符串之后返回结果
  4. 否则全部转成数字之后返回结果

预料之中的结果

当你将两个数组相加,结果跟预想的一样

> [] + []
  ''

[] 转成原始值会先调用 valueOf(),之后它返回了 array 自己:

 > var arr = [];
 > arr.valueOf() === arr
   true

因为结果不是原始值,所有会调用 toString 方法之后返回空字符串,最后 [] + [] 的结果就是加两个空字符串。

将一个空数组和对象相加一样符合我们的预期:

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

解释:把一个空对象转换成字符串会是下面这个结果

> String({})
  '[object Object]'

所以前一个结果就相当于是 '' + '[object Object]'

更多

> 5 + new Number(7)
  12
> 6 + { valueOf: function () { return 2 } }
  8
> "abc" + { toString: function () { return "def" } }
  'abcdef'

出乎意料的结果

如果将两个空对象相加会得到奇怪的结果

> {} + {}
  NaN

为啥?这个问题出现的原因就是因为第一个 {} 会在JS 解释器里面被当成一个空的代码块所有会被忽略。这个 NaN 出现就是因为计算了 +{}。你在这里看到的加并不是加法 运算符,而是被当作一个转成数字类型的运算符,类似于调用了 Number()。例如

 > +"3.65"
   3.65

下面的这个表达式也和上面的例子类似

+{}
Number({})
Number({}.toString())  // {}.valueOf() 不是原始值
Number("[object Object]")
NaN

为啥第一个 {} 会被解释成一个代码块?那是因为这个输入会被解释为一个声明之后大括号在声明的开头的话就会被解释成代码块。好彩你能强制将这个输入变成一个表达式:

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

函数的参数也会自动把它变成表达式的

> console.log({} + {})
  [object Object][object Object]

在知道了前一个问题之后,你就不会对下面这个表达式有疑问了:

 > {} + []
   0

空对象再次被解释为一个代码块。这个表达式运算过程跟下面的一样

+[]
Number([])
Number([].toString())  // [].valueOf() 不是原始值
Number("")
0

有趣的是在Node.js 的 REPL 的解释器里面的结果跟浏览器的后台不一样,虽然他们同样使用的是 V8 JS 引擎。

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

参考

原文链接

Clone this wiki locally