17道题完全理解 JavaScript 中的类型转换

类型转换是将值从一种类型转换为另外一种类型的过程(好比字符串转数字,对象转布尔值等)。任何类型不管是原始类型仍是对象类型均可以进行类型转换,JavaScript 的原始类型有:number, string, boolean, null, undefined, Symbol。git

本文将经过 17 道题目来深刻的了解 JS 中的类型转换,经过阅读本文以后,你将能自信的回答出下面题目的答案,而且可以理解背后的原理。在文章的最后,我讲写出答案并解释。在看答案以前,你能够把答案写下来,最后再对照一下,便于找出理解有误的地方。github

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
复制代码

相似于上面的这些问题大几率也会在 JS 面试中被问到, 因此继续往下读。面试

隐式 vs 显式类型转换

类型转换能够分为隐式类型转换和显式类型转换。算法

当开发人员经过编写适当的代码(如Number(value))用于在类型之间进行转换时,就称为显式类型强制转换(或强制类型转换)。数组

然而 JavaScript 是弱类型语言,在某些操做下,值能够在两种类型之间自动的转换,这叫作隐式类型转换。在对不一样类型的值使用运算符时一般会发生隐式类型转换。好比 1 == null, 2 / "5", null + new Date()。当值被 if 语句包裹时也有可能发生,好比 if(value) {} 会将 value 转换为 boolean类型。ui

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

隐式类型转换是一把双刃剑,使用它虽然能够写更少的代码但有时候会出现难以被发现的bug。prototype

三种类型转换

咱们须要知道的第一个规则是:在 JS 中只有 3 种类型的转换code

  • to string
  • to boolean
  • to number

第二,类型转换的逻辑在原始类型和对象类型上是不一样的,可是他们都只会转换成上面 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')  // TypeError is thrown
复制代码

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 类型转换

和 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('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
复制代码
  1. NaN 不等于任何值,包括它本身
NaN === NaN  // false

if(value !== value) { console.log('the value is NaN') }
复制代码

object 类型转换

到这里咱们已经深刻了解了原始类型的转换,接下来咱们来看一下 object 类型的转换。

当涉及到对象的操做好比:[1] + [2,3],引擎首先会尝试将 object 类型转为原始类型,而后在将原始类型转为最终须要的类型,并且仍然只有 3 种类型的转换:number, string, boolean

最简单的状况是 boolean 类型的转换,任何非原始类型老是会转换成 true,不管对象或数组是否为空。

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

[[ToPrimitive]] 方法接受两个参数一个输入值和一个须要转换的类型(Numer or String)

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

一般上 [[ToPrimitive]] 算法以下:

  1. 若是输入的值已是原始类型,直接返回这个值。
  2. 输入的值调用 toString() 方法,若是结果是原始类型,则返回。
  3. 输入的值调用 valueOf() 方法,若是结果是原始类型,则返回。
  4. 若是上面 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[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()。

总结

查看原文

关注github每日一道面试题详解

相关文章
相关标签/搜索