文章目录
  1. 1. 强制转换
  2. 2. 自动转换
    1. 2.1. 自动转换为布尔值
    2. 2.2. 自动转换为字符串
    3. 2.3. 自动转换为数值
    4. 2.4. 小结
  3. 3. 加法运算符的类型转化
    1. 3.1. 三种抽象操作
      1. 3.1.1. 通过ToPrimitive()将值转换为原始值
      2. 3.1.2. 通过ToNumber()将值转换为数字
      3. 3.1.3. 通过ToString()将值转换为字符串
    2. 3.2. 三种情况
    3. 3.3. 四个特殊表达式

JavaScript是一种动态类型语言,变量是没有类型的,可以随时赋予任意值。但是,数据本身和各种运算是有类型的,因此运算时变量需要转换类型。大多数情况下,这种数据类型转换是自动的,但是有时也需要手动强制转换。

强制转换

强制转换主要指使用Number、String和Boolean三个构造函数,手动将各种类型的值,转换成数字、字符串或者布尔值。
详细了解看这里:好好学学Number!

自动转换

当遇到以下几种情况,JavaScript会自动转换数据类型:

  • 不同类型的数据进行互相运算;
  • 对非布尔值类型的数据求布尔值;
  • 对非数值类型的数据使用一元运算符(即“+”和“-”)。

自动转换为布尔值

当JavaScript遇到预期为布尔值的地方(比如if语句的条件部分),就会将非布尔值的参数自动转换为布尔值。它的转换规则与上面的“强制转换成布尔值”的规则相同,也就是说,在预期为布尔值的地方,系统内部会自动调用Boolean方法。

因此除了以下六个值,其他都是自动转为true:

  • undefined
  • null
  • -0
  • +0
  • NaN
  • ‘’(空字符串)

自动转换为字符串

当JavaScript遇到预期为字符串的地方,就会将非字符串的数据自动转为字符串,转换规则与“强制转换为字符串”相同。

字符串的自动转换,主要发生在加法运算时。当一个值为字符串,另一个值为非字符串,则后者转为字符串。

1
2
3
4
5
6
7
8
'5' + 1 // '51'
'5' + true // "5true"
'5' + false // "5false"
'5' + {} // "5[object Object]"
'5' + [] // "5"
'5' + function (){} // "5function (){}"
'5' + undefined // "5undefined"
'5' + null // "5null"

自动转换为数值

当JavaScript遇到预期为数值的地方,就会将参数值自动转换为数值,转换规则与“强制转换为数值”相同。

除了加法运算符有可能把运算子转为字符串,其他运算符都会把两侧的运算子自动转成数值。

1
2
3
4
5
6
7
8
'5' - '2' // 3
'5' * '2' // 10
true - 1 // 0
false - 1 // -1
'1' - 1 // 0
'5'*[] // 0
false/'5' // 0
'abc'-1 // NaN

上面都是二元算术运算符的例子,JavaScript的两个一元算术运算符——正号和负号——也会把运算子自动转为数值。

1
2
3
4
+'abc' // NaN
-'abc' // NaN
+true // 1
-false // 0

小结

由于自动转换有很大的不确定性,而且不易除错,建议在预期为布尔值、数值、字符串的地方,全部使用Boolean、Number和String方法进行显式转换。

加法运算符的类型转化

加法运算符(+)需要特别讨论,因为它可以完成两种运算(加法和字符连接),所以不仅涉及到数据类型的转换,还涉及到确定运算类型。

三种抽象操作

加法运算符会触发三种类型转换(不包括一元+): 将值转换为原始值,转换为数字,转换为字符串,这刚好对应了JavaScript引擎内部的三种抽象操作: ToPrimitive()ToNumber()ToString()

通过ToPrimitive()将值转换为原始值

JavaScript引擎内部的抽象操作ToPrimitive()有着这样的签名:

1
ToPrimitive(input, PreferredType?)

可选参数PreferredType可以是Number或者String,它只代表了一个转换的偏好,转换结果不一定必须是这个参数所指的类型,但转换结果一定是一个原始值。
如果PreferredType被标志为Number,则会进行下面的操作来转换输入的值:

  1. 如果输入的值已经是个原始值,则直接返回它。
  2. 否则,如果输入的值是一个对象。则调用该对象的valueOf()方法。如果valueOf()方法的返回值是一个原始值,则返回这个原始值。
  3. 否则,调用这个对象的toString()方法。如果toString()方法的返回值是一个原始值,则返回这个原始值。
  4. 否则,抛出TypeError异常。

如果PreferredType被标志为String,则转换操作的第二步和第三步的顺序会调换。
如果没有PreferredType这个参数,则PreferredType的值会按照这样的规则来自动设置:Date类型的对象会被设置为String,其它类型的值会被设置为Number。
如:

1
2
3
4
5
6
7
8
9
10
11
12
new Date() + 1 // "Sat Jun 11 2016 12:32:55 GMT+0800 1" 调用了 toString()

var a = {
valueOf:function(){
return 222

},
toString:function(){
return "aaa"

}};
"1" + a//"1222"

通过ToNumber()将值转换为数字

下面的表格解释了ToNumber()是如何将原始值转换成数字的。

参数 结果
undefined NaN
null +0
布尔值 true被转换为1,false转换为+0
数字 无需转换
字符串 由字符串解析为数字。例如,”324”被转换为324

如果输入的值是一个对象,则会首先会调用ToPrimitive(obj, Number)将该对象转换为原始值,然后在调用ToNumber()将这个原始值转换为数字。

通过ToString()将值转换为字符串

下面的表格解释了ToString()是如何将原始值转换成字符串的。

参数 结果
undefined “undefined”
null “null”
布尔值 “true” 或者 “false”
数字 数字作为字符串,比如。”1.765”
字符串 无需转换

如果输入的值是一个对象,则会首先会调用ToPrimitive(obj, String)将该对象转换为原始值,然后再调用ToString()将这个原始值转换为字符串。

三种情况

加法运算符的类型转换,可以分成三种情况讨论。

(1)运算子之中存在字符串

两个运算子之中,只要有一个是字符串,则另一个不管是什么类型,都会被自动转为字符串,然后执行字符串连接运算。前面的《自动转换为字符串》一节,已经举了很多例子。

(2)两个运算子都为数值或布尔值

这种情况下,执行加法运算,布尔值转为数值(true为1,false为0)。

1
2
3
true + 5 // 6

true + true // 2

(3)运算子之中存在对象

运算子之中存在对象(或者准确地说,存在非简单类型的值),则先调用该对象的valueOf方法。如果返回结果为简单类型的值,则运用上面两条规则;否则继续调用该对象的toString方法,对其返回值运用上面两条规则。

1
2
3
1 + [12]// "11,2"
var arr = [];
arr.valueOf() === arr //true

上面代码的运行顺序是,先调用[1,2].valueOf(),结果还是数组[1,2]本身,则继续调用[1,2].toString(),结果字符串“1,2”,所以最终结果为字符串“11,2”

1
1 + {a:1} // "1[object Object]"

对象{a:1}valueOf方法,返回的就是这个对象的本身,因此接着对它调用toString方法。({a:1}).toString()默认返回字符串"[object Object]",所以最终结果就是字符串“1[object Object]”

有趣的是,如果更换上面代码的运算次序,就会得到不同的值。

1
{a:1} + 1 // 1

原来此时,JavaScript引擎不将{a:1}视为对象,而是视为一个代码块,这个代码块没有返回值,所以被忽略。因此上面的代码,实际上等同于 {a:1};+1,所以最终结果就是1。为了避免这种情况,需要对{a:1}加上括号。

1
2
({a:1})+1 //"[object Object]1"
console.log({a:1}+1) // "[object Object]1"

{a:1}放置在括号之中,由于JavaScript引擎预期括号之中是一个值,所以不把它当作代码块处理,而是当作对象处理,所以最终结果为“[object Object]1”

1
1 + {valueOf:function(){return 2;}}// 3

上面代码的valueOf方法返回数值2,所以最终结果为3。

1
1 + {valueOf:function(){return {};}}// "1[object Object]"

上面代码的valueOf方法返回一个空对象,则继续调用toString方法,所以最终结果是“1[object Object]”

1
1 + {valueOf:function(){return {};},toString:function(){return 2;}}// 3

上面代码的toString方法返回数值2(不是字符串),则最终结果就是数值3。

1
1 + {valueOf:function(){return {};},toString:function(){return {};}}// TypeError: Cannot convert object to primitive value

上面代码的toString方法返回一个空对象,JavaScript就会报错,表示无法获得原始类型的值。

四个特殊表达式

有了上面这些例子,我们再进一步来看四个特殊表达式。

(1)空数组 + 空数组

1
[] + []// ""

首先,对空数组调用valueOf方法,返回的是数组本身;因此再对空数组调用toString方法,生成空字符串;所以,最终结果就是空字符串。

(2)空数组 + 空对象

1
[] + {}// "[object Object]"

这等同于空字符串与字符串“[object Object]”相加。因此,结果就是“[object Object]”

(3)空对象 + 空数组

1
{} + []// 0

JavaScript引擎将空对象视为一个空的代码块,加以忽略。因此,整个表达式就变成“+ []”,等于对空数组求正值,因此结果就是0。转化过程如下:

1
2
3
4
5
6
+ []

// Number([])
// Number([].toString())
// Number("")
// 0

如果JavaScript不把前面的空对象视为代码块,则结果为字符串“[object Object]”

1
({}) + []// "[object Object]"

(4)空对象 + 空对象

1
{} + {}// NaN

JavaScript同样将第一个空对象视为一个空代码块,整个表达式就变成“+ {}”。这时,后一个空对象的ValueOf方法得到本身,再调用toSting方法,得到字符串“[object Object]”,然后再将这个字符串转成数值,得到NaN。所以,最后的结果就是NaN。转化过程如下:

1
2
3
4
5
+ {}

// Number({})
// Number({}.toString())
// Number("[object Object]")

如果,第一个空对象不被JavaScript视为空代码块,就会得到“[object Object][object Object]”的结果。

1
2
3
4
5
6
7
8
9
({}) + {}// "[object Object][object Object]"

({} + {})// "[object Object][object Object]"

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

var a = {} + {};
a
// "[object Object][object Object]"

需要指出的是,对于第三和第四种情况,Node.js的运行结果不同于浏览器环境。

1
2
3
{} + {}// "[object Object][object Object]"

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

可以看到,Node.js没有把第一个空对象视为代码块。原因是Node.js的命令行环境,内部执行机制大概是下面的样子:

1
eval.call(this"(function(){return {} + {}}).call(this)")

Node.js把命令行输入都放在eval中执行,所以不会把起首的大括号理解为空代码块加以忽略。

参考链接:阮一峰

文章目录
  1. 1. 强制转换
  2. 2. 自动转换
    1. 2.1. 自动转换为布尔值
    2. 2.2. 自动转换为字符串
    3. 2.3. 自动转换为数值
    4. 2.4. 小结
  3. 3. 加法运算符的类型转化
    1. 3.1. 三种抽象操作
      1. 3.1.1. 通过ToPrimitive()将值转换为原始值
      2. 3.1.2. 通过ToNumber()将值转换为数字
      3. 3.1.3. 通过ToString()将值转换为字符串
    2. 3.2. 三种情况
    3. 3.3. 四个特殊表达式