当前位置: 首页 > 后端技术 > Node.js

17道面试题彻底理解JavaScript中的类型转换

时间:2023-04-04 01:15:32 Node.js

类型转换就是将一个值从一种类型转换为另一种类型(比如字符串到数字、对象到布尔值等)的过程。任何类型都可以转换,无论它是原始类型还是对象类型。JavaScript的原始类型有:number、string、boolean、null、undefined、Symbol。本文将通过17道题,带你深入了解JS中的类型转换。阅读本文后,您将能够自信地回答以下问题并理解其背后的原理。在文章的最后,我写下了答案并进行了解释。看答案之前,可以把答案写下来,最后对比一下,找出误区。true+false12/"6""number"+15+315+3+"number"[1]>null"foo"++"bar""true"==truefalse=="false"null==""!!"false"==!!"true"["x"]=="x"[]+null+1[1,2,3]==[1,2,3]{}+[]+{}+[1]!+[]+[]+![]newDate(0)-0newDate(0)+0类似上面的问题很可能在JS面试中被问到,继续往下看。隐式类型转换vs显式类型转换类型转换可以分为隐式类型转换和显式类型转换。当开发人员通过编写适当的代码(例如Number(value))在类型之间进行转换时,它被称为显式类型转换(或强制转换)。然而,JavaScript是一种弱类型语言。在某些操作下,值可以在两种类型之间自动转换,这称为隐式类型转换。当运算符用于不同类型的值时,经常会发生隐式类型转换。例如1==null,2/"5",null+newDate()。当值被包裹在if语句中时,也会发生这种情况,例如if(value){}会将值转换为布尔类型。严格相等运算符(===)不会触发类型隐式转换,因此可用于比较值和类型是否相等。隐式类型转换是一把双刃剑。虽然使用它可以少写一些代码,但有时也会出现很难发现的bug。关于这三种类型的转换,我们首先要知道的规则是:JS中只有3种类型的转换:字符串到布尔值到数字;其次,类型转换的逻辑在原始类型和对象类型中是不同的,但它们都只是转换为上述3种类型之一。我们先来分析原始类型转换。字符串类型转换String()方法可用于将值显式转换为字符串。隐式转换通常在有+运算符且操作数为字符串类型时触发,如:String(123)//显式类型转换123+''//将所有原始类型隐式类型转换为String类型String(123)//'123'String(-12.3)//'-12.3'String(null)//'null'String(undefined)//'undefined'String(true)//'true'SymboltypetoStringtypeis比较严格,只能显式转换String(Symbol('symbol'))//'Symbol(symbol)'''+Symbol('symbol')//抛出TypeErrorBoolean类型转换可以使用Boolean()方法将值显式转换为布尔类型。隐式类型转换通常在有逻辑判断或逻辑运算符(||&&!)时触发。Boolean(2)//显式类型转换if(2){}//逻辑判断触发隐式类型转换!!2//逻辑运算符触发隐式类型转换2||'hello'//逻辑运算符触发隐式类型转换注意:逻辑运算符(如||和&&)在内部进行布尔类型转换,但实际上返回原始操作数的值,即使它们不是布尔类型。//returnnumbertype123insteadofbooleantypetrue//'hello'和'123'仍然在内部转换为boolean类型来计算表达式letx='hello'&&123//x===123boolean类型转换有只有两个结果,真或假。Boolean('')//falseBoolean(0)//falseBoolean(-0)//falseBoolean(NaN)//falseBoolean(null)//falseBoolean(undefined)//falseBoolean(false)//false对于任何未列出的above中的值都会被转换为true,包括object、function、Array、Date等,Symbol类型为true值,空对象和空数组也为true。Boolean({})//trueBoolean([])//trueBoolean(Symbol())//true!!Symbol()//trueBoolean(function(){})//trueNumber类型转换和Boolean(),String()方法,Number()方法可用于将值显式转换为数字类型。number的隐式类型转换比较复杂,因为它可以在以下几种情况下触发。比较运算(>,<,<=,>=)按位运算(|&^~)算术运算(-+*/%),注意当+运算的任意操作数为字符串类型时,不会触发Implicit数字类型转换一元+操作非严格相等操作(==或!==),注意当==操作的两个操作数都是字符串类型时,不会发生数字类型的隐式转换Number('123')//显式类型转换+'123'//隐式类型转换123!="456"//隐式类型转换4>"5"//隐式类型转换5/null//隐式类型转换true|0//隐式类型转换接下来查看原始类型以显示转换数字类型时发生的情况Number(null)//0Number(undefined)//NaNNumber(true)//1Number(false)//0Number("12")//12Number("-12.34")//-12.34Number("\n")//0Number("12s")//NaNNumber(123)//123将字符串转换为数字时,引擎首先去除前导和尾随空格、n、t字符,如果修剪后的字符串未成为有效数字,则返回NaN。如果字符串为空,则返回0。Number()方法对null和undefined的处理方式不同。Null将被转换为0,undefined将被转换为NaN。无论是显式还是隐式转换,Symbol类型都无法转换为number类型。当尝试这样做时,它会抛出一个错误。Number(Symbol('mysymbol'))//TypeErroristhrown+Symbol('123')//TypeErroristhrown这里有2个特殊规则要记住:当应用==到null或undefined时,不会进行数字转换发生。null仅等于null或undefined,不等于任何其他值。null==0//false,null不转换为0null==null//trueundefined==undefined//truenull==undefined//trueundefined==0//falseNaN不等于任何值,包括它自己的NaN===NaN//falseif(value!==value){console.log('valueisNaN')}对象类型转换到这里我们对原始类型的转换有了深刻的理解,我们来看看对象类型。当涉及到对象操作如:[1]+[2,3]时,引擎会先尝试将对象类型转换为原始类型,然后再将原始类型转换为最终需要的类型,还有只有3种类型的转换:number,string,boolean最简单的情况是boolean类型转换,任何非原始类型都会被转换为true,无论对象或数组是否为空。对象通过内部[[ToPrimitive]]方法转换为原始类型,该方法负责数字和字符串转换。[[ToPrimitive]]方法接受两个参数,一个输入值和一个要转换的类型(数字或字符串)。number和string的转换使用了对象的两个方法:valueOf和toString。这两个方法都在Object.prototype上声明,因此可用于任何派生类,如Date、Array等。通常[[ToPrimitive]]算法如下:如果输入值已经是原始类型,则直接返回此值。输入值调用toString()方法,如果结果是原始类型则返回。输入的值调用valueOf()方法,如果结果是基本类型,则该方法返回。如果经过上述3步,转换后的值仍然不是原来的类型,则抛出TypeError错误。数字类型的转换会先调用valueOf()方法,如果不是原始值,则调用toString()方法。字符串转换则相反。大多数JS内置对象类型的valueOf()返回对象本身,其结果通常被忽略,因为它不是原始类型。所以在大多数情况下,当对象需要转换为数字或字符串类型时,最后调用的是toString()方法。当运算符不同时,[[ToPrimitive]]方法接受不同的转换类型参数。当有==或+运算符时,一般先触发数字类型转换,再触发字符串类型转换。在JS中,您可以通过重写对象的toString和valueOf方法来修改对象到原始类型的转换逻辑。答案解析接下来我们就按照前面的转换逻辑对每道题进行解释,看看是不是和你的答案一样。true+false//1'+'运算符会触发数字类型的真假转换12/'6'//2个算术运算符会将字符串'6'转换为数字类型"number"+15+3//"number153"'+'运算符是从左到右依次执行的,所以先执行"number"+15,将15转成string类型得到"number15",然后执行"number15"+315+3+同理"number"//"18number"先执行15+3,运算符两边都是number类型,不做转换,再执行18+"number"最后得到"18number"[1]>null//true==>'1'>0==>1>0==>true比较运算符>执行数字类型的隐式转换。"foo"++"bar"//"fooNaN"==>"foo"+(+"bar")==>"foo"+NaN==>"fooNaN"一元+运算符优于二元+运算符更高的优先级。所以+栏表达式首先被评估。一元加号执行字符串“bar”的数字转换。因为该字符串不代表有效数字,所以结果为NaN。在第二步中,计算表达式'foo'+NaN。'true'==true//false==>NaN==1==>false'false'==false//false==>NaN==0==>false==运算符进行数字类型转换,'true'转为NaN,boolean类型true转为1null==''//falsenull不等于除null和undefined以外的任何值!!"false"==!!"true"//true==>true==true==>true!!运算符将字符串'true'和'false'转换为boolean类型true,因为它不是空字符串,然后两边都是boolean类型,不进行隐式转换操作。['x']=='x'//true==运算符对数组类型进行数字转换,首先调用对象的valueOf()方法,结果是数组本身,不是原始类型值,所以执行对象的toString()方法获取字符串'x'[]+null+1//'null1'==>''+null+1==>'null'+1==>'null1''+'运算符执行number类型转换,先调用对象的valueOf()方法,结果是数组本身,不是原始类型值,所以执行对象的toString()方法得到string'',然后执行表达式''+null+1。0||"0"&&{}//{}==>(0||'0')&&{}==>(false||true)&&true==>true&&true==>true逻辑操作||&&将值转换为布尔值,但返回原始值(不是布尔值)。[1,2,3]==[1,2,3]//false当运算符两边的类型相同时,不会进行类型转换,两个数组的内存地址不同,所以返回false{}+[]+{}+[1]//'0[objectObject]1'==>+[]+{}+[1]==>0+{}+[1]==>0+'[objectObject]'+'1'==>'0[objectObject]1'所有操作数都不是原始类型,所以数字类型的隐式转换会按照从左到右的顺序进行,并且valueOf()方法会返回它们自己,因此只需忽略并执行toString()方法即可。这里的技巧是第一个{}不被认为是一个对象,而是一个块声明语句,所以它被忽略了。计算从+[]表达式开始,它被转换为空字符串,然后通过toString()方法转换为0。!+[]+[]+![]//'truefalse'==>!(+[])+[]+(![])==>!0+[]+false==>true+[]+false==>true+''+false==>'truefalse'先执行一元运算符,+[]转为number类型0,![]转为boolean类型false。newDate(0)-0//0==>0-0==>0“-”运算符执行数字类型的隐式转换。对于日期值,Date.valueOf()返回以毫秒为单位的时间戳。newDate(0)+0==>'1970年1月1日星期四02:00:00GMT+0200(EET)'+0==>'1970年1月1日星期四02:00:00GMT+0200(EET)0''+'运算符触发默认转换,因此使用toString()方法而不是valueOf()。综上,查看原文关注github上一道日常面试题的详解