今天偶然看到一篇文章,里面的一些小题目还是引起了我很大的兴趣,让本来佛系写博客的我,突然想认认真真的记录一下学习心得。下面从 if
作为切入口,一起来聊聊上述博客的一些题目吧。相信你看完本篇博客可以解决上述文章中的很多问题!
执行
if
语句,如果是其他类型的条件,则会把其他类型的数据强制转换为Boolean类型
,再进行判断;
// 相当于先执行Boolean(a)得到true或者false的返回值,再执行后续逻辑
if(a) {
console.log("true");
} else {
console.log("false");
}
如果上述条件 a
是以下几种情况,则 Boolean(a)
返回 false
,其他情况返回 true
:
- 无初始值;
0
(无论是+0
还是-0
);null
;undefined
;""
(注意是空字符串,"false"
是会返回true
的);NaN
;
如果条件只有一个这么简单那就好了,实际开发中我们往往会使用到
==
或===
以及多个条件来进行逻辑判断。
思考一下如下问题,为什么 if([])
为 true
(按照上面讲的规则,这里确实为true),但是if([] == true)
反而为 false
?
if([]) { // true
console.log("true");
} else {
console.log("false");
}
if([] == true) { // false
console.log("true");
} else {
console.log("false");
}
这里就需要介绍 ==
运算符的规则了;对于语句 x == y
,该运算符的处理如下:
先补充两个额外的知识:
JS数据类型有:(1)基础类型:String
,Number
,Boolean
,Null
,Undefined
,Symbol
(2) 复杂类型(引用类型):Object
,Array
,Function
TypeOf
操作符:对于null
和数组
返回的结果是object
;对于NaN
返回结果是number
【1】如果Type(x)和Type(y) 相同:
类型都为Undefined或Null,则返回true
;- 类型都为Number,x和y的数值相等(+0和-0都看成是0),则返回true,不等返回false;注意特例:
x和y有任一个为NaN,返回false
(因此可以通过判断一个值等不等于它自身来辨别出是不是NaN); - 类型都为String,当x和y是完全相同的字符序列(包括长度)才返回true,否则返回false;
- 类型都为Boolean,需要同时为false或者true,才返回true;
- 类型都为引用类型,需要
引用同一对象
的时候才返回true(怎么算引用同一对象这里不做展开,可以理解为浅拷贝);
【2】如果Type(x)和Type(y) 不相同
- [2.1] x和y分别为null和undefined(互换也可,注意这里是指x和y的
值而非类型
),返回true; - [2.2] Type(x)和Type(y)一个为Number,另一个为String,则把
String转化成Number
再进行比较;(转化成Number的规则后面补充) - [2.3] Type(x)和Type(y)有一个为Boolean,则把
Boolean转化成Number
再进行比较; - [2.4] Type(x)和Type(y)一个为String或者Number,另一个为Object,则把
Object转化成String或者Number
再进行比较;(这里更为准确的说法是调用toPrimitive
函数转化成原始值
,更多细节可以参考该文章); - 否则,返回false
简单讲一下
toPrimitive()
:
(1)如果是原始值则正常返回;
(2)否则依次调用valueOf()
和toString()
直到返回一个原始值;
(3)如果都没有则抛出错误;(这里的解释不太准确,作为一个大致理解是没问题的,更详细的内容参考我上面[2.4]给出的文章);
原始值
是:undefined,null,boolean,number,string;除原始值外,其他所有值都是对象类型的值;
规则讲了这么多,看得也会比较累,那我们试着利用上述规则解释需要大家思考的那个问题?即为什么 [] == true
会返回 false
- 根据[2.3]规则,首先把
true
转换成Number
,即为1,接下来比较[] == 1
; - 根据[2.4]规则,需要把
[]
转换成Number
;
到这我们就卡住了,别急,下面补充一下转化成Number的规则
【1】Boolean类型,true为1,false为0;
【2】Number类型,返回本身;
【3】null值,返回0;
【4】undefined值,返回NaN;
【5】String类型,只包含数字,会转成十进制数(第一个字符可以是正负号,前导0会被忽略,也可以带小数点);开头是"0x"(表示十六进制),则会转换成相同大小的十进制;空字符会转换成0;包含除上述格式外的其他字符,会转换成NaN(这里只是简单的说明一下常用的情况,更多细节可以自行百度)
【6】如果是引用类型,则调用valueOf()方法
,然后根据上述规则转换返回的值,若转换结果为NaN(或不匹配前五条规则),则调用toString()方法
,然后根据前面的规则转换返回的字符串值。
这里贴两个讲解valueOf()和toString()的博客;
所有对象(包括
基本包装类型
)都拥有valueOf()
和toString()
方法;基本包装类型有String
,Number
,Boolean
;null和undefined不是,所以没有这两个方法;
继续之前的分析,把 []
转换成 Number
;
- 匹配的是上述第【6】条规则,因为数组Array类型的valueOf()返回的是原数组,即
[].valueOf() = []
不匹配上述前五条规则,因此我们要调用toString()转换返回的字符串
。
var a = [1];
console.log(a.valueOf()); //[1]
console.log(a === a.valueOf()); //true
- 数组Array类型的toString()方法会返回
数组内容组成的字符串
,空数组就会返回空字符串
,多维数组会先进行扁平化处理在输出;这里注意对象Object类型及自定义对象类型(返回[object Object])
var arr = [1,[1,2,,2],[[[]],1]];
console.log(arr.toString()); //"1,1,2,,2,,1"
// 对象Object类型
var obj = {a:1};
console.log(obj.toString()); //"[object Object]"
function Foo(){};
// 自定义对象类型
var foo = new Foo();
console.log(foo.toString()); //"[object Object]"
- 所以上述问题转化成了
"" == 1
而根据Number转换规则【5】,就变成了0 == 1
,结果为false
,至此已经解释完了if([] == true)
为false
的原因了。
注:有上可知,==
真的很麻烦,强烈建议开发中使用 ===
替换 ==
,养成良好习惯从你我做起。(PS: 那我为什么还要讲呢?一方面是应付面试
的时候用==刁难你,另一方面是在探究背后过程中掌握js类型隐式转换
的原理)
好了,下面思考几个问题,并解释一下最开始那篇博客里的几个题目
问题一:{} == false
返回什么?{} == true
又返回什么?
两个都是false
,因为对象Object类型及自定义对象类型(返回[object Object])
而字符串"[object Object]"
转化成数字时,会变成NaN
问题二:!!"false" == !!"true"
输出什么?
这里加入了非操作符,非操作符即将true
置为false
,将false
置为true
。首先判断Boolean("false")
的真假,根据最上面的Boolean规则,可知返回true,两个!!
等于没有,最终左边等于true
,右边同理也为true
;最终结果为true
问题三:"b" + "a" + +"a" + "a"
输出是什么?
这里加入了+号操作符,推荐讲解该操作符的一篇博客;里面有几个关于+的题目
很有趣,例如猜猜看在浏览器控制台分别输入[] + []
,{} + {}
,[] + {}
,{} + []
,({} + [])
对应的输出什么??
(答案分别为""
,[object Object][object Object]
,[object Object]
,0
,[object Object][object Object]
)注意在不同的引擎下,{} + {}
解析结果会比较不同,原因是解析器可能把{}
解析成空语句(可以在外层包一个圆括号防止这种意料之外的解析),从而变成了+{}
,+{}
在运算中相当于NaN
,具体原因看下文:
回归正题,简单概括一下+
规则:(1)先将两个值转换成原始值(即调用上面说的toPrimitive
函数)(2)有任一个为字符,则把另外一个也变成字符,然后拼接,(3)否则都转成数字,然后想加;
在这个题目里,其实还考察了作为一元运算符的+
,+a
的意思就是利用隐形转换让a变成Number类型,如+[]
等同于Number([])
等同于Number([].toString())
等同于Number("")
等同于0
;
所以根据上述所讲,题目等同于"ba" + (+"a") + "a"
等同于"ba" + (Number("a")) + "a"
等同于"ba" + NaN + "a"
,最终结果为baNaNa
问题四:下面来一个比较复杂的,可以看看,如下代码输出什么?
var a = (![] + [])[+[]] +
(![] + [])[+!+[]] +
([![]] + [][[]])[+!+[] + [+[]]] +
(![] + [])[!+[] + !+[]];
console.log(a)
这道题基本上结合了上面讲的很多内容,一个个拆分。下面写法有点不正规,但大概意思应该能看懂
![] + []
等同于!Boolean([]) + []
等同于!true + []
等同于false + []
false已经是原始值了;继续把后面变成原始值,等同于false + [].toString()
这里解释一下,通过ToPrimitive()
将值转换为原始值,因为[].valueOf()
返回的不是原始值,所以调用toString()
(为了方便把这步省略了);等同于false + ""
,有一个为字符则需要把另一个也变成字符在拼接,最终结果为"false"
(这里讲这么详细是因为,很多人特别容易把上面变成false + 0
,导致最终结果为0)一定要熟记问题三里概括的加法三步走的战略
,先把加号两边的数都变成原始值了,再判断是否要进行Number处理;(![] + [])[+[]]
中+[]
上文讲过了,等于0
,所以上述式子等同于取字符串"false"
第0个字符,即"f"
(![] + [])[+!+[]]
中[+!+[]]
等同于Number(!+[])
,而+[]
等于0,!+[]
等于1,则Number(!+[])
等于1,上述式子等同于取字符串"false"
第个字符,即"a"
([![]] + [][[]])[+!+[] + [+[]]]
这个有点复杂,一步步拆分,前半部分([![]] + [][[]])
等同于[false] + [][[]]
后面有点故弄玄虚,可以理解为取空数组[]
的第[]
元素,第[]
元素不就是Number([])
这个上面也讲过了,等于0;空数组的第0个元素等于什么,当然是undefined
;[false] + undefined
还记得上面加法三步走的规则么,再来一遍,把[false]
转换成原始值,调用toString(),得到字符串"false"
; undefined已经是原始值了,不变。再根据规则,有一个是字符,则另一个也要转成字符,即String(undefined)
等于"undefined"
(注意undefined是没有toString()方法的) 拼起来得到字符串"falseundefined"
;后半部分[+!+[] + [+[]]]
前面这块+!+[]
上面也讲了等于1,后面这块[+[]]
等于[0]
,然后计算1+[0]
这里不展开了,等于字符串"10"
;所以上述式子等同于取字符串"falseundefined"
第10个字符,即"i"
(![] + [])[!+[] + !+[]]
前面是"false"
,后面!+[] + !+[]
等于1+1=2,即上述式子等同于取字符串"false"
第2个字符,即"l"
- 最终结果为字符串
fail
本文只是记录博主在工作中碰到了一点小问题,从而引发的思考历程;毕竟也只有工作一年的经历,阅历浅薄,所有肯定会有很多问题或理解不到位的地方,欢迎大家指正发言;最后我想说,其实开发中大多数情况下肯定不会写题目这种"花里胡哨"的代码,我整理出来,只是想带动大家在思考这些问题的过程中对js类型转换的规则有
更深刻的理解,参考的博客也都在博文里有体现;而我只是总结了看到一篇文章里部分小题目的学习历程,有遇到更好的小例子也会继续整理的。
最后的最后,希望我的思考能带给你一点点的启发。创作不易,点个赞吧