JavaScript隱式型別轉換

onejustone404發表於2019-02-16

JavaScript隱式型別轉換

基本資料型別

ECMAScript 一共定義了七種 build-in types,其中六種為 Primitive ValueNullUndefinedStringNumberBooleanSymbol。而最後一種 Object build-in type 與通常意義上的 JavaScript 中 Object 並不一樣,總的來說,只要不屬於 Primitive Value 的值,就屬於 Object 型別,比如陣列、物件、日期、正則、函式。

裝箱轉換

每一種基本型別 number, string, boolean, symbolObject(build-in type) 中都有對應的類。所謂裝箱轉換,正是把基本型別轉換為對應的物件,他是型別轉換中一種相當重要的種類。

JavaScript 語言設計上試圖模糊物件和基本型別之間的關係,比如,我們可以直接在基本型別上使用物件的方法:

console.log(`abc`.charAt()); // a

甚至我們在原型上新增方法,都可以應用於基本型別。

實際上是 . 運算子提供了裝箱操作,它會根據基礎型別構造一個臨時物件,使得我們能在基礎型別上呼叫對應物件的方法。

拆箱轉換

在 JavaScript 標準中,規定了 ToPrimitive 函式,它是物件型別到基本型別的轉換(即,拆箱轉換)。

物件到 String 和 Number 的轉換都遵循“先拆箱再轉換”的規則。通過拆箱轉換,把物件變成基本型別,再從基本型別轉換為對應的 String 或者 Number。

拆箱轉換會嘗試呼叫 valueOf 和 toString 來獲得拆箱後的基本型別。如果 valueOf 和 toString 都不存在,或者沒有返回基本型別,則會產生型別錯誤 TypeError。

ToPrimitive

ToPrimitive 用於將 Object 轉為 Primitive Value

對於我們平常遇到的 Object,其處理邏輯是:

  • 呼叫 Object.valueOf,如果結果是 Primitive Value,則返回;
  • 呼叫 Object.toString,如果結果是 Primitive Value,則返回;
  • 都不是,返回 TypeError

普通物件和陣列的這兩個方法返回的結果如下:

var a = [12]
var b = {a: 123}

// [12]
a.valueOf()

// `12`
a.toString()

// {a: 123}
b.valueOf()

// `[object Object]`
b.toString()

如上,兩者的 valueOf 返回的都不是 Primitive Value (返回了自身,還是 Object 型別)。那麼,根據規範,兩者呼叫 ToPrimitive 返回的將是一個 字串

顯示型別轉換

ToBoolean

這個方法用於將不是 Boolean 型別的值轉換為 Boolean 型別。

  • Undefined 返回 false
  • Null 返回 false
  • 所有 Object 型別都會被轉換為 true;
  • Number 型別中,0,NaN 會被轉換為 false,其它都為 true
  • 只有空字串為 false,其它都為 true

ToNumber

其它型別轉換為 Number 型別。

  • Undefined 返回 NaN
  • Null 返回 0
  • Boolean 型別,true 為 1; false 為 0
  • String 型別,如果滿足數字語義則轉為數字,否則轉換為 NaN
  • Object 型別,先轉換為 Primitive Value 再遞迴呼叫自身 ToNumber 來轉換。
// `56` ==> 56
Number([56])

// `,56` ==> NaN
Number([,56])

// `55,56` ==> NaN
Number([55, 56])

ToString

  • Number 返回 對應數值字串
  • Boolean 返回字串 “true” 或者 “false”
  • Undefined 返回 “undefined”
  • Null 返回 “null”

隱式型別轉換

瞭解了上面的知識,可以開始進入我們的正題了,在 JavaScript 中可以觸發隱式型別轉換的操作有:

  • 四則運算: +, -, *, /
  • 比較運算子: ==, <, >, >=, <=
  • 判斷語句: if, while
  • Native呼叫: console, alet 輸入時會自動轉換成 String 型別
  • 邏輯非 !,將直接呼叫 ToBoolean 方法,然後取反返回。

比較運算子

非嚴格比較(==)

  • 如果 Type 相同,等價於 A === B
  • 特別的, undefined == null
  • String == Number,則把 String 轉換成 Number
  • Boolean 值的,將 Boolean 轉換成 Number
  • Object String/Number/Symbol,將 Object 轉換成 Primitive Value
  • 否則,返回 false
// `12` ==> 12;
// 返回 true
12 == `12`

// 轉 boolean: [] == 0
// 轉 object: `` == 0
// 轉 string: 0 == 0
// 返回 true
[] == false

// 轉 object: `45` == 45
// 轉 string: 45 == 45
// 返回 true
[45] == 45

// 單目: {} == false
// 轉 boolean: {} == 0
// 轉 object: `[object Object]` == 0
// 轉 string: NaN == 0
// 返回 false
{} == !{}

// 單目:[] == fasle
// 轉 boolean: [] == 0
// 轉 array: "" == 0
// 轉 string: 0 == 0
// 返回 true
[] == ![]

[] == []
[] == 0

嚴格比較 (===)

  • 型別不同,直接返回 false
  • Number 型別判斷:有 NaN 就 false;
  • 特別的 +0 === -0;
  • 最後呼叫 SameValueNonNumber

另外 != 和 !== 則是指出了 A != B 與 !(A == B) 是完全等價的。在判斷 !=/!== 時,其實就是在判斷 ==/===.

不等關係

  • 兩邊運算元呼叫 ToPrimitive 轉換為 Primitive Value
  • 由於 Primitive Value 出來有 StringNumber 兩種結果,分別有不同的比較規則;

    1. 如果兩邊的值都是 String,則 按 code unit 比較,
    2. 如果一邊是 Number,則將另一邊也轉換為 Number;注意 Number 需要處理 +0/-0/NaN/Infinity 等情況
// 注意轉換後為 `45` < `46`
// 按字串規則比較 最終比較的是 `5`.charCodeAt() < `6`.charCodeAt() => 53 < 54
// 返回 true
[45] < [46]

// 同理 [10] < [9] 最後進行的是 `10` < `9` 的比較,是字串之間的筆記,不會轉換為數字間的比較,
// 其實最終比較的是 `1`.charCodeAt() < `9`.charCodeAt() => 49 < 57.
[10] < [9]

練習題

// 每個表示式是 true 還是 false 呢?為啥呢?

// 初階
!{}
12 == `12`
`false` == false
null == undefined

// 高階
[] == []
[] == false
[] === false
[45] == 45

// 終階
[45] < [46] ?
[10] < [9] ?
{} == !{}
{} != {}
-0 === +0
NaN === NaN
NaN != NaN

// 轉換條件 轉換後型別 結果
[]+[] // String “”
[1,2]+[3,4] // String “1,23,4”
[]+{} // String “[object Object]”
[1,2] + {a:1} // String “1,2[object Object]”
{}+[] // Number 0
{}+[1] //Number 1
{a:1}+[1,2] // Number NaN
{a:1}+{b:2} // Chrome - String “[object Object][object Object]” (背後實現eval)
{a:1}+{b:2} // Firefox - Number NaN
true+true // Number 2
1+{a:1} // String “1[object Object]”

reference

JavaScript 中的隱式型別轉換的規範

JavaScript 運算子規則與隱式型別轉換詳解

JavaScript型別:關於型別,有哪些你不知道的細節?

深入淺出弱型別JS的隱式轉換

JavaScript字串間的比較

ecma-sec-relational-operators

相關文章