看看這幾行程式碼的輸出結果:
console.log(8 * null) // 0
console.log("5" - 1) // 4
console.log("5" + 1) // "51"
console.log("five" * 2) // NaN
console.log(false == 0) // true為什麼 "5" - 1 等於 4,但 "5" + 1 卻等於 "51"?
為什麼 8 * null 不報錯,反而輸出 0?
要搞懂這些奇怪的結果,我們要先從「型別轉換」說起。
什麼是型別轉換?
每一個值在 JavaScript 裡都有它的型別(type),用來告訴程式「這個值是什麼種類的資料」。
JavaScript 常見的型別有以下幾種:
但在實際寫程式的時候,你常常會遇到「型別不匹配」的情況。
例如網頁上有一個輸入框,使用者在裡面打了 5。
你可能以為程式拿到的是數字 5,但其實輸入框回傳的值一律是字串,所以程式拿到的是 "5"。
這時候如果你想拿它來做數學運算,就需要先把字串 "5" 轉換成數字 5。
這就是型別轉換:把一個值從某個型別,轉換成另一個型別。
最直接的方式是主動寫明要轉換成什麼型別,你決定什麼時候轉、轉成什麼,所以結果完全在你的掌控之中。
JavaScript 提供了幾個內建的寫法,可以直接指定要轉換的型別,你現在不需要深究這些寫法的原理,只要知道它們的用途就好:
Number("5") // 把字串 "5" 轉成數字 5
String(123) // 把數字 123 轉成字串 "123"
Boolean(0) // 把數字 0 轉成布林值 false什麼是自動型別轉換?
上面的主動轉換很直觀,因為你明確寫出了要轉換成什麼型別。
但如果每次運算都要手動轉型,對新手來說會很繁瑣。
JavaScript 當初的設計目標,是讓沒有程式背景的人也能快速上手。
為了達到這個目標,它選擇成為一個「寬容」的語言——當程式遇到型別不符合的情況,不要直接報錯讓整個網頁壞掉,而是盡量猜測你的意圖,自動轉換後繼續執行。
這個行為就叫做自動型別轉換(type coercion)。
以開頭的 "5" - 1 為例,你沒有寫任何轉換,但 JavaScript 猜測你想做數字相減,所以自動把字串 "5" 轉成數字 5,然後繼續執行,得出 4。
這個設計的出發點是好的,但問題在於,JavaScript 猜測的結果不一定是你預期的。
讓我們逐一拆解開頭的例子,搞清楚每一行發生了什麼事。
8 * null → 0
null 被轉換成數字 0,所以 8 * 0 = 0。
"5" - 1 → 4
字串 "5" 被轉換成數字 5,所以 5 - 1 = 4。
"5" + 1 → "51"
這裡很容易混淆:+ 運算子有兩個用途——數字相加和字串串接。
當其中一側是字串,+ 會優先選擇串接字串,所以數字 1 被轉換成字串 "1",結果變成 "51"。
"five" * 2 → NaN
"five" 沒辦法被轉換成有意義的數字,所以 JavaScript 會給它一個特殊值:NaN(Not a Number)。
有一點要特別注意:對 NaN 做任何算數運算,結果都還是 NaN。
console.log(NaN + 1) // NaN
console.log(NaN * 100) // NaN如果你的程式輸出一直是 NaN,很可能就是在某個地方發生了非預期的型別轉換。
== 比較運算子的轉換邏輯
前面介紹的自動型別轉換,都發生在算術運算(+、-、*)。
但其實比較兩個值的時候,也會發生自動轉換。
== 是 JavaScript 的比較運算子,用來判斷兩邊的東西是否相等,結果會是 true 或 false。
console.log(5 == 5) // true
console.log("hello" == "hello") // true
console.log(5 == 3) // false當你用 == 比較相同型別的兩個值,邏輯很直覺:兩個值一樣就是 true,不一樣就是 false(NaN 除外,它連自己都不等於自己)。
麻煩的是當型別不同時,== 會先嘗試把其中一個值轉換成另一個的型別,再做比較。
console.log(false == 0) // true
// false 被轉成 0,實際比較的是:
console.log(0 == 0) // true
console.log("" == false) // true
// "" 被轉成 0,false 也被轉成 0,實際比較的是:
console.log(0 == 0) // true這就是 == 容易讓人混淆的地方——你可能不會預期空字串和 false 是「相等」的。
null 和 undefined 是特例
前面提到 == 會自動轉換型別再比較,但 null 和 undefined 是例外。
null 和 undefined 用 == 比較時,不管是 null == undefined、null == null、還是 undefined == undefined,結果都是 true:
console.log(null == undefined) // true
console.log(undefined == null) // true
console.log(null == null) // true
console.log(undefined == undefined) // true跟其他任何東西比,不管是 0、""、false,結果都是 false,== 不會嘗試轉換它們:
console.log(null == 0) // false
console.log(null == "") // false
console.log(undefined == false) // false為什麼要這樣設計?因為 null 和 undefined 都代表「沒有值」,只是原因不同——null 是你主動設為空值,undefined 是根本還沒賦值。
在實際開發中,你通常不在乎「為什麼沒有值」,只在乎「有沒有值」,所以讓它們用 == 比較時相等,可以一次處理兩種情況。
那要怎麼利用這個特性?來看一個常見的情境。
假設你想確認變數 name 有值(不是 null 也不是 undefined),最直覺的寫法是分開檢查:
if (name !== null && name !== undefined) {
console.log("Hello, " + name)
}但因為 null == undefined 是 true,它的相反 null != undefined 就是 false。
這代表 != 會把 null 和 undefined 當成同一類,所以你可以簡化成一行:
if (name != null) {
console.log("Hello, " + name)
}當 name 是 null 或 undefined 時,name != null 都會是 false,一行就能擋掉兩種情況。
用 === 避免型別轉換的意外
如果你不想讓 JavaScript 偷偷轉換型別,要改用 ===(嚴格相等)和 !==(嚴格不相等)。
這兩個運算子會同時比較值和型別,只要型別不同就直接回傳 false,不做任何轉換。
console.log("" == false) // true(發生型別轉換)
console.log("" === false) // false(型別不同,直接 false)小結
這篇文章介紹了 JavaScript 的型別轉換機制,整理三個重點帶走:
- 型別轉換是把值從一個型別轉成另一個型別,可以主動呼叫,也可以自動發生。
- 自動型別轉換(type coercion) 是 JavaScript 在背後偷偷轉的行為,通常不是你預期的結果,遇到奇怪的輸出時要往這個方向排查。
- 預設使用
===和!==,除非你確定比較兩側的型別相同,否則避免使用==和!=,可以省去很多除錯的麻煩。