起因是在别人代码里看到了
+变量名
这样的写法,意思是
“string 转 number"
。查资料过程中复习到了类型转换的相关知识,整理一下。
一元运算符的 +
查询 MDN 可以知道,+
是可以作为一元运算符使用的。用在操作数前面,作用是将操作数转换为 number
,转换规则与 Number()
完全相同:(来自 MDN,对应 ECMA 262)
可以将字符串转换成整数和浮点数形式
也可以转换非字符值true
,false
和null
小数和十六进制格式字符串也可以转换成数值
负数形式字符串也可以转换成数值(对于十六进制不适用)
如果它不能解析一个值,则计算结果为NaN
二元运算符的 +
而作为我们熟知的二元 +
,规则其实非常简单:
- 数字和数字相加
- 字符串和字符串相加
所有其他类型的值都会被隐式转换为这两种类型的值。+
会触发的类型转换有三种:
- 转换为原始值
ToPrimitive()
- 转换为数字
ToNumber()
- 转换为字符串
ToString()
这三种都是 JavaScript 引擎内部的抽象操作,下面根据 ECMA 262 对这三种方法进行描述:
ToPrimitive
ToPrimitive ( input [ , PreferredType ] )
ToPrimitive
方法具有一个可选的 PreferredType
参数,可以传入转换时的“偏好”,具体而言,传入 Number
或 String
会分别对应下面的操作:
- 如果
input
是个原始值,直接返回- 否则,如果
input
是个对象
- 如果
PreferredType
是Number
,则按obj.valueOf()
->obj.toString()
的顺序调用- 如果
PreferredType
是String
,则按obj.toString()
->obj.valueOf()
的顺序调用- 途中遇到返回原始值就返回,如果一直到最后都没有返回原始值则抛出
TypeError
如果没有传入 PreferredType
时,Date
对象会设为 String
,其他对象都会设为 Number
。
ToNumber
Argument Type | Result |
---|---|
Undefined | NaN |
Null | +0 |
Booleam | true -> 1 ; false -> +0 |
Number | 无需转换 |
String | 试着由字符串解析为数字 |
Symbol / BigInt | TypeError |
Object | ToPrimitive(input, Number) |
这也正是 Number()
作为一个函数调用时引擎内部的操作。
ToString
Argument Type | Result |
---|---|
Undefined | "undefined" |
Null | "null" |
Booleam | true -> "true" ; false -> "false" |
Number | Number::toString(input) |
String | 无需转换 |
Symbol | TypeError |
BigInt | BigInt::toString(input) |
Object | ToPrimitive(input, String) |
可以通过自己定义对象里的 valueOf
和 toString
来观察引擎内部的操作,这里就不赘述。
二元 + 做了什么
对于操作 value1 + value2
,内部其实进行了如下操作:
- 试着进行
prim1 / 2 = ToPrimitive(value1 / value2)
,不传递PreferredType
进去采用默认操作- 如果上述转换结果
prim
中任意一个为字符串,则把另一个也转为字符串,然后返回两个字符串拼接的结果- 否则,将两个
prim
都转换为数字,返回它们的和
一些实例
[] + [] // -> ''
首先 ToPrimitive:array 的 valueOf 返回它本身;因此继续到 toString,返回空字符串
因此实际上是两个空字符串连接
[] + {} // -> '[object Object]`
因为空对象 toString 会得到 "[object Object]"
{} + {} // -> NaN
这个问题的原因是,js 把第一个大括号对解释成了一个空代码块并忽略了它,实际上执行的是 + {}
也就是 Number({})
关于为什么会解析为代码块可以参考 这个解释