1.什么是JS的隐式转换
JavaScript中的隐式转换是指在运行过程中,代码自动将一个数据类型转换为另一个数据类型,而无需显式地调用转换函数。
原因
JavaScript是弱类型语言,这意味着它不像Java,C++一样的强类型语言有预先确定的类型。
并且变量的类型是由值的类型来决定的,这导致了一个问题,一个变量可能上一步骤操作中还是String,下一步操作可能立刻变为了Object,为了解决不同类型无法进行计算,JS底层会将不同类型转换为同一类型,由JS运行环境自动帮我们去做的类型转换
2.发生情况
JavaScript 中的隐式类型转换主要发生在 + - * /以及==、>、<
这些运算符之间。
当运算符两端的数据类型不一致,会触发隐式转换
例子
1 + '23' // '123'
1 + false // 1
1 + Symbol() // Uncaught TypeError: Cannot convert a Symbol value to a number
'1' + false // '1false'
false + true // 1true == 1 // true
true == '2' // false,
true == ['1'] // true
true == ['2'] // falseconsole.log("11" > 10) // true
3.隐式转换规则
首先要介绍ToPrimitive
方法,这是 JavaScript 中每个值隐含的自带的方法,用来将值 (无论是基本类型值还是对象)转换为基本类型值。如果值为基本类型,则直接返回值本身;如果值为对象,其看起来大概是这样:
ToPrimitive(obj,type)
type
的值为number
或者string
。
(1)当type
为number
时规则如下:obj
- 调用的valueOf方法,如果为原始值,则返回,否则下一步;
- 调用obj的toString方法,后续同上;
- 抛出TypeError异常。
(2)当type
为string
时规则如下:
- 调用obj的toString方法,如果为原始值,则返回,否则下一步;
- 调用obj的valueOf方法,后续同上;
- 抛出TypeError 异常。
可以看出两者的主要区别在于调用toString
和valueOf
的先后顺序。默认情况下:
- 如果对象为 Date 对象,则type默认为String;
- 其他情况下,type默认为number.
总结上面的规则,对于 Date 以外的对象,转换为基本类型的大概规则可以概括为一个函数:
var objToNumber = value => Number(value.valueOf().toString())
objToNumber([ ]) === 0
objToNumber({ }) === NaN
而 JavaScript 中的隐式类型转换主要发生在+、-、*、/
以及==、>、<
这些运算符之间。而这些运算符只能操作基本类型值,所以在进行这些运算前的第一步就是将两边的值用ToPrimitive
转换成基本类型,再进行操作。
以下是基本类型的值在不同操作符的情况下隐式转换的规则 (对于对象,其会被ToPrimitive
转换成基本类型,所以最终还是要应用基本类型转换规则):
+ 操作符 +
当一侧为 string 类型,被识别为字符串拼接,并会优先将另一侧转换为字符串类型。
当一侧为 Number 类型,另一侧为原始类型,则将原始类型转换为Number 类型。
当一侧为 Number 类型,另一侧为引用类型,将引用类型和 Number 类型转换成字符串后拼接。
1 + '23' // '123'
1 + false // 1
1 + Symbol() // Uncaught TypeError: Cannot convert a Symbol value to a number
'1' + false // '1false'
false + true // 1
123 + '123' // 123123
123 + null // 123
123 + true // 124
123 + {} // 123[object Object]
- * / 操作符 - * /
我们在对各种非Number
类型运用数学运算符(- * /
)时,会先将非Number
类型转换为Number
类型。
1 * '23' // 23
1 * false // 0
1 / 'aa' // NaN
1 - true // 0, 首先把 true 转换为数字 1, 然后执行 1 - 1
1 - null // 1, 首先把 null 转换为数字 0, 然后执行 1 - 0
1 * undefined // NaN, undefined 转换为数字是 NaN
2 * ['5'] // 10, ['5']首先会变成 '5', 然后再变成数字 5
== 操作符 ==
操作符两边的值都尽量转成number
:
3 == true // false, 3 转为number为3,true转为number为1
'0' == false //true, '0'转为number为0,false转为number为0
'0' == 0 // '0'转为number为0
NaN == NaN // false NaN和其他任何类型比较永远返回false
true == 1 // true
true == '2' // false, 先把 true 变成 1,而不是把 '2' 变成 true
true == ['1'] // true, 先把 true 变成 1, ['1']拆箱成 '1', 再参考规则3
true == ['2'] // false, 同上
undefined == false // false ,首先 false 变成 0,然后参考规则4
null == false // false,同上
123 == '123' // true, '123' 会先变成 123
'' == 0 // true, '' 会首先变成 0
null == undefined // true
null == '' // false
null == 0 // false
null == false // false
undefined == '' // false
undefined == 0 // false
'[object Object]' == {}
// true, 对象和字符串比较,对象通过 toString 得到一个基本类型值
'1,2,3' == [1, 2, 3]
// true, 同上 [1, 2, 3]通过 toString 得到一个基本类型值
> < 比较符 > <
如果两边都是字符串,则比较字母表顺序:
'ca' < 'bd' // false
'a' < 'b' // true
其他情况下,转换为数字再比较:
'12' < 13 // true
false > -1 // true
以上说的是基本类型的隐式转换,而对象会被ToPrimitive
转换为基本类型再进行转换:
var a = {}
a > 2 // false
a.valueOf() // {}, 上面提到过,ToPrimitive默认type为number
所以先valueOf,结果还是个对象,下一步
a.toString() // "[object Object]",现在是一个字符串了
Number(a.toString()) // NaN,根据上面 < 和 > 操作符的规则,要转换成数字
NaN > 2 //false,得出比较结果var a = {name:'Jack'}
var b = {age: 18}
a + b // "[object Object][object Object]"a.valueOf() // {},上面提到过,ToPrimitive默认type为number,所以先valueOf,结果还是个对象,下一步
a.toString() // "[object Object]"
b.valueOf() // 同理
b.toString() // "[object Object]"
a + b // "[object Object][object Object]"