当前位置: 首页 > Web前端 > HTML5

JavaScript深入头痛的类型转换(下)

时间:2023-04-05 23:35:26 HTML5

前言举个例子:JavaScript中的console.log(1+'1'),这完全没问题,但是你有没有想过为什么1和'1'属于不同的数据类型,为什么可以操作?这其实是因为JavaScript会自动转换数据类型,也就是我们通常所说的隐式类型转换。但是我们都知道+运算符既可以用于数字相加,也可以用于字符串拼接,那么在这个例子中,是不是将数字1转换为字符串'1'进行拼接呢?或者将字符串'1'转换成数字1进行加法?让我先告诉你一些事情,虽然我想你知道答案。今天,我们将介绍常见的隐式类型转换场景。一元运算符+console.log(+'1');当+运算符作为一元运算符使用时,见ES5规范1.4.6,会调用ToNumber对值进行处理,相当于Number('1'),最终结果返回数字1。那么呢以下结果?console.log(+[]);console.log(+['1']);console.log(+['1','2','3']);console.log(+{});由于调用了ToNumber方法,回想《JavaScript 深入之头疼的类型转换(上)》中的内容,当输入值为对象时,首先调用ToPrimitive(input,Number)方法,执行步骤为:若obj为基本类型,则返回直接地;否则,调用valueOf方法,如果返回原始值,则JavaScript返回它。否则,调用toString方法,如果返回原始值,则JavaScript返回它。否则,JavaScript会抛出TypeError异常。以+[]为例,[]调用valueOf方法返回空数组,因为不是原值,调用toString方法返回""。得到返回值后,再调用ToNumber方法。""对应的返回值为0,所以最后返回0。其余示例依此类推。结果是:console.log(+['1']);//1console.log(+['1','2','3']);//NaNconsole.log(+{});//NaN二元运算符+规范现在+运算符又变成了二元运算符,毕竟它也是加减乘除的加号1+'1'我们知道答案是'11',那么null+1、[]+[]、[]+{}、{}+{}?如果我们想要了解这些操作的结果,就不可避免地要从规范入手。规范地址:http://es5.github.io/#x11.6.1不过,这次我们不会直接引用规范中大段大段的内容,直接告诉大家精简后的内容。执行+操作时会经过哪些步骤?看一下规范11.6.1:计算value1+value2时:lprim=ToPrimitive(value1)rprim=ToPrimitive(value2)如果lprim是字符串或者rprim是字符串,则返回ToString(lprim)和ToString的拼接resultof(rprim)返回ToNumber(lprim)和ToNumber(rprim)的运算结果规范的内容就这样结束了。没有什么新鲜事。ToString、ToNumber、ToPrimitive在《JavaScript 深入之头疼的类型转换(上)》中都有提到,直接进入分析阶段:下面举几个例子:1.Null和数字console.log(null+1);按照标准步骤分析:lprim=ToPrimitive(null)因为null是基本类型,所以直接返回,所以lprim=nullrprim=ToPrimitive(1)因为1是基本类型,所以直接返回,所以rprim=null都是lprimandrprimNotastring返回ToNumber(null)和ToNumber(1)的运算结果下一个:ToNumber(null)的结果为0,(回想一下上一篇Number(null)),ToNumber(1)的结果为1所以,null+1相当于0+1,最后的结果就是数字1。这个还算简单,看起来复杂一点:2.Arraysandarraysconsole.log([]+[]);还是按照规范:lprim=ToPrimitive([]),[]是一个数组,相当于ToPrimitive([],Number),先调用valueOf方法,返回对象本身,因为不是原始值,调用toString方法,返回空字符串""rprim类似。lprim和rprim都是字符串,进行拼接操作。所以,[]+[]相当于“”+“”,最后的结果是一个空字符串“”。看一个比较复杂的:3.数组和对象//两者结果一致console.log([]+{});console.log({}+[]);根据规范:lprim=ToPrimitive([]),lprim=""rprim=ToPrimitive({}),相当于调用ToPrimitive({},Number),先调用valueOf方法,返回对象本身,因为不是原始值,调用toString方法,返回"[objectObject]"lprim和rprim都是字符串,进行拼接操作。因此,[]+{}等同于“”+“[objectObject]”,最终结果为“[objectObject]”。下面的例子中,可以根据例子类导出结果:console.log(1+true);console.log({}+{});console.log(newDate(2017,04,21)+1)//这个就知道是数字还是字符串类型了。结果是:console.log(1+true);//2console.log({}+{});//"[objectObject][objectObject]"console.log(newDate(2017,04,21)+1)//"SunMay21201700:00:00GMT+0800(CST)1"请注意以上操作都是在console.log中进行的,如果你直接在Chrome或者Firebug开发工具中直接输入命令行,你可能会惊讶地看到结果有些差异,例如:我们刚才说的{的结果}+[]is"[objectObject]"现在,它是如何变成0的?别着急,我们试着加个括号:结果又变成了正确的值,这是为什么呢?实际上,当没有括号时,{}被认为是一个独立的空代码块,所以{}+[]变成了+[],结果变成了0。同样的问题也出现在{}+{}中,并且Firefox和Google的结果不一样:>{}+{}//Firefox:NaN//Google:"[objectObject][objectObject]"如果把{}当做一个独立的代码块,那么这句话相当于+{},相当于Number({}),结果自然是NaN,但是Chrome这里返回的是正确的值。那么为什么这里返回的是正确的值呢?不知道,欢迎回答~==相等说明“==”用来比较两个值是否相等。当要比较的两个值是不同类型时,会发生类型转换。关于使用“=”进行比较,具体步骤参见Specification11.9.5:执行x==y时:如果x和y是同一类型:x为Undefined,返回truex为Null,返回truex是一个数字:x是NaN,返回falsey是NaN,返回falsex等于y,返回truex是+0,y是-0,返回truex是-0,y是+0,返回true返回falsex是一个字符串,完全相等返回true,否则返回falseex为布尔值,x和y均为true或false,返回true,否则返回falseex和y指向同一个对象,返回true,否则返回falseex为null且y未定义,returntrueexisundefinedandyisnull,如果x是数字返回true,y是字符串,判断x==ToNumber(y)x是字符串,y是数字,判断ToNumber(x)==yx是a布尔值,判断ToNumber(x)==yy是布尔值,判断x==ToNumber(y)x不是字符串或数字,y是对象,判断x==ToPrimitive(y)x是对象,y不是字符串也不是数字,判断ToPrimitive(x)==y返回false是不是觉得规范判断太复杂了?我们来看几种情况:1.nullandundefinedconsole.log(null==undefined);看规范的第二步和第三步:如果x为null且y为undefined,则返回true如果ex为undefined且y为null,则返回true所以例子的结果自然为true。这时候我们可以回忆起《JavaScript专题之类型判断(上)》看到的一个demo,就是在写一个判断对象类型的类型函数时,如果输入值为undefined,则返回字符串undefined,如果是null,则返回字符串null。如果是你,你会怎么写?以下是jQuery的写法:functiontype(obj){if(obj==null){returnobj+'';}...}2.字符串和数字console.log('1'==1);结果一定是真的。问题是字符串是转成数字跟数字比较还是数字转成字符串跟字符串比较?看规范的第4步和第5步:4.x是数字,y是字符串,判断x==ToNumber(y)5.x是字符串,y是数字,判断ToNumber(x)==y结果很明显,全部转换成数字再比较3.布尔值和其他类型console.log(true=='2')当要判断的一方为false时,往往是最容易出错的,比如上面的例子,直觉上应该是真的,毕竟Boolean('2')的结果是真的,但是这道题的结果是假的。说到底,还是要看规格。规范的第6步和第7步:6.x是布尔值,判断ToNumber(x)==y7.y是布尔值,当一侧出现布尔值时判断x==ToNumber(y)此时,ToNumber会对这边的值进行处理,也就是说,true会转为1,true=='2'相当于1=='2'相当于1==2,而结果自然是假的。因此,当其中一方为布尔值时,将转换布尔值。因为这个特点,xx==true和xx==false的写法尽量少用。例如://不推荐if(a==true){}//建议if(a){}//更好if(!!a){}4.对象和非对象console.log(42==['42'])看规范的第8步和第9步:x不是字符串也不是数字,y是对象,判断x==ToPrimitive(y)x是对象,y不是字符串也不是anumber,判断ToPrimitive(x)==y以本例为例,使用ToPrimitive处理['42'],调用valueOf,返回对象本身,再调用toString,返回'42',所以42==['42']等同于42=='42'等同于42==42,计算结果为真。至此,我们已经读完了第2、3、4、5、6、7、8、9步,其他的都将返回false。别人多举几个例子分析一下:console.log(false==undefined)false==undefined相当于0==undefined不满足以上情况,执行最后一步返回falseconsole.log(false==[])false==[]等同于0==[]等同于0==''等同于0==0,结果返回trueconsole.log([]==![])会先执行![]操作并转换为false,相当于[]==false相当于[]==0相当于''==0相当于0==0,结果返回true最后,一些会让人踩坑的例子:console.log(false=="0")console.log(false==0)console.log(false=="")console.log(""==0)console.log(""==[])console.log([]==0)console.log(""==[null])console.log(0=="\n")console.log([]==0)以上除了这两种情况都返回true另外,其实还有很多情况会发生隐式类型转换,比如if,?:,&&等,不过相对来说还是比较简单的,就不多解释了。JavaScript深入系列深入系列目录地址:https://github.com/mqyqingfen...JavaScript深入系列预计写15篇左右,旨在帮助大家打通JavaScript底层知识,重点关于原型、作用域、执行上下文、变量对象、this、闭包、按值传递、调用、应用、绑定、新建、继承等难点概念的解释。如有错误或不准确的地方,请务必指正,万分感谢。如果你喜欢或者有启发,欢迎star,这也是对作者的鼓励。