鸡汤
我觉得这个世界上没有什么毫无道理的横空出世,如果没有大量的积累,大量的思考,是不会把事情做好的,人可以不上学,但一定要学习,这世上有太多的能人,你以为的极限,弄不好,只是别人的起点。所以只有不停的进取,才能做到不丢人。 —— 韩寒
序言
我们都知道js中的数据类型强制转换包括显示跟隐式,那在日常的开发过程中,尤其是在条件判断的时候,经常会因为数据类型的显示跟隐式转换而导致判断条件不生效,从而引起一些列奇奇怪怪的bug。可能工作过五六年的老兵,对这块也未必达到熟练于心。所以,无论你是职场新人or是职场老兵,在你我都是平凡人智商的情况下,想要挖掘自己的技术深度跟广度,成为行业专家,笔者认为是需要经过反复的刻意练习跟不断的复盘才能达到的。
先来看两道道常见的面试题
第一道
null == 0 // false
null > 0 // false
null < 0 // false
null >= 0 // true
null <= 0 // true
[undefined] == false // true
undefined == false // false
[] == ![] // true
[] == [] // false
{} == {} // false
{} == !{} // false
[] == 0 // true
[2] == 2 // true
['0'] == false // true
'0' == false // true
[] == false // true
[null] == 0 // true
上述80%的比较看上去不难吧?是不是有几个有点晕?没关系,那我们再来一道。
第二道
var a = ?;
if (a == 1 && a == 2 && a == 3) {
console.log('小样儿!');
}
问:当a等于什么的时候,if条件成立,并打印?
这道题,如果大家对隐式转换有一定的认识跟积累的话,可能很快就有思路知道怎么去解答,相反,如果你没有积累的话,面试官问到你,大脑瞬间一定会有很多小星星。这道题的答案我们在最后在揭晓。
再来回顾一下js的数据类型
关于第一道题中的[] == []、{} == {}、[] == ![]的结果,估计很多小伙伴都会产生疑问?所以,在揭穿这几个判断之前我们有必要再回顾一下js的基本数据类型以及存储。
js的基本数据类型:
- string
- boolean
- number
- null
- undefined
- symbol
还有一种较为复杂的数据类型:object
如果按照数据的存储方式来说的话,我们又可以把数据类型分为:值类型和引用类型。值类型是存储在栈内存中的,而引用类型的地址是存储在栈内存中的,比如对象名,函数名等,然后通过指针指向存储在堆内存中的数据。
一张图
// 基本数据类型 - 栈内存
let a1 = 123456;
// 基本数据类型 - 栈内存
let a2 = 'abcdef';
// 基本数据类型 - 栈内存
let a3 = true;
// 基本数据类型 - 栈内存
let a4 = null;
// 基本数据类型 - 栈内存
let a5 = undefined;
// 数组的指针存放在栈内存中,指针指向的数组存放在堆内存中
let c = [1, 2, 3];
// 对象的指针存放在栈内存中,指针指向的对象存放在堆内存中
let b = { a: 1 };
回顾完js类型的存储后我们再来看这道题
[] == []、{} == {}
在js进行类型转换时如果两边都是引用类型,则直接比较内存中的地址(也就是指针指向的地址)
console.log([]==[]) // false,指针指向的地址不同
console.log({} == {}) // false,指针指向的地址不同
js隐式类型转换的规则
ToString是将其他类型转换为字符串类型的操作
ToString
解释一下上述转换:
- null:转为"null"
- undefined:转为"undefined"
- 布尔类型:true和false分别被转为"true"和"false"
- 数字类型:转为数字的字符串形式,如10转为"10", 1e21转为"1e+21"
- 数组:转为字符串是将所有元素按照","连接起来,相当于调用数组的Array.prototype.join()方法,如[1, 2, 3]转为"1,2,3",空数组[]转为空字符串,数组中的null或undefined,会被当做空字符串处理
- 普通对象:转为字符串相当于直接使用Object.prototype.toString(),返回"[object Object]"
ToNumber
ToNumber是将其他类型转换为数字类型的操作
解释一下上述转换:
- null: 转为0
- undefined:转为NaN
- 字符串:如果是纯数字形式,则转为对应的数字,空字符转为0, 否则一律按转换失败处理,转为NaN
- 布尔型:true和false被转为1和0
- 数组:数组首先会被转为原始类型,也就是ToPrimitive,然后在根据转换后的原始类型按照上面的规则处理
- 对象:对象首先会被转为原始类型,也就是ToPrimitive,然后在根据转换后的原始类型按照上面的规则处理
ToBoolean
ToBoolean是将其他类型转换为布尔类型的操作
解释一下上述转换:
- null: 转为false
- undefined:转为false
- '':转为false
- NaN:转为false
- 0:转为false
- []: 转为true
- {}:转为true
- Infinity:转为true
总结:
js中的假值只有false、null、undefined、空字符、0和NaN,其它值转为布尔型都为true。
ToPrimitive
ToPrimitive指对象类型类型(如:对象、数组)转换为原始类型的操作。
两个非常重要的转换原则:
- 当对象类型需要被转为原始类型时,它会先查找对象的valueOf方法,如果valueOf方法返回原始类型的值,则ToPrimitive的结果就是这个值
- 如果valueOf不存在或者valueOf方法返回的不是原始类型的值,就会尝试调用对象的toString方法,也就是会遵循对象的ToString规则,然后使用toString的返回值作为ToPrimitive的结果。
解释一下上述转换:
- Number([]), 空数组会先调用valueOf,但返回的是数组本身,不是原始类型,所以会继续调用toString,得到空字符串,相当于Number(''),所以转换后的结果为"0"
- Number(['10'])相当于Number('10'),得到结果10
- obj1的valueOf方法返回原始类型100,所以ToPrimitive的结果为100
- obj2没有valueOf,但存在toString,并且返回一个原始类型,所以Number(obj2)结果为102
- obj3的toString方法返回的不是一个原始类型,无法ToPrimitive,所以会抛出错误。
一张图总结
宽松比较==隐式转换的区别
通常我们在开发的过程中建议使用全等来做数据类型的比较,防止宽松相等在隐式转换所带来的各种各样的问题。
布尔类型和其他类型的相等比较
- 只要布尔类型参与比较,该布尔类型的值首先会被转换为数字类型
- 根据布尔类型的ToNumber规则,true转为1,false转为0
false == 0 // true
true == 1 // true
true == 2 // false
const a = 10;
// a回被隐式转换为布尔类型true
if (a) {
console.log('执行回调');
}
数字类型和字符串类型的相等比较
- 当数字类型和字符串类型做相等比较时,字符串类型会被转换为数字类型
0 == '' // true
1 == '1' // true
对象类型和原始类型的相等比较
- 当对象类型和原始类型做相等比较时,对象类型会依照ToPrimitive规则转换为原始类型
[2] == 2 // true
- 数组[2]是对象类型,所以会进行ToPrimitive操作,也就是先调用valueOf再调用toString,根据数组ToString操作规则,会得到结果"2", 而字符串"2"再和数字2比较时,会先转为数字类型,所以最后得到的结果为true。
第一道题中特殊的一道比较分析:
[] == ![] // true
- !优先级比==高,先转右边,[]是对象类型,转成布尔值为true,!true就是false,即[] == false
- 右侧转成数字为0,即[] == 0
- 左侧是一个对象,valueOf()转出来不是字符也不是字符串,调用toString(),得到空字符串,即'' == 0
- 空字符串转成数字,即0 == 0 为true
我们再回头看第二道面试题分析:
var a = {
// 定义一个属性来做累加
i: 1,
valueOf () {
return this.i++
}
};
if (a == 1 && a == 2 && a == 3) {
console.log('小样儿!');
}
当然,如果我们没有定义valueOf方法的时候,也可以定义toString方法来达到上述题目的要求
var a = {
// 定义一个属性来做累加
i: 1,
toString () {
return this.i++
}
};
if (a == 1 && a == 2 && a == 3) {
console.log('小样儿!');
}
null、undefined、NaN跟原始类型作相等比较
- 当null、undefined、NaN跟原始类型作相等比较时,不会发生隐式转换,比较的结果直接为false
回顾一下第一道面试题的前几道相等比较:
null == 0 // false
null > 0 // false
null < 0 // false
NaN == NaN // false
undefined == null // true
null == null // true
null == undefined // true
总结
- 一个中心:左右两边转换成number为中心
- 两个基本点:转换条件:1.类型不同时才转换 2.两边都是引用类型时直接比较地址
- 一国两制:null、NaN、undefined使用一套制作,其它的使用另一套制度