JS 令人头疼的一点就是它属于弱类型语言,一个变量存储的值可以是字符串、数值、布尔值或者对象等,可以随时变更。获取一个变量,你不会知道其存储的值是什么类型的,所以很多时候都需要进行类型检测。

除了手动变更类型之外,有些情况下,JS 内部也会自动进行类型转换,以满足部分操作符以及语句等的执行。

下面就是我对 JS 类型转换的一些总结,其中大部分来自《JavaScript 高级程序设计》这本书。

1. Boolean 转换

转换情况

  • 调用 Boolean()

  • ifwhile 流程控制中,内部执行相应的 Boolean() 转换

  • 逻辑运算符,主要是 !!!,内部执行 Boolean() 转换

转换规则

数据类型 转换为 true 的值 转换为 false 的值
String 任何非空字符串 空字符串""
Number 任何非零数字值(包括无穷大) 0NaN
Object 任何对象 null
Undefined 不适用 undefined

也就是说,只有 0 / NaN / '' / null / undefined 才会转换为 false,其余都是 true

所以在 if 判断中,直接写变量有时候是不够严谨的

  1. if (!a){
  2. //=> 想要在 a 不存在或没定义的时候执行
  3. //=> 实际上存储的值是 0 或者 '' 等也会执行
  4. // 但是很多时候,a 都有约定的值类型,所以这种写法也经常使用
  5. }
  6. //=> 应该使用 typeof
  7. if (typeof a == 'undefined') {
  8. //=> a 不存在或没定义的时候执行
  9. }
  10. //=> 或者使用全等
  11. if (a === undefined) {
  12. //=> 使用 == 也不够严谨,因为 null == undefined
  13. //=> 需要区分 null 或 undefined 的 情况较少
  14. }

2. Number 转换

转换情况

  • 调用 Number()parseInt()parseFloat()

  • 其他情况都是使用 Number() 的转换机制

    • isNaN()

    • 一元加或减操作符,注意:这里是一元的,如 s = + s,区别于加法和拼接

    • 递增或递减,以及加法(除了拼接)、减法、乘法、除法、求模等操作符

    • 关系操作符

转换规则

Number()

  • Boolean 值

    • true => 1

    • false => 0

  • null => 0

  • undefined => NaN

  • String 值

    • 只包含数字(包含正负号),将其转换为十进制数值,忽略前导零

    • 包含有效的浮点格式,将其转换为对应的浮点数值,忽略前导零

    • 包含有效的十六进制格式,将其转换为相同大小的十进制数值

    • 空字符串 "",空格 ' ',换行符'\n',制表符'\t' => '0'

    • 包含其他格式的字符串,则将其转换为 NaN

  • Object 值

    • 调用对象的 valueOf() 方法,如果得到基本类型,则走前面的规则,否则走第二步
    • 调用对象的 toString() 方法,如果得到基本类型,则走前面的规则,否则报错
// [普通对象]
({}).toString() => '[object Object]' => NaN

// [数组]
[12,23].toString() => '12, 23' => NaN
[12].toString() => '12' => 12
[].toString() => '' => 0

// [正则]
/^$/.toString() => '/^$/' => NaN

// [函数]
function a() {}
a.toString() => "function a(){}" => NaN

parseInt()

  • 第一个参数必须是字符串,如果不是则转换为字符串,使用 toString() 方法转换为字符串

  • 忽略前面的空格,直至找到第一个非空字符

  • 第一个非空字符不是数字字符或者负号,则返回 NaN

  • 空字符串返回 NaN

  • 第一个是数字字符,会继续解析下一个字符,直至遇到非数字字符,返回前面的数字字符

  • 遇到非数字字符后,后面的字符都是无效的了

  • 传入第二个参数:转换时使用的基数,即多少进制,就可以解析二进制、八进制、十六进制的字符串。指定了第二个参数的,字符串中甚至不用带前面的前缀,如 parseInt("AF", 16);//175

  • 为了避免解析错误,任何情况下都应该明确指定基数,十进制也不例外

parseFloat()

  • 第一个参数必须是字符串,如果不是则转换为字符串,使用 String() 方法

  • 字符串中第一个小数点有效,第二个无效

  • 空字符串返回 NaN

  • 始终忽略前导零

  • 只要遇到非浮点字符或者第二个小数点,后面的字符串都无效

  • 字符串解析为整数,那么返回整数

3. String 转换

转换情况

  • 基于 alert / confirm / prompt / document.write 等方法输出内容

  • 调用 toString()String()

  • 加号操作符,有一个是字符串时,内部调用 toString()

  • 对象转换成 Number 类型时,内部调用 toString()

转换规则

toString() 方法

  • 返回相应值的字符串表现

  • 数值、布尔值、对象和字符串值都有一个 toString() 方法

  • 字符串的 toString() 方法返回字符串的一个副本

  • null => "null"

  • undefined => "undefined"

  • NaN => 'NaN'

  • true => 'true'false => 'false'

  • 普通对象只能返回 [object Object],不能返回字符串形式

  • 数组 [] => ''[12, 23] => '12,23'

  • 正则、日期等对象都返回其字符串表现 [object RegExp]、[object Date]

String() 转换函数,你可以认为 StringtoString 转换机制是一样的。

4. 特殊转换

+ 号操作的特殊情况

// 引用类型先调用 valueOf,没有得到基本类型的值
// 再调用 toString 会转换为字符串,这样就变成了字符串拼接
[12] + 10 //=> "1210"
({}) + 10 //=> '[object Object]10'

{} + 10 //=> 10
//=> 原因是 {} 会被解析为代码块,最后只是操作了 +10 

{} + {} //=> '[object Object][object Object]'
//=> 非常特殊,不同浏览器解析有差异

({})+{} //=> '[object Object][object Object]'
{} + ({}) //=> NaN
//=> 一行中开头的 {} 才会被解析为代码块,所以用 () 包裹后可以避免被解析为代码块
// 一行后面的 {} 不会被解析为代码块,而是空对象。
// 实际上是 + {} 会将其作为 Number() 转换来处理

== 进行比较时
对象 == 对象:地址相等才相等

{} == {} //=> false,注意此时 {} 不会被解析为代码块
{} == [] //=> 报错,此时 {} 被解析为代码块
[] == [] //=> false
{name: 'xxx'} == {name: 'xxx'} //=> false

var obj1 = {};
var obj2 = obj1;
obj1 == obj2 //=> true

对象 == 数字:把对象转换为数字
对象 == 布尔:都转化为数字
字符串 == 数字:把字符串转换为数字
字符串 == 布尔:都转化为数字
布尔 == 数字:把布尔转换为数字
对象 == 字符串:把对象转换为字符串之后再比较

1==true //=> true
1==false //=> false
2==true //=> false,都转化为数字

[]==false //=> true,都转换为数字
[]==true //=> false,都转换为数字
![]==false //=> true,先算 ![] 为 false,再比较
![]==true //=> false,先算 ![] 为 false,再比较
[]==![] //=> true,先算 ![] 为 false,然后都转换为数字再比较