前言之前采访过几位开发者。他们确实做过很多项目,能力也不错,但是发现js基础不扎实,所以决定写这篇javascrip数据类型相关的基础文章。其实不只是因为我采访了他们。之前面试的时候被骂过。我还记得面试官说的最深刻的一句话。基础非常重要。只有基础好,bug才会少。大多数错误是由基础薄弱引起的。这里有两道面试题和答案,涉及到我们公司数据类型的基础知识。如果你能做对并且知道为什么(你可以选择忽略这篇文章)://类型转换相关问题varbar=true;console.log(bar+0);console.log(bar+"xyz");console.日志(栏+真);console.log(栏+假);console.log('1'>栏);console.log(1+'2'+false);console.log('2'+['考拉',1]);varobj1={a:1,b:2}console.log('2'+obj1);varobj2={toString:function(){return'a'}}console.log('2'+obj2)//输出结果1truexyz21false12false2koala,12[objectObject]2a//作用域和NaN这里不具体说作用域,意在说明NaNvarb=1;functionouter(){varb=2;functioninner(){b++;console.log(b);varb=3;}inner();}outer();//输出结果NaN本文将以面试官提问的角度进行分析。js中的数据类型面试官:告诉我javascript中有哪些数据类型?JavaScript中有七种内置数据类型,包括基本类型和对象类型。基本类型基本类型分为以下六种类型:string(字符串)boolean(布尔)number(数字)symbol(符号)null(空值)undefined(未定义)注:string,number,boolean和nullundefined这几种类型统称为原始类型(Primitive),表示不能进一步细分的基本类型;symbol是ES6中的一种新数据类型,symbol代表最高值,通过调用Symbol函数生成。由于生成的symbol值是Primitive类型,所以不能用new调用Symbol函数;null和undefined通常被认为是特殊值,这两种类型的唯一值就是它自己。对象类型对象类型也称为引用类型,数组和函数是对象的子类型。从逻辑上讲,对象是属性的无序集合,是存储各种值的容器。对象值存储引用地址,因此不同于基本类型值的不可变特性,对象值是可变的。js弱类型语言面试官:谈谈你对javascript作为弱类型语言的理解?JavaScript是一种弱类型语言,JavaScript在声明变量时没有预先确定的类型。变量的类型就是它的值的类型,也就是说变量当前的类型是由它的值决定的。夸张一点,前一秒是String类型,下一秒可能就是Number类型。在这个过程中,可能会进行一些操作,发生强制类型转换。弱类型不需要预定类型的特性虽然给我们带来了方便,但也给我们带来了麻烦。为了充分利用这个特性,我们必须掌握类型转换的原理。js中的强制转换规则面试官:javascript中的强制类型转换是一个非常容易出错的点。你知道强制转换的规则吗?注意:规则***在转换发生时与以下规则结合使用以查看更好的结果。ToPrimitive(转换为原始值)??ToPrimitive不转换原始类型,只针对引用类型(对象),其目的是将引用类型(对象)转换为非对象类型,即原始类型。ToPrimitive运算符接受一个值和一个可选的所需类型作为参数。ToPrimitive运算符将值转换为非对象类型。如果对象能够被转换为多个原始类型,可选的预期类型可以用来暗示该类型。转换结果的原始类型由预期类型决定,实际上就是我们传递的类型。直接看下面以获得更多清晰度。ToPrimitive方法可能看起来像这样,如下所示。/***@obj待转换对象*@type期望转换为原始数据类型,可选*/ToPrimitive(obj,type)不同值的类型描述typeisstring:首先调用toString的方法obj,如果是原始值,则返回,否则转第2步,调用obj的valueOf方法,如果是原始值,则返回,否则转第3步,抛出TypeError异常typeisnumber:firstcallobj的valueOf方法,如果是原值则返回,否则转第2步调用obj的toString方法,如果是原值则返回,否则第3步抛出TypeError异常类型参数为空如果对象是Date,则type设置为String否则,type设置为NumberDate数据类型特别说明:对于Date数据类型,我们期望得到的是转换为时间的字符串,而不是毫秒值(timestamp).如果是数字,会得到对应的毫秒值。显然Strings用得更多。其他类型的对象可以根据值的类型进行操作。ToPrimitive总结了ToPrimitive转换成哪种原始类型,取决于类型,类型参数是可选的。如果指定,将根据指定的类型进行转换。如不指定,默认根据实际情况分为两种情况。日期是字符串,其他对象是数字。那么什么时候指定类型呢,要看下面两种转换方式。toStringObject.prototype.toString()toString()方法返回表示对象的字符串。每个对象都有一个toString()方法,当对象被表示为文字值时或当对象以需要字符串的方式被引用时,该方法会自动调用。请记住,valueOf()和toString()在某些情况下会调用自己。valueOfObject.prototype.valueOf()方法返回指定对象的原始值。JavaScript调用valueOf()方法将对象转换为基本类型的值(数字、字符串和布尔值)。但我们很少需要自己调用这个函数,valueOf方法通常由JavaScript自动调用。不同内置对象的valueOf的实现:String=>返回字符串值Number=>返回数值Date=>返回一个数字,即时间值,字符串中的内容是一个Boolean,依赖于具体实现=>返回BooleanthisvalueObject=>返回这个比较代码会更清楚:varstr=newString('123');console.log(str.valueOf());//123varnum=newNumber(123);console.log(num.valueOf());//123vardate=newDate();console.log(date.valueOf());//1526990889729varbool=newBool??ean('123');console.log(bool.valueOf());//truevarobj=newObject({valueOf:()=>{return1}})console.log(obj.valueOf());//1NumberNumber运算符转换规则:null转换为0,undefined转换为NaN,true转换为1,false转换为0,字符串转换时遵循数值常量的规则,转换失败则返回NaN。注意:必须先将对象转换为原始值,然后调用ToPrimitive转换。type指定为number,继续返回ToPrimitive进行转换。StringString运算符转换规则:null转换为'null',undefined转换为undefined,true转换为'true',false转换为'false'数字转换遵循一般规则,极小的数字使用指数form注意:对象必须先转换为原始值,调用ToPrimitive转换,类型指定为string,继续返回ToPrimitive进行转换(上面有ToPrimitive的转换规则)。String(null)//'null'String(undefined)//'undefined'String(true)//'true'String(1)//'1'String(-1)//'-1'String(0)//'0'String(-0)//'0'String(Math.pow(1000,10))//'1e+30'String(Infinity)//'Infinity'String(-Infinity)//'-Infinity'String({})//'[objectObject]'String([1,[2,3]])//'1,2,3'String(['koala',1])//考拉,1BooleanToBoolean运算符转换规则除以下6个值转换为false外,其他均为true:undefinednull-00or+0NaN''(空字符串)所有值均为true。其中,所有对象(包括空对象)的转换结果为真,即使是false对应的布尔对象newBoolean(false)也为真。Boolean(undefined)//falseBoolean(null)//falseBoolean(0)//falseBoolean(NaN)//falseBoolean('')//falseBoolean({})//trueBoolean([])//trueBoolean(newBool??ean(false)))//truejs的转换规则适用于不同的场景。面试官问:我知道具体的转换规则,但是什么情况下会发生什么样的转换?什么时候会自动转为没有对象的字符串类型呢?字符串的自动转换主要发生在添加字符串的过程中。当一个值是字符串而另一个是非字符串时,后者被转换为字符串。'2'+1//'21''2'+true//"2true"'2'+false//"2false"'2'+undefined//"2undefined"'2'+null//"2null"当有对象和对象时+//toString的对象varobj2={toString:function(){return'a'}}console.log('2'+obj2)//输出结果2a//正则对象varobj1={a:1,b:2}console.log('2'+obj1);//输出结果2[objectObject]//几个特殊对象'2'+{}//"2[objectObject]"'2'+[]//"2"'2'+function(){}//"2function(){}"'2'+['koala',1]//2koala,1详细为下面的'2'+obj2例子如下:左边是string,转换后ToPrimitive的原值不变。右侧也按照ToPrimitive进行转换。由于指定类型为number,ToPrimitive转换调用obj2.valueof(),得到的值不是原始值。进行第三步,调用toString()返回。'a'符号两边都是字符串,+运算符通过String规则转换为字符串类型拼接输出结果2a。下面的'2'+obj1具体说明如下:Left如果是字符串,ToPrimitive转为原值后不会改变。右侧转换时,原值也会按照ToPrimitive进行转换。由于指定类型为number,进行ToPrimitive转换,调用obj2.valueof()得到{a:1,b:2}调用toString()返回[objectObject]符号两边都有字符串,+运算符通过拼接输出结果的String规则转换为字符串类型2.[objectObject]代码中几个特殊对象的转换规则基本相同。不一一说理解的话,可以想想这个过程注:不管是不是对象,都有一个转换为原始值的过程,即ToPrimitive转换,但是原始类型的转换并没有改变,具体将发生对象类型的转换。string类型转换开发中可能出错的点:varobj={width:'100'};obj.width+20//"10020"预期输出结果为120时实际输出结果10020自动转换为Number类型,有一个加法运算符,但是当没有String类型时,会先转为Number类型。示例:true+0//1true+true//2true+false//1除了加法运算符,其他运算符会自动将运算转换为值。示例:'5'-'2'//3'5'*'2'//10true-1//0false-1//-1'1'-1//0'5'*[]//0false/'5'//0'abc'-1//NaNnull+1//1undefined+1//NaN//一元运算符(注)+'abc'//NaN-'abc'//NaN+true//1-false//0注意:null转为值时为0,undefined转为值时为NaN。判断等号也放在Number中,用于特殊说明==抽象的相等比较不同于+运算符。不再是String优先,而是Number优先。以下是x==y的示例。如果x和y都是数字,就没有什么可以直接解释的了。1==2//false如果有对象,ToPrimitive()类型转为number,后面再比较varobj1={valueOf:function(){return'1'}}1==obj1//true//obj1转换为原值,调用obj1.valueOf()//返回原值'1'//'1'toNumber得到1然后比较1==1[]==![]//true//[]asobjectToPrimitive得到''//![]asboolean转换得到0//''==0//转换为0==0//true存在boolean,根据boolean转换为1或0ToNumber,后面再比较//boolean先转为number,按照上面的规则得到1//3==1false//0==0true3==true//false'0'==false//真4。如果x为字符串,y为数字,x转为数字进行比较//'0'toNumber()得到0//0==0true'0'==0//true何时进行布尔转换布尔比较if(obj),while(obj)等判断或三元运算符只能包含布尔值条件部分的每个值都等价于false,使用否定运算符后就变成trueif(!undefined&&!null&&!0&&!NaN&&!''){console.log('true');}//true//下面两种情况也会被转换成boolean类型的表达式?true:false!!expressionjs中的数据类型判断面试官问:如何判断数据类型?如何判断一个值是数组类型还是对象?三种方式,即typeof、instanceof和Object.prototype.toString()typeof都是通过typeof运算符来判断一个值属于哪个原始类型。typeof'seymoe'//'string'typeoftrue//'boolean'typeof10//'number'typeofSymbol()//'symbol'typeofnull//'object'无法判断是否为nulltypeofundefined//'undefined'typeof{}//'object'typeof[]//'object'typeof(()=>{})//'function'上面代码的输出显示null的判断有误。如果使用typeof,null得到的结果就是对象运算符判断对象类型及其子类型,如函数(可调用对象)、数组(有序索引对象)等,object的结果将是获得除了功能。综上,可以看出typeOf对于类型的判断还是有一些不足的,在object子类型和null的情况下。instanceof也可以通过instanceof操作符来判断对象类型。原理是检测构造函数的原型是否出现在被检测对象的原型链上。[]instanceofArray//true({})instanceofObject//true(()=>{})instanceofFunction//true复制代码注意:instanceof也不完美。例如:letarr=[]letobj={}arrinstanceofArray//truearrinstanceofObject//trueobjinstanceofObject//true在这个例子中,arr数组相当于来自newArray()的实例,所以arr.__proto__===Array.prototype,又因为Array属于Object的子类型,即Array.prototype.__proto__===Object.prototype,所以Object的构造函数在arr的原型链上。所以instanceof还是不能优雅的判断一个值是属于数组还是普通对象。还有一件事需要解释。有些开发人员会说Object.prototype.__proto__===null。不是说arrinstanceofnull也应该为真吗?这个语句实际上会报错,提示右边的参数应该是一个对象,这也证实了typeof为object的null结果真的只是javascript的一个bug。Object.prototype.toString()可以说是JavaScript中判断数据类型的最佳方案。具体用法请参考以下代码:Object.prototype.toString.call({})//'[objectObject]'Object.prototype.toString.call([])//'[objectArray]'Object.prototype.toString.call(()=>{})//'[objectFunction]'Object.prototype.toString.call('seymoe')//'[objectString]'Object.prototype.toString.call(1)//'[objectNumber]'Object.prototype.toString.call(true)//'[objectBoolean]'Object.prototype.toString.call(Symbol())//'[objectSymbol]'Object.prototype.toString.call(null)//'[objectNull]'Object.prototype.toString.call(undefined)//'[objectUndefined]'Object.prototype.toString.call(newDate())//'[objectDate]'Object.prototype.toString.call(Math)//'[objectMath]'Object.prototype.toString.call(newSet())//'[objectSet]'Object.prototype.toString.call(newWeakSet())//'[objectWeakSet]'Object.prototype.toString.call(newMap())//'[objectMap]'Object.prototype.toString.call(newWeakMap())//'[objectWeakMap]'我们可以发现,无论传入何种类型的值,该方法都能返回对应的准确对象类型。虽然用法简单明了,但有几点需要理解清楚:该方法的本质是依赖于Object.prototype.toString()方法来获取对象的内部属性[[Class]]目的。包装null和undefined后,输出结果就是内部实现。进行NaN相关汇总。NaN的概念是全局对象的属性。NaN是一种特殊类型的数字。何时返回NaN(从头开始的第二个问题也解决了)无穷大除以无穷大对任何负数取平方根操作数不是数字或不能转换为数字的算术运算符字符串被解析为数字一些例子:Infinity/Infinity;//无穷大除以无穷大Math.sqrt(-1);//对任意负数'a'-1进行平方根运算;//算术运算符与不是数字或不能转换为数字的操作数一起使用'a'*1;'a'/1;parseInt('a');//字符串解析成数字parseFloat('a');Number('a');//NaN'abc'-1//NaNundefined+1//NaN//一元运算符(注)+'abc'//NaN-'abc'//NaN的误解toString和String的区别toStringtoString()可以把所有数据都转成字符串,null和undefined不能被转换。console.log(null.toString())//ErrorTypeError:Cannotreadproperty'toString'ofnullconsole.log(undefined.toString())//ErrorTypeError:Cannotreadproperty'toString'ofundefinedtoString()括号中可以写数字,代表基本系统二进制:.toString(2);八进制:.toString(8);十进制:.toString(10);十六进制:.toString(16);StringString()可以将null和undefined转成字符串,但是Method不能转十六进制字符串console.log(String(null));//nullconsole.log(String(undefined));//undefined【编者推荐】2019年,对开发者有用的7个常用JavaScript工具如何构建基于数据的舆情分析?2019年6月Github热门JavaScript开源项目!新手可以看看!!!必备的6个JavaScript库干货分享:利用Java多线程技术将数据导入Elasticsearch
