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每日一道面試題詳解

相關文章
相關標籤/搜索