一文搞懂JS的类型转换

266 阅读6分钟

引言

面试当中我们常会碰到一些奇奇怪怪的判断语句,隐藏在其中的类型转换可能会惹得许多小白困扰不已,接下来就由小编来讲清楚js中神鬼莫测的类型转换,拨开大家心底的迷雾。

首先来一段代码开开胃,说明打印结果的原因:

console.log(1 == '1');  // true
console.log(1 === '1');  // false

let a = []
let b = []
console.log([] == []);  // false

先要聊一聊‘==’‘===’的区别:两个等号只判断两边变量的值是否相等,不在乎类型;三个等号要判断是否全等,值和类型都要相等。因为js引擎在用‘==’判断是否相等时会发生隐式类型转换,而‘===’判断时不会。

第6行打印false是因为js引擎在判断两边变量是否相等时只会读取栈里面的值,栈里变量a和b的值是堆中的两个不同的地址

显式类型转换

原始值转原始值

1. 原始值转布尔

  • 直接调用布尔的构造函数Boolean(),传入原始值作为参数
    • 原始值为数值类型,只有0NAN会被转换为false
    • 原始值为字符串类型,只有空字符串''会被转换为false
    • 原始值为undefinednull都会被转换为false

2. 原始值转数字

  • 直接调用Number(),传入原始值作为参数

    • 原始值为字符串类型,若所有字符都为数字,则会被转换成对应数值;若所有字符不都为数字,则转换为NaN;空字符串被转换为0
    • 原始值为布尔类型,true会被转换为1,false会被转换为0
    • 原始值为null 会被转换为0
    • 原始值为undefined 会被转换为NaN
  • 在字符串前放一个加号。这与调用Number构造函数效果相同是因为JS引擎在执行它们的时候都会自动调用一个ToNumber的函数,以此来将原始值转换为数值

console.log(+'123');  // 123

3. 原始值转字符串

  • 直接调用String(),传入原始值作为参数。传入的的所有的原始类型都会被直接在两边加上引号

原始值转对象

  • new XXX() 想要转成哪种对象就new它对应的包装器。比如数值123转成字符串对象:
  • 或者直接使用Object()构造函数
console.log(new String(123)); // [String: '123']

隐式类型转换

对象转原始值

对象转原始值我们通常只聊隐式的,因为日常开发中隐式转换的情况更常见,对比如 if判断语句中对象转布尔值:

let a = {}
if(a) {
    console.log('hello');
}

也可能是计算过程中对象与与原始值相加'1' + [1].....不论哪种,大多都不是开发者特地调用指定函数转换的,而是js引擎自动发生的。

转换方式:

  • 对象转布尔:任何对象转布尔都是true
  • 对象转字符串:js引擎自动调用ToString方法,ToString方法内部又会调用ToPrimitive方法将对象转成原始类型
  • 对象转数字:同理,js引擎自动调用ToNumber方法,ToNumber方法内部又会调用ToPrimitive方法将对象转成原始类型

ToPrimitive()

ToPrimitive() 用于把对象转成原始类型,这种方法不是给我们开发者调用的,而是发生隐式类型转换时只有js引擎自己能用。它接收两个参数,第一个是要转换的对象,第二个是这个对象通过 ToPrimitive() 最终想转换成的类型。

  • 若要转成String类型,ToPrimitive(obj, String)
  1. 如果obj接收到的是原始值,直接返回值
  2. 如果接收到的是对象,调用toString,若得到原始值,直接返回
  3. 否则,调用valueOf,若得到原始值,直接返回
  4. 如果valueOf()返回的不是原始值,报错
  • 若要转成Number类型,ToPrimitive(obj, Number)
  1. 如果obj接收到的是原始值,直接返回值
  2. 如果接收到的是对象,调用valueOf,若得到原始值,直接返回
  3. 否则调用toString,若得到原始值,直接返回
  4. 如果toString()返回的不是原始值,报错

ToPrimitive()转换至 String 类型或 Number 类型的过程区别就是调用 toString 方法和调用 valueOf 方法的先后顺序

toString()

toString方法有几个版本:

  1. {}.toString() 得到由"[object" 和 class 和 "]" 组成的字符串
  2. [].toString() 数组的toString方法重写了对象上的toString方法,返回由数组内部元素以逗号拼接的字符串
  3. xx.toString() 返回字符串字面量,比如
let fn = function(){}; 
console.log( fn.toString() ) //  "function () {}"
valueOf()

也可以把对象转成原始值,但是仅限包装类对象

运算符的隐式转换规则

一元运算符 +

前面提到原始值转成数值类型可以在字符串前添加一个加号,效果等同于Number(),即Number(str) == +str。同样,一个对象转换成数值也可以用一元运算符 +

  • 举例:+[] ===> 0
  1. js引擎自动调用ToNumber方法
  2. ToPrimitive([], Number)
    • [].valueOf(),不能得到原始值
    • [].toString(),得到空字符串'',是原始值,ToPrimitive()工作结束
  3. 空字符串转成0

二元操作符 +

v1 + v2 会发生的过程:

  1. lprim = ToPrimitive(v1)
  2. rprim = ToPrimitive(v2)
  3. 如果 lprim 和 rprim 其中有一个是字符串,那么另一个也要被转换为字符串,两者再拼接
  4. 如果没有一个是字符串,ToNumber(lprim) + ToNumber(rprim)

举几个二元操作符 + 发生隐式类型转换过程的例子:

  • 1 + '1' ===> '11'
  1. lprim = ToPrimitive(1) = 1,rprim = ToPrimitive('1') = '1'
  2. 1 + '1'
  3. ToString(1) + '1'
  4. '1' + '1' ===> '11'
  • null + 1
  1. lprim = ToPrimitive(null) = 0 , rprim = ToPrimitive(1) = 1
  2. 0 + 1 ===> 1
  • [] + {}
  1. lprim = ToPrimitive([]) = '' , rprim = ToPrimitive({}) = '[object Object]'
  2. '' + '[object Object]' ===> '[object Object]'

==

等号两边值相等即为true,不同类型的变量在判断过程中js引擎会进行隐式类型转换

转换过程中需要注意的几点: (参考官方文档[Annotated ES5])(es5.github.io/#x11.9.3)

  • null == undefined 为 true
  • 若一方为Number,另一方为String,往Number上转
  • 若有一方为布尔,把布尔类型转为 Number
  • 若一方为String 或 Number,另一方是对象,就把对象转成原始类型,再根据情况决定双方往 String 上转还是往 Number上转

举例: [] == ![]

  1. 取反优先级更高,[ ]转换成布尔得到true,再取反得到false
  2. false转换成数值为 0
  3. [ ]转换成数值为空字符串
  4. 空字符串转换成数值为0
  5. 0 == 0 ===> true

结语

显式类型转换规则还是比较好记的,面试中大多偏向对隐式类型转换过程的考察,通常发生在比较运算符、算术运算符旁,我们需要理解并记忆ToPrimitive被调用的过程以及toStringvalueOf方法的使用场景,牢记不同运算符的类型转换规则,每一步的转换都一定有对应的规则摆在那,只要符合规则,就一定能成功解答的。