当前位置: 首页 > 科技观察

JavaScript运算符规则与隐式类型转换详解

时间:2023-03-11 21:01:23 科技观察

本文涉及的参考资料均在JavaScript数据结构学习与实践数据索引中声明。隐式类型转换在JavaScript中,当我们进行比较运算或加减乘除四种算术运算时,往往会触发JavaScript的隐式类型转换机制;这部分经常令人困惑。例如,浏览器中的console.log操作通常是将任意值转换成字符串再显示,而数学运算则是先将值转换成数字类型(Date类型对象除外)再进行操作。我们先来看看JavaScript中运算符运算的一些典型结果,希望看完这一部分后,能对每一项给出合理的解释://Compare[]==![]//trueNaN!==NaN//true1==true//true2==true//false"2"==true//flasenull>0//falsenull<0//falsenull==0//falsenull>=0//true//加法true+1//1undefined+1//NaNletobj={};{}+1//1,其中{}被视为一个代码块{1+1}+1//1obj+1//[objectObject]1{}+{}//Chrome显示“[objectObject][objectObject]”,Firefox显示NaN[]+{}//[objectObject][]+a//[objectObject]+[]//相当于+""=>0{}+[]//0a+[]//[objectObject][2,3]+[1,2]//'2,31,2'[2]+1//'21'[2]+(-1)//"2-1"//减法或其他操作,不能进行字符串拼接,所以返回NaN[2]-1//1[2,3]-字符串格式错误1//NaN{}-1//-1基本类型之间的转换我们在JavaScript中常说的基本类型包括数值类型、字符串类型、布尔类型、空类型;转换函数为String、Number和Boolean//Stringletvalue=true;console.log(typeofvalue);//booleanvalue=String(value);//nowvalueisastring"true"console.log(typeofvalue);//string//Numberletstr="123";console.log(typeofstr);//stringletnum=Number(str);//变成number123console.log(typeofnum);//numberlettage=Number("anarbitrarystringinsteadofanumber");console.log(age);//NaN,conversionfailed//Booleanconsole.log(Boolean(1));//trueconsole.log(Boolean(0));//falseconsole.log(Boolean("hello"));//trueconsole.log(Boolean(""));//false最后,我们可以得到如下JavaScript原始类型转换表(包括复合类型到原始类型转换的例子):在比较运算和加法运算中,涉及到将运算符两边的运算对象转换为原对象的步骤;在JavaScript中,这种转换实际上是由ToPrimitive函数执行的实际上,当一个对象出现在需要原始类型进行操作的上下文中时,JavaScript会自动调用ToPrimitive函数将对象转换为原始类型;这是一个典型的场景。这个函数的签名如下:ToPrimitive(input,PreferredType?)为了更好的理解它的工作原理,我们可以简单的用JavaScript实现:varToPrimitive=function(obj,preferredType){varAPIs={typeOf:function(obj){returnObject.prototype.toString.call(obj).slice(8,-1);},isPrimitive:function(obj){var_this=this,types=['Null','Undefined','String','Boolean','Number'];returntypes.indexOf(_this.typeOf(obj))!==-1;}};//如果obj本身已经是原始对象,则直接返回if(APIs.isPrimitive(obj)){returnobj;}//对于Date类型,会优先使用它的toString方法;否则,valueOf方法preferredType=(preferredType==='String'||APIs.typeOf(obj)==='Date')?'String':'Number';if(preferredType==='Number'){if(APIs.isPrimitive(obj.valueOf())){returnobj.valueOf()};if(APIs.isPrimitive(obj.toString())){returnobj.toString()};}else{if(APIs.isPrimitive(obj.toString())){returnobj.toString()};if(APIs.isPrimitive(obj.valueOf())){returnobj.valueOf()};}thrownewTypeError('TypeError');}我们可以简单地覆盖一对对象的valueOf方法,可以发现运算结果发生了变化:letobj={valueOf:()=>{return0;}}obj+1//1如果我们强制一个对象的valueOf和toString方法是overwritten如果返回值是对象,会直接抛出异常log("toString");return{};//notaprimitive}}obj+1//errorUncaughtTypeError:Cannotconvertobjecttoprimitivevalueat:1:5值得一提的是调用数值类型的valueOf()函数的结果为仍然是一个数组。所以数组类型隐式类型转换的结果是一个字符串。ES6引入Symbol类型后,JavaScript会先调用对象的[Symbol.toPrimitive]方法将对象转换为原始类型,然后方法的调用顺序变为:当obj[Symbol.toPrimitive](preferredType)方法如果存在,先调用该方法;如果preferredType参数是String,则依次尝试obj.toString()和obj.valueOf();如果preferredType参数是Number或默认值,则依次尝试obj.valueOf()和obj.toString()。[Symbol.toPrimitive]方法的签名是:obj[Symbol.toPrimitive]=function(hint){//returnaprimitivevalue//hint=oneof"string","number","default"}我们也可以重写这个Method修改对象的运行性能:user={name:"John",money:1000,[Symbol.toPrimitive](hint){console.log(`hint:${hint}`);returnhint=="string"?`{name:"${this.name}"}`:this.money;}};//conversionsdemo:console.log(user);//hint:string->{name:"John"}console.log(+user);//hint:number->1000console.log(user+500);//hint:default->1500比较操作JavaScript为我们提供了严格比较和类型转换比较两种模式,严格比较(===)只有当运算符两边的操作数类型和内容相同时才返回true,否则返回false。比较广泛使用的==运算符首先将操作数转换为相同的类型,然后进行比较。对于<=等操作,会先转化为原始对象(Primitives),再进行比较。标准相等运算符(==和!=)使用抽象相等比较算法来比较运算符(x==y)两侧的操作数。算法流程要点提炼如下:如果x或y其中之一为NaN,则返回false;如果x和y都为null或undefined,则返回true(null==undefined//true);否则返回false(null==0//false);ifx,y的类型不一致,且x,y是String,Number,Boolean其中之一,则使用toNumber函数将x,y转为Number类型再进行比较;如果x,y其中一个是Object,先用ToPrimitive函数将其转换为原始类型再进行比较。我们来回顾一下文章开头提出的比较操作[]==![]。首先,[]是一个对象,然后调用ToPrimitive函数将其转换为字符串"";对于右边的![],首先会显式Typecast,将其转为false。那么在比较运算中,运算符两边的操作数都会被转为数值类型,即转为0,所以最终的比较结果为真。上面也介绍了null>=0为真的比较结果,ECMAScript中也规定了如果<为假,那么>=为真。Addition对于加法运算,JavaScript首先将运算符两边的对象转换为Primitive类型;然后,当适当的隐式类型转换可以产生有意义的值时,JavaScript首先执行隐式类型转换,然后再执行操作。例如表达式value1+value2会先调用ToPrimitive函数将两个操作数转换为原始类型:调用了first方法,由于数组的valueOf方法的返回值仍然是数组类型,所以返回的是它的字符串表示形式。如果转换后的prim1和prim2中有一个是字符串,则先进行字符串拼接;否则进行加法运算。