17道JavaScript类型转换面试题深度解析

作者:开源拾贝时间:2026-10-23 10:11:30浏览:278

本文通过17道JavaScript经典题目,深入解析类型转换的底层逻辑。内容涵盖显式与隐式转换的区别,String、Boolean、Number三种原始类型的转换规则,以及对象类型通过[[ToPrimitive]]方法转换为原始类型的机制。重点解析一元运算符、比较运算符及相等运算符在类型转换中的行为,帮助开发者理解并避免常见的类型转换陷阱。

前言

前言

类型转换是指将数据从一种数据类型转换为另一种数据类型的过程,例如将字符串转换为数字,或将对象转换为布尔值。在JavaScript中,无论是原始类型还是对象类型,都可以进行类型转换。JavaScript的原始类型包括:number、string、boolean、null、undefined和Symbol。

本文将通过17道题目来深入探讨JavaScript中的类型转换机制。阅读完本文后,你将能够自信地回答这些问题,并理解其背后的原理。文章末尾提供了答案及详细解释。在阅读答案之前,建议先记录下你的答案,最后进行对照,以便发现理解上的偏差。


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]
! + [] + [] + ![]
new Date(0) - 0
new Date(0) + 0

上述类型的问题在JavaScript面试中出现的频率极高,因此建议继续阅读以下内容。

隐式与显式类型转换

隐式与显式类型转换

类型转换主要分为隐式类型转换和显式类型转换两类。

当开发人员编写特定代码(如使用Number(value))来在类型之间进行转换时,这被称为显式类型强制转换(或强制类型转换)。

然而,JavaScript是一种弱类型语言,在某些操作下,值可以在两种类型之间自动转换,这称为隐式类型转换。当对不同类型的值使用运算符时,通常会发生隐式类型转换,例如1 == null, 2 / "5", null + new Date()。此外,当值被if语句包裹时也可能发生类型转换,例如if(value) {}会将value转换为boolean类型。

严格相等运算符(===)不会触发类型的隐式转换,因此它可以用于比较值和类型是否都相等。

隐式类型转换是一把双刃剑。虽然使用它可以减少代码量,但有时也会导致难以发现的bug。

三种类型转换

三种类型转换

我们需要了解的第一条规则是:在JavaScript中,只有3种类型的转换。

  • to string
  • to boolean
  • to number

第二条规则是,类型转换的逻辑在原始类型和对象类型上有所不同,但它们最终只会转换成上述3种类型之一。

我们首先分析原始类型的转换过程。

String类型转换

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') // TypeError is thrown

Boolean类型转换

Boolean类型转换

Boolean()方法可用于显式将值转换为boolean类型。

隐式类型转换通常在逻辑判断或存在逻辑运算符(||、&&、!)时被触发。


Boolean(2) // 显示类型转换
if(2) {} // 逻辑判断触发隐式类型转换
!!2  // 逻辑运算符触发隐式类型转换
2 || 'hello' // 逻辑运算符触发隐式类型转换

注意:逻辑运算符(如||和&&)在内部会进行boolean类型转换,但实际上返回的是原始操作数的值,即使它们都不是boolean类型。


// 返回 number 类型 123,而不是 boolean 型 true
// 'hello' 和 '123' 仍然在内部会转换成 boolean 型来计算表达式
let x = '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类型转换

Number类型转换

与Boolean()和String()方法类似,Number()方法可用于显式将值转换为number类型。number的隐式类型转换较为复杂,因为它可能在多种情况下被触发。

  1. 比较操作(>、<、<=、>=)
  2. 按位操作(|、&、^、~)
  3. 算术操作(-、+、*、/、%)。注意,当+运算符存在任意一个操作数是string类型时,不会触发number类型的隐式转换。
  4. 一元+操作
  5. 非严格相等操作(==或!==)。注意,当==运算符的两个操作数都是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('my symbol')) // TypeError is thrown
+Symbol('123')   // TypeError is thrown

这里有2个特殊的规则需要记住:

1、当将==应用于null或undefined时,不会发生数值转换。null只等于null或undefined,不等于其他任何值。


null == 0  // false, null is not converted to 0
null == null  // true
undefined == undefined // true
null == undefined // true
undefined == 0  // false

2、NaN不等于任何值,包括它自己。


NaN === NaN // false

if(value !== value) { console.log('the value is NaN') }

object类型转换

object类型转换

至此,我们已经深入了解了原始类型的转换,接下来我们来看一下object类型的转换。

当涉及对象的操作(如[1] + [2,3])时,引擎首先会尝试将object类型转换为原始类型,然后再将原始类型转换为最终需要的类型,且仍然只有3种类型的转换:number、string、boolean。

最简单的情况是boolean类型的转换,任何非原始类型总是会转换成true,无论对象或数组是否为空。

对象通过内部[[ToPrimitive]]方法转换为原始类型,该方法负责数字和字符串转换。

[[ToPrimitive]]方法接受两个参数:一个输入值和一个需要转换的类型(Number或String)。

number和string的转换都使用了对象的两个方法:valueOf和toString。这两个方法都在Object.prototype上被声明,因此可用于任何派生类,如Date、Array等。

通常[[ToPrimitive]]算法如下:

  • 如果输入的值已经是原始类型,直接返回这个值。
  • 输入的值调用toString()方法,如果结果是原始类型,则返回。
  • 输入的值调用valueOf()方法,如果结果是原始类型,则返回。
  • 如果上述3个步骤之后,转换后的值仍然不是原始类型,则抛出TypeError错误。

number类型的转换首先会调用valueOf()方法,如果不是原始值再调用toString()方法。string类型的转换则相反。

大多数JavaScript内置对象类型的valueOf()返回这个对象本身,其结果经常被忽略,因为它不是一个原始类型。因此,大多数情况下,当object需要转换成number或string类型时,最终都调用了toString()方法。

当运算符不同时,[[ToPrimitive]]方法接受的转换类型参数也不相同。当存在==或+运算符时,一般会先触发number类型的转换,再触发string类型转换。

在JavaScript中,你可以通过重写对象的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[object Object]1'

==> +[] + {} + [1]
==> 0 + {} + [1]
==> 0 + '[object Object]' + '1'
==> '0[object Object]1'

所有的操作数都不是原始类型,所以会按照从左到右的顺序执行number类型的隐式转换,object和array类型的valueOf()方法返回它们本身,所以直接忽略,执行toString()方法。这里的技巧是,第一个{}不被视为object,而是块声明语句,因此它被忽略。计算从+[]表达式开始,该表达式通过toString()方法转换为空字符串,然后转换为0。


! + [] + [] + ![] // 'truefalse'

==> !(+[]) + [] + (![])
==> !0 + [] + false
==> true + [] + false
==> true + '' + false
==> 'truefalse'

一元运算符优先执行,+[]转为number类型0,![]转为boolean型false。


new Date(0) - 0 // 0

==> 0 - 0
==> 0

-运算符执行number类型隐式转换对于Date型的值,Date.valueOf()返回到毫秒的时间戳。


new Date(0) + 0

==> 'Thu Jan 01 1970 02:00:00 GMT+0200 (EET)' + 0
==> 'Thu Jan 01 1970 02:00:00 GMT+0200 (EET)0'

+运算符触发默认转换,因此使用toString()方法,而不是valueOf()。

总结

查看原文

好了,以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,谢谢大家对本站的支持。

作者最新文章