由if引起的js类型转换的思考

今天偶然看到一篇文章,里面的一些小题目还是引起了我很大的兴趣,让本来佛系写博客的我,突然想认认真真的记录一下学习心得。下面从 if 作为切入口,一起来聊聊上述博客的一些题目吧。相信你看完本篇博客可以解决上述文章中的很多问题!

执行if 语句,如果是其他类型的条件,则会把其他类型的数据强制转换为Boolean类型,再进行判断;

// 相当于先执行Boolean(a)得到true或者false的返回值,再执行后续逻辑
if(a) {
  console.log("true");
} else {
  console.log("false");
}

如果上述条件 a 是以下几种情况,则 Boolean(a) 返回 false,其他情况返回 true:

  1. 无初始值;
  2. 0(无论是 +0 还是 -0 );
  3. null
  4. undefined
  5. "" (注意是空字符串,"false" 是会返回 true 的);
  6. 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)基础类型:StringNumberBooleanNullUndefinedSymbol (2) 复杂类型(引用类型): ObjectArrayFunction
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

  1. 根据[2.3]规则,首先把 true 转换成 Number,即为1,接下来比较[] == 1
  2. 根据[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()方法;基本包装类型有StringNumberBoolean;null和undefined不是,所以没有这两个方法;

继续之前的分析,把 [] 转换成 Number

  1. 匹配的是上述第【6】条规则,因为数组Array类型的valueOf()返回的是原数组,即[].valueOf() = []不匹配上述前五条规则,因此我们要调用toString() 转换返回的字符串
var a = [1];
console.log(a.valueOf()); //[1]
console.log(a === a.valueOf()); //true
  1. 数组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. 所以上述问题转化成了 "" == 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类型转换的规则有更深刻的理解,参考的博客也都在博文里有体现;而我只是总结了看到一篇文章里部分小题目的学习历程,有遇到更好的小例子也会继续整理的。

最后的最后,希望我的思考能带给你一点点的启发。创作不易,点个赞吧

  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值