前言
面试官问你:[] == ![] 相等吗?可能很多人就会问,这道题不是一目了然吗?空数组和非空数组肯定不相等啊。但是我明确的告诉你,这俩玩意它相等。
究竟是为什么呢?那么本文就带着这个疑问,一步一步的让你走出泥潭,让你彻底搞懂类型转换,以后碰到这类题你就偷着乐吧!
正文
我们都知道,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.隐式转换
- 数学运算符
let a = 5;
let b = "10";
console.log(a + b); // "510",字符串拼接
- 比较运算符
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官方文档,我们发现,对于引用类型转字符串:
这第一步中的ToPrimitive是个什么东西?
ToPrimitive
ToPrimitive 方法不是 JavaScript 中的一个单独的方法,而是一种抽象操作,用于将值转换为原始值(primitive value)。它在 JavaScript 内部广泛用于类型转换的过程中。
1.转为Number
ToPrimitive(obj , Number) ===> Number({})
这时ToPrimitive内部操作流程:
- 如果obj是基本类型,直接返回
- 否则,调用
valueOf方法,如果得到原始值则返回 - 否则,调用
toString方法, 如果得到原始值,则返回 - 否则,报错
2.转为字符串
ToPrimitive(obj , String) ===> String({})
这时ToPrimitive内部操作流程
- 如果obj是基本类型,直接返回
- 否则,调用
toString方法,如果得到原始值,则返回 - 否则,调用
valueOf方法,如果得到原始值则返回 - 否则,报错
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(因为引用地址不同)。那么对于一边是引用类型,一边是原始类型的情况,我们来看看官方文档怎么说的:
没错,不管原始类型是什么,都是先把引用类型通过ToPrimitive(object,Number)转换一下。
NaN == NaN //false
1 == {} //false
false == [] //true
以1 == {}为例,{}先转为字符串'[object object]',在通过Number()转为NaN,所以为false。
面试题
那么通过上面的学习,面试官再问你:
//相等吗?
[] == ![]
你可以很轻松的分析出来。通过运算符的优先级,先执行一元运算符!,那么我们知道!要先把操作符转为布尔类型,所以[]就是true,那么!true就是false。上述式子就变成了[] == false,所以最终答案是true。
最后
类型转换在JavaScript中非常常见,了解和掌握类型转换的概念对于编写正确且高效的代码至关重要。如果还有没懂的小伙伴可以参考一下:JS官方文档。
欢迎在评论区留言!