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

令人头秃的js隐式转换面试题,你能做对吗_2

时间:2023-03-27 17:08:25 JavaScript

令人头疼的js隐式转换面试题,你做对了吗?很难自信,js是怎么计算得出结果的,你对它的原理有深刻的理解吗?下面将深入讲解其实现原理。其实这篇文章的初稿是三个月前写的。之前在看一些源码库的时候,遇到了这些基础知识,想归档整理一下,于是翻到了这篇文章。由于最近比较忙,没时间整理,所以最近看到这个热门话题,决定整理一下这篇文章。consta={i:1,toString:function(){returna.i++;}}if(a==1&&a==2&&a==3){console.log('helloworld!');网上有很多不错的分析流程。看完下面的内容,你会对其执行过程有更深入的了解。1.js数据类型js中有7种数据类型,分为两大类:原始类型、对象类型:基本类型(原始值):Undefined、Null、String、Number、Boolean、Symbol(es6新发布的,本文不讨论这种类型)复杂类型(对象值):object2,三种隐式转换js的难点之一就是js的隐式转换,因为js在某些操作符下会对其类型做一些改变,所以js是灵活,同时导致容易出错且难以理解。涉及最多隐式转换的两个运算符+和==。+运算符可以添加数字或字符串。所以转换起来很麻烦。==和===不同,所以也有隐式转换。-*/这些运算符只针对数字类型,所以转换结果只能转换为数字类型。既然有隐式转换,那怎么转换呢?应该有一套转换规则来跟踪最终转换的内容。隐式转换主要涉及三个转换:1.将值转换为原始值,ToPrimitive()。2.将值转换为数字,ToNumber()。3.将值转换为字符串,ToString()。2.1.通过ToPrimitive将值转换为原始值。js引擎内部的抽象操作ToPrimitive有这样一个签名:ToPrimitive(input,PreferredType?)input是要转换的值,PreferredType是可选参数,可以是Number或String类型。它只是一个转换标志,转换后的结果不一定是这个参数值的类型,但转换后的结果必须是原始值(否则会报错)。2.1.1.如果PreferredType被标记为Number,则将执行以下操作过程以转换输入值。1.如果输入值已经是一个原始值,直接返回2.否则,如果输入值是一个对象,调用对象的valueOf()方法,如果valueOf()方法的返回值是一个原始值value,返回原始值。3.否则,调用该对象的toString()方法。如果toString()方法返回原始值,则返回原始值。4.否则抛出TypeError异常。2.1.2.如果PreferredType被标记为String,则将执行以下操作过程以转换输入值。1、如果输入的值已经是原值,则直接返回。2.否则,调用该对象的toString()方法。如果toString()方法返回原始值,则返回原始值。3.否则,如果输入值是对象,则调用对象的valueOf()方法,如果valueOf()方法的返回值是原值,则返回原值。4.否则抛出TypeError异常。既然PreferredType是一个可选参数,没有这个参数怎么转换呢?PreferredType的值会按照以下规则自动设置:1.如果对象是Date类型,PreferredType设置为String2,否则PreferredType设置为Number2.1.3,valueOf方法和toString方法解析上面主要提到了valueOf方法和toString方法,这两个方法是否一定存在于对象中?答案是肯定的。在控制台输出Object.prototype,会发现有valueOf和toString方法,Object.prototype是所有对象原型链的最顶层原型。所有对象都会继承这个原型的方法,所以任何对象都会有valueOf和toString方法。先看对象的valueOf函数,转换结果是什么?js常用内置对象:Date、Array、Math、Number、Boolean、String、Array、RegExp、Function。1、Number、Boolean、String这三个构造函数生成的基础值的对象形式,经过valueOf转换后成为对应的原始值。例如:参考前端高级面试题的详细答案varnum=newNumber('123');num.valueOf();//123varstr=newString('12df');str.valueOf();//'12df'varbool=newBoolean('fd');bool.valueOf();//true2,Date是一个特殊的对象,原型Date.prototype上内置的valueOf函数将日期转换为日期的毫秒数形式的值。vara=newDate();a.valueOf();//15151438955003,其他返回的都是this,也就是对象本身:(有问题请留言)vara=newArray();a.valueOf()===a;//truevarb=newObject({});b.valueOf()===b;//true我们再看看toString函数,转换结果是什么?js常用内置对象:Date、Array、Math、Number、Boolean、String、Array、RegExp、Function。1、Number、Boolean、String、Array、Date、RegExp、Function等构造函数生成的对象经过toString转换后会变成对应的字符串,因为这些构造函数封装了自己的toString方法。如:Number.prototype.hasOwnProperty('toString');//trueBoolean.prototype.hasOwnProperty('toString');//trueString.prototype.hasOwnProperty('toString');//trueArray.prototype.hasOwnProperty('toString');//trueDate.prototype.hasOwnProperty('toString');//trueRegExp.prototype.hasOwnProperty('toString');//trueFunction.prototype.hasOwnProperty('toString');//truevarnum=newNumber('123sd');num.toString();//'NaN'varstr=newString('12df');str.toString();//'12df'varbool=newBoolean('fd');bool.toString();//'真'vararr=newArray(1,2);arr.toString();//'1,2'vard=newDate();d.toString();//"2017年10月11日星期三08:00:00GMT+0800(中国标准时间)"varfunc=function(){}func.toString();//"function(){}"除了这些对象和它们的实例化对象之外,其他的对象返回对象的类型,(如果有问题请告诉我),它们都是继承自Object.prototype.toString方法。varobj=new对象({});obj.toString();//"[object对象]"Math.toString();//"[objectMath]"从上面valueOf和toString两个函数转换对象可以看出为什么对于ToPrimitive(input,PreferredType?),在没有设置PreferredType的情况下,除了Date类型,都设置了PreferredType为String,其他设置为Number。因为valueOf函数会将Number、String、Boolean基本类型的对象类型值转换为基本类型,Date类型会转换为毫秒,其他的会返回对象本身,而toString方法会将所有对象转换为字符串。显然,对于大多数对象的转换,valueOf转换是比较合理的,因为不指定转换类型,应该尽可能保留原始值,而不是像toString方法那样将其转换为字符串。因此,当不指定PreferredType类型时,最好先转换valueOf方法,将PreferredType设置为Number类型。对于Date类型,将valueOf转换为毫秒数类型。在进行隐式转换时,当没有指定将其转换为number类型时,转换为这么大的number类型值显然是没有意义的。(无论是在+运算符还是==运算符)都不如转换成字符串格式的日期,所以默认的Date类型会优先进行toString转换。所以就有了上面的规则:当不设置PreferredType时,对于Date类型的对象,PreferredType默认设置为String,对于其他类型的对象,PreferredType默认设置为Number。2.2.使用ToNumber将值转换为数字。根据参数类型,进行如下转换:参数结果undefinedNaNnull+0布尔值true转换1,false转换为+0数字,不转换字符串。字符串解析为数字,例如:'324'转换对于324,'qwer'先转换为NaN对象(obj),通过ToPrimitive(obj,Number)转换得到原始值,再将ToNumber转换为数字2.3、通过ToString将值转为字符串,根据参数类型进行如下转换:parameter结果undefined'undefined'null'null'boolean值转换为'true'或'false'数字转换字符串,例如:1.765转换为'1.765'字符串,无需先将对象(obj)转换为ToPrimitive(obj,String)转换得到原始值。关于将ToString转换为字符串,我已经讲了很多。还不是很清楚吗?让我们看一个例子:({}+{})=?两个对象的值都是+运算符,肯定需要隐式转换为原始类型才能进行计算。1、进行ToPrimitive转换。由于没有指定PreferredType类型,{}会取默认值Number,执行ToPrimitive(input,Number)操作。2、因此会执行valueOf方法,({}).valueOf(),返回的对象还是{}对象,不是原来的值。3、继续执行toString方法,({}).toString(),返回“[objectObject]”,也就是原来的值。因此,最终得到的结果是,“[objectObject]”+“[objectObject]”=“[objectObject][objectObject]”另一个指定类型的例子:2*{}=?1。首先,*运算符只能对数字类型进行操作,所以第一步是对{}进行ToNumber类型转换。2、由于{}是对象类型,所以先进行原始类型转换,再进行ToPrimitive(input,Number)操作。3、因此会执行valueOf方法,({}).valueOf(),返回的对象还是{}对象,不是原来的值。4、继续执行toString方法,({}).toString(),返回“[objectObject]”,也就是原来的值。5.转为原值再执行ToNumber操作后,“[objectObject]”会转为NaN。所以,最终的结果是2*NaN=NaN3,==运算符隐式转换了==运算符,==运算符的正则性没有那么强。按照以下流程进行es5文档比较操作x==y,其中x和y为值,返回true或false。这样的比较执行如下:1.如果Type(x)与Type(y)相同,则1*如果Type(x)未定义,则返回true。2*如果Type(x)为Null,则返回true。3*如果Type(x)是Number,则(1),如果x是NaN,返回false。(2).如果y为NaN,则返回false。(3)如果x和y的值相等,则返回true。(4).如果x为+0且y为?0,则返回true。(5).如果x为?0且y为+0,则返回true。(6)、返回假。4*如果Type(x)是String,如果x和y是相同的字符序列(相同长度和相同位置的相同字符),则返回true。否则,返回假。5*如果Type(x)是布尔值,当x和y都为真或都为假时返回真。否则,返回假。6*当x和y指代同一个对象时返回真。否则,返回假。2.如果x为null且y未定义,则返回true。3.如果x未定义且y为空,则返回true。4.如果Type(x)为Number,Type(y)为String,则返回x==ToNumber(y)的比较结果。5、如果Type(x)为String,Type(y)为Number,则返回ToNumber(x)==y的比较结果。6.如果Type(x)为Boolean,则返回比较ToNumber(x)==y的结果。7.如果Type(y)为Boolean,则返回x==ToNumber(y)的比较结果。8.如果Type(x)为String或Number,Type(y)为Object,则返回比较x==ToPrimitive(y)的结果。9.如果Type(x)为Object,Type(y)为String或Number,则返回ToPrimitive(x)==y的比较结果。10.返回假。上面主要分为两类,当x和y的类型相同时,当类型不同时。当类型相同时,不进行类型转换,主要注意NaN不等于任何值,包括它本身,即NaN!==NaN。当类型不同时,1,x,y要么为null要么为undefined//返回true2,当x,y为Number和String类型时,转为Number类型比较。3.当有Boolean类型时,将Boolean转换为Number类型进行比较。4.一个Object类型,一个String或者Number类型,转换Object类型后,按照上面的流程比较原始值。3.1、==实例分析所以当类型不同的时候,可以对比上面几项,例如:vara={valueOf:function(){return1;},toString:function(){return'123'}}true==a//true;首先,x和y是不同的类型,x是boolean类型,所以ToNumber转换为1,是number类型。接下来,x为数字,y为对象类型,对y进行原始转换,ToPrimitive(a,?),不指定转换类型,使用默认的数字类型。然后,ToPrimitive(a,Number)首先调用valueOf方法,返回1,得到原始类型1。最后1==1,返回true。我们来看一个非常复杂的比较,如下:[]==!{}//1。这!运算符的优先级高于==,所以先做吧!手术。2、!{}运算结果为false,比较结果变为[]==false。3.根据上面第7项,等式右边的y=ToNumber(false)=0。结果变成[]==0。4.根据上面第9项,比较变成ToPrimitive([])==0。按照上面规则转换原值,[]会先调用valueOf函数,然后返回这。不是原来的值,继续调用toString方法,x=[].toString()=''。所以结果是''==0比较。5.根据上述第5项,等式左侧的x=ToNumber('')=0。所以结果变成:0==0,返回true,比较结束。最后再看文章开头提到的问题:consta={i:1,toString:function(){returna.i++;}}if(a==1&&a==2&&a==3){console.log('helloworld!');}1.当a==1&&a==2&&a==3时执行时,会从左到右一步步解析。首先,a==1会执行上面的第9步进行转换。ToPrimitive(a,Number)==1.2.ToPrimitive(a,Number),根据上面的原始类型转换规则,会先调用valueOf方法,a的valueOf方法继承自Object.prototype。返回a本身,而不是原始类型,因此将调用toString方法。3、因为重写了toString,会调用重写的toString方法,所以会返回1,注意这里是i++,不是++i。它会先返回i,然后返回i+1。所以ToPrimitive(a,Number)=1。也就是1==1,此时i=1+1=2。4、执行完a==1,返回true后,会执行a==2。同理会调用ToPrimitive(a,Number),如上,先调用valueOf方法,再调用toString方法。因为第一步,i=2.,ToPrimitive(a,Number)=2,即2==2,此时i=2+1。5.同上,可以推导出a==3也返回真。因此,最终结果a==1&&a==2&&a==3返回true。其实,了解了上面隐式转换的原理之后,你有没有发现这些隐式转换并没有想象中那么难。