17道题让你彻底理解JS中的类型转换
前言
类型转换是将值从一种类型转换为另一种类型的过程(比如字符串转数字,对象转布尔值等)。任何类型不论是原始类型还是对象类型都可以进行类型转换,JavaScript的原始类型有:number,string,boolean,null,undefined,Symbol。
本文将通过17道题目来深入的了解JS中的类型转换,通过阅读本文之后,你将能自信的回答出下面题目的答案,并且能够理解背后的原理。在文章的最后,我讲写出答案并解释。在看答案之前,你可以把答案写下来,最后再对照一下,便于找出理解有误的地方。
true+false 12/"6" "number"+15+3 15+3+"number" [1]>null "foo"++"bar" "true"==true false=="false" null=="" !!"false"==!!"true" ["x"]=="x" []+null+1 [1,2,3]==[1,2,3] {}+[]+{}+[1] !+[]+[]+![] newDate(0)-0 newDate(0)+0
类似于上面的这些问题大概率也会在JS面试中被问到,所以继续往下读。
隐式vs显式类型转换
类型转换可以分为隐式类型转换和显式类型转换。
当开发人员通过编写适当的代码(如Number(value))用于在类型之间进行转换时,就称为显式类型强制转换(或强制类型转换)。
然而JavaScript是弱类型语言,在某些操作下,值可以在两种类型之间自动的转换,这叫做隐式类型转换。在对不同类型的值使用运算符时通常会发生隐式类型转换。比如1==null,2/"5",null+newDate()。当值被if语句包裹时也有可能发生,比如if(value){}会将value转换为boolean类型。
严格相等运算符(===)不会触发类型隐式转换,所以它可以用来比较值和类型是否都相等。
隐式类型转换是一把双刃剑,使用它虽然可以写更少的代码但有时候会出现难以被发现的bug。
三种类型转换
我们需要知道的第一个规则是:在JS中只有3种类型的转换
- tostring
- toboolean
- tonumber
第二,类型转换的逻辑在原始类型和对象类型上是不同的,但是他们都只会转换成上面3种类型之一。
我们首先分析一下原始类型转换。
String类型转换
String()方法可以用来显式将值转为字符串,隐式转换通常在有+运算符并且有一个操作数是string类型时被触发,如:
String(123)//显式类型转换 123+''//隐式类型转换
所有原始类型转String类型
String(123)//'123' String(-12.3)//'-12.3' String(null)//'null' String(undefined)//'undefined' String(true)//'true'
Symbol类型转String类型是比较严格的,它只能被显式的转换
String(Symbol('symbol'))//'Symbol(symbol)' ''+Symbol('symbol')//TypeErroristhrown
Boolean类型转换
Boolean()方法可以用来显式将值转换成boolean型。
隐式类型转换通常在逻辑判断或者有逻辑运算符时被触发(||&&!)。
Boolean(2)//显示类型转换 if(2){}//逻辑判断触发隐式类型转换 !!2//逻辑运算符触发隐式类型转换 2||'hello'//逻辑运算符触发隐式类型转换
注意:逻辑运算符(比如||和&&)是在内部做了boolean类型转换,但实际上返回的是原始操作数的值,即使他们都不是boolean类型。
//返回number类型123,而不是boolean型true //'hello'和'123'仍然在内部会转换成boolean型来计算表达式 letx='hello'&&123//x===123
boolean类型转换只会有true或者false两种结果。
Boolean('')//false Boolean(0)//false Boolean(-0)//false Boolean(NaN)//false Boolean(null)//false Boolean(undefined)//false Boolean(false)//false
任何不在上面列表中的值都会转换为true,包括object,function,Array,Date等,Symbol类型是真值,空对象和空数组也是真值。
Boolean({})//true Boolean([])//true Boolean(Symbol())//true !!Symbol()//true Boolean(function(){})//true
Number类型转换
和Boolean()、String()方法一样,Number()方法可以用来显式将值转换成number类型。
number的隐式类型转换是比较复杂的,因为它可以在下面多种情况下被触发。
- 比较操作(>,<,<=,>=)
- 按位操作(|&^~)
- 算数操作(-+*/%),注意,当+操作存在任意的操作数是string类型时,不会触发number类型的隐式转换
- 一元+操作
- 非严格相等操作(==或者!==),注意,==操作两个操作数都是string类型时,不会发生number类型的隐式转换
Number('123')//显示类型转换 +'123'//隐式类型转换 123!="456"//隐式类型转换 4>"5"//隐式类型转换 5/null//隐式类型转换 true|0//隐式类型转换
接下来看一下原始类型显示转换number类型会发生什么
Number(null)//0 Number(undefined)//NaN Number(true)//1 Number(false)//0 Number("12")//12 Number("-12.34")//-12.34 Number("\n")//0 Number("12s")//NaN Number(123)//123
当将一个字符串转换为一个数字时,引擎首先删除前尾空格、\n、\t字符,如果被修剪的字符串不成为一个有效的数字,则返回NaN。如果字符串为空,则返回0。
Number()方法对于null和undefined的处理是不同的,null会转换为0,undefined会转换为NaN
不管是显式还是隐式转换都不能将Symbol类型转为number类型,当试图这样操作时,会抛出错误。
Number(Symbol('mysymbol'))//TypeErroristhrown +Symbol('123')//TypeErroristhrown
这里有2个特殊的规则需要记住:
1、当将==应用于null或undefined时,不会发生数值转换。null只等于null或 undefined,不等于其他任何值。
null==0//false,nullisnotconvertedto0 null==null//true undefined==undefined//true null==undefined//true undefined==0//false
2、NaN不等于任何值,包括它自己
NaN===NaN//false if(value!==value){console.log('thevalueisNaN')}
object类型转换
到这里我们已经深入了解了原始类型的转换,接下来我们来看一下object类型的转换。
当涉及到对象的操作比如:[1]+[2,3],引擎首先会尝试将object类型转为原始类型,然后在将原始类型转为最终需要的类型,而且仍然只有3种类型的转换:number,string,boolean
最简单的情况是boolean类型的转换,任何非原始类型总是会转换成true,无论对象或数组是否为空。
对象通过内部[[ToPrimitive]]方法转换为原始类型,该方法负责数字和字符串转换。
[[ToPrimitive]]方法接受两个参数一个输入值和一个需要转换的类型(NumerorString)
number和string的转换都使用了对象的两个方法:valueOf和toString。这两个方法都在Object.prototype上被声明,因此可用于任何派生类,比如Date,Array等。
通常上[[ToPrimitive]]算法如下:
- 如果输入的值已经是原始类型,直接返回这个值。
- 输入的值调用toString()方法,如果结果是原始类型,则返回。
- 输入的值调用valueOf()方法,如果结果是原始类型,则返回。
- 如果上面3个步骤之后,转换后的值仍然不是原始类型,则抛出TypeError错误。
number类型的转换首先会调用valueOf()方法,如果不是原始值在调用toString()方法。string类型的转换则相反。
大多数JS内置对象类型的valueOf()返回这个对象本身,其结果经常被忽略,因为它不是一个原始类型。所以大多数情况下当object需要转换成number或string类型时最终都调用了toString()方法。
当运算符不同时,[[ToPrimitive]]方法接受的转换类型参数也不相同。当存在==或者+运算符时一般会先触发number类型的转换再触发string类型转换。
在JS中你可以通过重写对象的toString和valueOf方法来修改对象到原始类型转换的逻辑。
答案解析
接下来我们按照之前的转换逻辑来解释一下每一道题,看一下是否和你的答案一样。
true+false//1
'+'运算符会触发number类型转换对于true和false
12/'6'//2
算数运算符会把字符串‘6'转为number类型
"number"+15+3//"number153"
'+'运算符按从左到右的顺序的执行,所以优先执行“number”+15,把15转为string类型,得到“number15”然后同理执行“number15”+3
15+3+"number"//"18number"
15+3先执行,运算符两边都是number类型,不用转换,然后执行18+“number”最终得到“18number”
[1]>null//true ==>'1'>0 ==>1>0 ==>true
比较运算符>执行number类型隐式转换。
"foo"++"bar"//"fooNaN" ==>"foo"+(+"bar") ==>"foo"+NaN ==>"fooNaN"
一元+运算符比二元+运算符具有更高的优先级。所以+bar表达式先求值。一元加号执行字符串“bar”的number类型转换。因为字符串不代表一个有效的数字,所以结果是NaN。在第二步中,计算表达式'foo'+NaN。
'true'==true//false ==>NaN==1 ==>false 'false'==false//false ==>NaN==0 ==>false
==运算符执行number类型转换,'true'转换为NaN,boolean类型true转换为1
null==''//false
null不等于任何值除了null和undefined
!!"false"==!!"true"//true ==>true==true ==>true
!!运算符将字符串'true'和'false'转为boolean类型true,因为不是空字符串,然后两边都是boolean型不在执行隐式转换操作。
['x']=='x'//true
==运算符对数组类型执行number转换,先调用对象的valueOf()方法,结果是数组本身,不是原始类型值,所以执行对象的toString()方法,得到字符串'x'
[]+null+1//'null1' ==>''+null+1 ==>'null'+1 ==>'null1'
'+'运算符执行number类型转换,先调用对象的valueOf()方法,结果是数组本身,不是原始类型值,所以执行对象的toString()方法,得到字符串'',接下来执行表达式''+null+1。
0||"0"&&{}//{} ==>(0||'0')&&{} ==>(false||true)&&true ==>true&&true ==>true
逻辑运算符||和&&将值转为boolean型,但是会返回原始值(不是boolean)。
[1,2,3]==[1,2,3]//false
当运算符两边类型相同时,不会执行类型转换,两个数组的内存地址不一样,所以返回false
{}+[]+{}+[1]//'0[objectObject]1' ==>+[]+{}+[1] ==>0+{}+[1] ==>0+'[objectObject]'+'1' ==>'0[objectObject]1'
所有的操作数都不是原始类型,所以会按照从左到右的顺序执行number类型的隐式转换,object和array类型的valueOf()方法返回它们本身,所以直接忽略,执行toString()方法。这里的技巧是,第一个{}不被视为object,而是块声明语句,因此它被忽略。计算从+[]表达式开始,该表达式通过toString()方法转换为空字符串,然后转换为0。
!+[]+[]+![]//'truefalse' ==>!(+[])+[]+(![]) ==>!0+[]+false ==>true+[]+false ==>true+''+false ==>'truefalse'
一元运算符优先执行,+[]转为number类型0,![]转为boolean型false。
newDate(0)-0//0 ==>0-0 ==>0
'-'运算符执行number类型隐式转换对于Date型的值,Date.valueOf()返回到毫秒的时间戳。
newDate(0)+0 ==>'ThuJan01197002:00:00GMT+0200(EET)'+0 ==>'ThuJan01197002:00:00GMT+0200(EET)0'
'+'运算符触发默认转换,因此使用toString()方法,而不是valueOf()。
总结
查看原文
好了,以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,谢谢大家对毛票票的支持。