前言

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 判断中,直接写变量有时候是不够严谨的。

if (!a){
//=> 想要在 a 不存在或没定义的时候执行
//=> 实际上存储的值是 0 或者 '' 等也会执行
}

//=> 应该使用 typeof
if (typeof a == 'undefined') {
//=> a 不存在或没定义的时候执行
}
//=> 或者使用全等
if (a === undefined) {
//=> 使用 == 也不够严谨,因为 null == undefined
//=> 需要区分 null 或 undefined 的 情况较少
}

在真实的项目中还是会经常用到第一种写法,因为其较为简单,而且能满足需求。因为默认情况下,判断的值只有两种情况:第一种是不存在或者未定义、第二种就是约定的值。很少会出现约定的值是转换为 false 的情况,但是使用的时候也要特别的注意。

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 值

    • 调用对象的 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'
  • 正则、日期等对象都返回其字符串表现

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

4. 特殊转换

+ 号操作的特殊情况

// 虽然没有看到字符串,但是引用类型转换为数字时,会先转换为字符串,这样就变成了字符串拼接
[12] + 10 //=> "1210"
({}) + 10 //=> '[object Object]10'

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

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

({})+{} //=> '[object Object][object Object]'
{} + ({}) //=> NaN
//=> 一行中开头的 {} 才会被解析为代码块,所以用 () 包裹后可以避免被解析为代码块,一行后面的 {} 不会被解析为代码块,而是空对象。

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

{} == {} //=> 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,然后都转换为数字再比较
生命的意义不仅是活着,而是我们给别人的生命带来了何种不同。