JavaScript中的数据类型转换
|JavaScript中的数据类型转换是一个很容易造成困惑的地方。本文是《你不知道的JavaScript(中)》类型转换相关的知识整理,强烈建议去看这本书。
<!--more-->
首先我们需要了解JS中一些显式的类型转换
1. 显式类型转换
1.1. ToString
ToString
负责非字符串到字符串的强制类型转换
对于基础类型
null
转换为'null'
undefined
转换为'undefined'
true
转换为'true'
,false
转换为'false'
- 数字遵循通用规则转换,极大值或极小值使用指数形式
对于对象而言,
- 如果未覆盖
toString
,则返回[object Object]
- 如果覆盖了
toString
,则调用该方法并使用返回值 - 数组对象默认自定义了
toString
,会将所有单元字符串化之后再使用逗号,
连接起来- 这里的一个特例是null与undefined的元素会被字符串化为空字符串
''
而不是上面的普通类型转换规则
- 这里的一个特例是null与undefined的元素会被字符串化为空字符串
1.2. ToPrimitive
关于对象转原始类型ToPrimitive(input, PreferredType?)
,可以参考:
其中hint的取值可以为number
、string
或default
,表示当前语境传入的值(或者说是表示这是一个什么类型的运算),常见的运算中
var a = {
[Symbol.toPrimitive](hint) {
console.log(hint)
}
}
+a // hint为number
a + 1 // hint为default
// 下面情况hint为string
var o = {}
o[a] = 'xx' // string
alert(a) // string
parseInt(a) // string
ToPrimitive
的具体流程如下:
- 如果对象存在
Symbol.toPrimitive
,调用 objSymbol.toPrimitive,- 其中hint为当前语境传入的值(或者说是表示这是一个什么类型的运算),取
number
、string
或default
;
- 其中hint为当前语境传入的值(或者说是表示这是一个什么类型的运算),取
- 如果不存在
Symbol.toPrimitive
,且hint
取值是 "string":- 无论是否存在,先调用
obj.toString()
,如果返回了原始值,则使用该值;否则调用obj.valueOf()
。
- 无论是否存在,先调用
- 否则(也就是 hint 取值是 "number" 或 "default" 的情况):
- 无论是否存在,先调用
obj.valueOf()
,如果返回了原始值,则使用该值;否则调用obj.toString()
。
- 无论是否存在,先调用
需要注意
Symbol.toPrimitive
方法或最后调用的obj.toString
只能返回原始值,否则会抛出TypeError
- 我们创建的字面量对象
{}
和[]
不包含Symbol.toPrimitive
方法,且valueOf
方法返回的是自身,仍旧为引用值 - 但是
Date
对象实现了默认的Symbol.toPrimitive
方法,且返回的返回的是date.toString
的值
let user = {
name: "John",
money: 1000,
[Symbol.toPrimitive](hint) {
// 手动修改toPrimitive的值
return false
}
};
console.log(user == false) // true
// 先调用valueOf,返回[],需要再调用toString,返回'', 表达式变成'' == false,
// 然后将布尔值转换为数字, '' == 0,
// 字符串与数字比较时将字符串转换为数字,表达式变为 0 == 0,返回true
console.log([] == false)
总结一下,toPrimitive
将对象转换为基础值
- 首先检查该值是否有
valueOf
方法,如果有且该方法返回的是基础值,则使用该返回值进行强制类型转换- 注意数组的valueOf返回自身
- 如果没有,则使用
toString
的返回值进行强制类型转换 - 如果不具备
valueOf
和toString
则报TypeError
1.3. ToNumber
ToNumber
定义了将非数字转换为数字的规则
基本类型的转换
true
转换为1,false
转换为0undefined
转换为NaN
,null
转换为0''
、'\n'
、' '
等空白字符时会返回0,其余数字常量的字符串会转换为对应数字,转换失败则返回NaN
- '0'开头的十六进制数字字符串是按照十进制进行转换
对象首先会被ToPrimitive
转换成对应的基本值,如果返回的是非数字的基本类型,再应用上面的规则转换为数字
1.4. ToBoolean
JS中的值分为两类,
- 可以被强制转换为
false
的值,包括undefined
、null
、false
、+0
、-0
、NaN
和空字符串''
- 其他被强制转换为
true
的值- 注意对象(包括包装对象)永远为真值
2. 隐式类型转换
==
与===
的区别大家都比较了解,正确的解释是:“== 允许在相等比较中进行强制类型转换,而 === 不允许。”
如果两个值的类型相同,则仅比较他们是否相等,但是要注意特例
- NaN不等于NaN
+0
等于-0
- 引用类型判断的是内存地址是否相等,而不是具体值是否相等
如果两个值的类型不同,则会发生强制类型转换,会将其中之一或者二者转换成同一类型后再继续比较
在x == y
时
ES5 规范 11.9.3.4-5 这样定义:
规则1:二者有一个为数字,另一个为字符串,则将字符串转换成数字类型
- 如果 Type(x) 是数字,Type(y) 是字符串,则返回 x == ToNumber(y) 的结果
- 如果 Type(x) 是字符串,Type(y) 是数字,则返回 ToNumber(x) == y 的结果
// 返回100 == 100的结果,类型相同,比较值,返回true '100' == 100 // true
// 首先ToNumber('100x')返回NaN,然后 NaN == 100都为数字类型,比较值,返回false '100x' == 100 // false
规则2:二者有一个为布尔值,另一个为其他类型时,会先将布尔值转换成数字,然后再进行`==`比较
* 如果 Type(x) 是布尔类型,则返回 ToNumber(x) == y 的结果
* 如果 Type(y) 是布尔类型,则返回 x == ToNumber(y) 的结果
* `ToNumber(true)`的结果为1,`ToNumber(false)`的结果为false
```js
// 首先将a转换成数字1,此时计算 1 == '100',应用上面的规则1,将字符串转换成数字,返回 1 == 100的结果
true == '100' // false
// 基于上面的特点,永远不要使用下面的写法
if(a == true) {}
规则3:null与undefined之间的比较
- 如果 x 为 null,y 为 undefined,则结果为 true。
- 如果 x 为 undefined,y 为 null,则结果为 true。
- 除此之外其他任何值都与这两个值不相等
null == undefined // true undefined = true // true
null == true // false null == false // false null == '' // false null == 0 // false
undefined == true // false undefined == false // false undefined == '' // false undefined == 0 // false
规则4:对象与非对象之间的对比
* 如果 Type(x) 是字符串或数字,Type(y) 是对象,则返回 x == ToPrimitive(y) 的结果;
* 如果 Type(x) 是对象,Type(y) 是字符串或数字,则返回 ToPrimitive(x) == y 的结果。
* 如果基础值是布尔值,会先应用规则3,然后再进行上面的ToPrimitive操作
// 首先[42]调用ToPrimitive转换为基础值 42 == [42] // true
建议
* 如果两边的值中有 true 或者 false,千万不要使用 ==。
* 如果两边的值中有 []、"" 或者 0,尽量不要使用 ==。
还有一些常见的坑
```js
// []转换为'',而{}转换为[object Object],因此这里是字符串相加
[] + {}; // "[object Object]"
// 这里{}会被当做一个空的代码块,因此等价于 +[],会将[]转换为数字,
// 因此调用ToPrimitive返回空字符串, 然后将空字符串转ToNumber换为数字0
{} + []; // 0
3. 小结
JavaScript的数据类型转换是开发和面试中经常遇见的问题,稍有不慎就容易犯错,本文主要整理类型转换的一些规则,用以备忘。