JavaScript隱式型別轉換
基本資料型別
ECMAScript 一共定義了七種 build-in types
,其中六種為 Primitive Value
,Null
, Undefined
,String
, Number
, Boolean
, Symbol
。而最後一種 Object
build-in type 與通常意義上的 JavaScript 中 Object 並不一樣,總的來說,只要不屬於 Primitive Value 的值,就屬於 Object
型別,比如陣列、物件、日期、正則、函式。
裝箱轉換
每一種基本型別 number
, string
, boolean
, symbol
在 Object(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
出來有String
和Number
兩種結果,分別有不同的比較規則;- 如果兩邊的值都是 String,則 按 code unit 比較,
- 如果一邊是 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]”