详解JS的类型转换

273 阅读6分钟

前言

面试官问你:[] == ![] 相等吗?可能很多人就会问,这道题不是一目了然吗?空数组和非空数组肯定不相等啊。但是我明确的告诉你,这俩玩意它相等。

究竟是为什么呢?那么本文就带着这个疑问,一步一步的让你走出泥潭,让你彻底搞懂类型转换,以后碰到这类题你就偷着乐吧!

正文

我们都知道,JS中有很多的数据类型,一般可以分为原始数据类型和引用数据类型。我们现在不是来搞懂怎么判断它是什么类型的,而是弄清楚JS中数据的类型是如何转换的。如果你还对类型判断一知半解,可以看看我的这篇文章:(JS)数据类型大揭秘:深度解析几种最常用的方法

类型转换其实就是指将一个数据类型转换为另一个数据类型的过程,但是其实这个过程没有那么简单……

原始类型转化

对于原始数据类型的转换,还是比较容易的。

显式转换

let a = 123
let b = '123'
//转布尔值
console.log(Boolean(a)) //true
//转数字
console.log(Number(b)) //123
//转字符串
console.log(String(a)) //'123'
//转对象
console.log(Object(a)) //{123}
//或者
console.log(new Number(a)) //{123}

注意:

  • Boolean()方法只有非0数字和非空字符串转布尔值为ture,其它如undefined、null都为false。
  • Number()方法返回一个number类型的值而不是一个对象,如果没有传参,则返回0。且要注意一些特殊情况:
console.log(Number(undefined)) //NaN
console.log(Number(null)) //0
//字符串要看情况
console.log(Number('hello')) //NaN
console.log(Number('123')) //123
  • String()方法返回一个string类型的值而不是一个对象,如果没有传参,则返回''

显式类型转换是由开发人员手动执行的类型转换。JavaScript提供了上述内置函数来执行这种转换,除了上述方式,还有例如parseInt()parseFloat()等等,这里就不再过多赘述。

2.隐式转换

  1. 数学运算符
let a = 5;  
let b = "10"; 
console.log(a + b); // "510",字符串拼接
  1. 比较运算符
console.log("5" == 5); // 输出: true 
console.log("10" > 5); // 输出: true

隐式类型转换是指在运行时由JavaScript引擎自动执行的类型转换。这种转换通常发生在不同数据类型之间进行操作时。

引用类型转化

先讨论转number和string:

转number

对于引用类型转number类型,可以使用valueOf方法,但是这个方法仅对包装类管用,否则返回这个对象本身。

let obj1 = new Number('123')
let obj2 = {
    b: '123'
}
console.log(obj1.valueOf()); //123
console.log(obj2.valueOf()); // { b:'123' }

当然还有显式转换的方法:Number()

let a = {}
let b = []
let c = [1,2,3]
console.log(Number(a)); //NaN
console.log(Number(b)); //0
console.log(Number(c)); //NaN

转string

对于转字符串,可以用Object.prototype.toString()方法:

  • 对象转字符串: {}.toString() 返回由"[object" 和 class 和 "]" 组成的字符串。
  • 数组转字符串: [].toString() 返回由数组内部元素以逗号拼接的字符串。
  • 其他 :xx.toString() 直接返回字符串字面量。
  • 注意null和undefined身上没有这个方法!
//转字符串 : toString
// let a = {}
// let b = [1,2,3]
// // console.log(a.toString()); //'[object Object]'
// console.log(b.toString()); // '1,2,3'
// let time = new Date()
// console.log(time.toString()); 

那么对象的转换真的是如此吗?查阅JS官方文档,我们发现,对于引用类型转字符串:

屏幕截图 2024-03-12 135801.png

这第一步中的ToPrimitive是个什么东西?

ToPrimitive

ToPrimitive 方法不是 JavaScript 中的一个单独的方法,而是一种抽象操作,用于将值转换为原始值(primitive value)。它在 JavaScript 内部广泛用于类型转换的过程中。

1.转为Number

ToPrimitive(obj , Number) ===> Number({})

这时ToPrimitive内部操作流程:

  1. 如果obj是基本类型,直接返回
  2. 否则,调用 valueOf 方法,如果得到原始值则返回
  3. 否则,调用toString方法, 如果得到原始值,则返回
  4. 否则,报错

2.转为字符串

ToPrimitive(obj , String) ===> String({})

这时ToPrimitive内部操作流程

  1. 如果obj是基本类型,直接返回
  2. 否则,调用 toString方法,如果得到原始值,则返回
  3. 否则,调用 valueOf 方法,如果得到原始值则返回
  4. 否则,报错

3.转为布尔值

所有对象转布尔值都是true

注意:上述的操作规则均参考JS官方文档,有疑问可自行查看。

*对象的隐式类型转换

一元和二元运算符

对于一元运算符,以+为例:V8引擎会将操作数转换为数字类型。

+ '1' //Number(1)
+ []  // 0
+ {} //NaN
+ [1,2,3] //NaN

对于引用类型,很明显,要执行ToPrimitive(obj , Number)的操作规则,所以对于[],先调用valueOf方法,无法得到原始值,继续调用toString方法,返回一个空字符串''。再去对'',因为依旧不是数字,所以再去显式转化,Number(''),最终结果为0

对于二元运算符,依旧以+为例:

console.log(1 + '1'); //'11'
console.log([] + []); //空字符串
console.log([] + {}); //''+'[object Object]'
console.log({} + {}); //'[object Object][object Object]'

我们可以把这个过程看成:lprim + rprim == ToPrimitive(v1) + ToPrimitive(v2)

其中 lprim 和 rprim 都是ToPrimitive(obj , Number)的执行结果。主要的执行逻辑是:

  • 如果lprim 或 rprim是字符串(有一个即可),则按字符串进行拼接,不会转为number。
  • 否则,则转为number再计算。

[] + {}为例:lprim是空字符串,rprim是'[object object]',两者都是字符串,按字符串拼接,所以答案是'[object Object]'

注意在浏览器上{}+{}为NaN,因为会把上式看成 +{},所以外部要加括号({})+({})

==(等于等于)

先讨论原始类型,对于相同类型的值:

  • 对于null、undefined,如果类型相同则返回true。
  • 那么对于number、string、boolean是需要值相同,有一点特殊是number类型中的NaN,不管在==左边还是右边,均返回false。

对于不相同类型的值:

  • 如果是null == undefined 则返回true。
  • 那么对于其他的类型,只要==左右有不同于number类型的值,均要转为number类型再去判断。

讨论完原始类型,我们再来说说引用类型的规则,对于两边都是引用类型的情况就不要考虑了,都是false(因为引用地址不同)。那么对于一边是引用类型,一边是原始类型的情况,我们来看看官方文档怎么说的:

屏幕截图 2024-03-12 152850.png

没错,不管原始类型是什么,都是先把引用类型通过ToPrimitive(object,Number)转换一下。

NaN == NaN //false

1 == {} //false
false == [] //true

1 == {}为例,{}先转为字符串'[object object]',在通过Number()转为NaN,所以为false。

面试题

那么通过上面的学习,面试官再问你:

//相等吗?
[] == ![]

你可以很轻松的分析出来。通过运算符的优先级,先执行一元运算符,那么我们知道要先把操作符转为布尔类型,所以[]就是true,那么!true就是false。上述式子就变成了[] == false,所以最终答案是true

最后

类型转换在JavaScript中非常常见,了解和掌握类型转换的概念对于编写正确且高效的代码至关重要。如果还有没懂的小伙伴可以参考一下:JS官方文档

欢迎在评论区留言!