JavaScript 不論遇到什麼奇形怪狀的資料運算,都會竭盡所能的轉換,好讓程式執行下去;反觀其他程式語言只要型別沒轉好,絕對報錯報到飽。
這麼貼心的型別轉換也是兩面刃,當特殊情況發生報錯的時候讓絕對 debug 半天滿頭問號,馬上來認識 JavaScript 的轉型吧!
強制轉型(coercion)
強制轉型分為兩種
顯性轉型
(explicit coercion)隱性轉型
(implicit coercion)
💎 顯性轉型(explicit coercion)
顯性轉型是指在程式碼直接撰寫
型別轉換。
🔸 轉數字
parseInt()
:- 轉整數。
- 小數點以後無條件捨去。
- 轉換方式:由左至右,遇到無法轉成數字的時候停止,回傳前面的數字。
console.log(parseInt('1.23')); // 1 console.log(parseInt('1.23a')); // 1 console.log(parseInt('a1.23')); // NaN
parseFloat()
:- 轉浮點數(包含小數點以後)。
- 轉換方式與 parseInt() 相同。
console.log(parseFloat('1.23')); // 1.23
Number()
:- 可以轉整數和浮點數。
- 數字範圍在正負 2的53次方 -1 間。
- 轉換方式:整筆字串都可以轉成數字才轉換,否則得到 NaN。
console.log(Number('-1.23')); // -1.23 console.log(Number('-1.23a')); // NaN
BigInt()
:- 轉整數
- 範圍可以超過2的53次方(表示方式會在數字後面加上
n
) - 轉換方式:非整數時直接報錯,不會得到 NaN。
- 需注意瀏覽器支援度。
console.log(BigInt("9007199254740991")); // 9007199254740991n console.log(typeof 1n); // bigint console.log(BigInt("900a")); // Cannot convert 900a to a BigInt
算數運算子:
- 前方加上
+
:容易和++
混淆。 - 前方加上
-
:內容若是負數,會被轉成正數。 - 後方加上
- 0
:較穩定寫法。console.log(+'-1'); // -1 console.log(++'-1'); // Invalid left-hand side expression in prefix operation console.log(-'-1'); // 1 console.log('-1' - 0); // -1
- 前方加上
🔸 轉字串
toString()
:無法轉換 null 和 undefined、提供數字進位制轉換。console.log((3).toString()); // 3 console.log(undefined.toString()); // Cannot read properties of undefined console.log((3).toString(2)); // 11
String()
:可以轉換 null 和 undefined、無法提供數字進位制轉換。console.log(String(3)); // 3 console.log(String(undefined)); // undefined
- 算數運算子:加上空字串
+ ''
。console.log(typeof (3 + '')); // string
🔸 轉布林值
- 布林值只有兩種結果: true 或 false,轉換後會對應 true 的值稱為 truthy,對應 false 的則是 falsy,屬於 falsy 的有以下幾種,其他的都是 truthy:
false
0
-0
0n
(BigInt)“”
(空字串,包含 ``, ‘’)null
undefined
NaN
document.all
(正常情況下不會用到)
- 轉換方式:
- Boolean()
console.log(Boolean([])); // true
- 邏輯運算子:前方加上雙驚嘆號
!!
,單驚嘆號!
會是相反結果。console.log(!true); // false console.log(!!{}); // true
- Boolean()
💎 隱性轉型(implicit coercion)
隱性轉型出現在使用運算子時,這在其他程式語言通常是不允許的(需要相同型別才可以運算),JavaScript 則會自動轉型讓程式繼續執行,也因為規則繁雜,容易疏忽出錯。
🔸 認識核心的 toPrimitive
JavaScript 在執行運算時會使用 toPrimitive
將運算元轉換成 基本型別(Primitives)
才能繼續運算,toPrimitive
帶有一個 hint
(提示),用來決定要轉成什麼型別,下面的例子會逐步說明。
🔸 +
算數運算子
+
運算中只要有一個屬於字串型別,就會轉為 string,最高優先;一般情況都是轉成 number。// 有字串 console.log('' + 1 + null + true); // 1nulltrue // 無字串 console.log(1 + null + true); // 2
🔸 其他算數運算子
-
、*
、/
、%
運算都會轉成 number。console.log('6' - undefined); // NaN console.log(9 * [9]); // 81 console.log('3' / {valueOf: function(){ return 3}}); // 1 console.log(6 % true); // 0
🔸 object 的轉型機制
toPrimitive
執行物件類型的型別轉換時會呼叫valueOf
或toString
方法來取得原始型別的回傳值(預設先轉數字,不能轉數字再轉字串),以下範例先宣告 4 個物件來,並覆蓋預設的回傳值:// 覆蓋 valueOf 回傳值、toString 回傳值 let a = { valueOf: function() { return 1; }, toString: function() { return 2; } }; // 只覆蓋 toString 的回傳值 let b = { toString: function() { return 3; } }; // 覆蓋 valueOf 回傳值、toString 回傳值,但是回傳物件 let c = { valueOf: function() { return []; }, toString: function() { return {}; } }; // 空物件,使用預設值 let d = {};
先轉數字,不能轉數字再轉字串
// 先取 valueOf 成功,後方運算元為字串(最高優先),故轉字串運算 console.log(a + ''); // "1" // 先取 valueOf 失敗,改取 toString,後方運算元為數字,故轉數字運算 console.log(b + 0); // 3 // 取不到原始型別,就會報錯 console.log(c + 0); // Cannot convert object to primitive value
指定為字串時,呼叫
toString()
方法,// 指定取 toString String(a); // "2" // 取不到原始型別,一樣報錯 String(c); // Cannot convert object to primitive value // 返回 toString() 預設的回傳值 String(d); // "[object Object]"
物件類型的
valueOf
是物件本身,物件本身並不是原始型別
,所以上面String(d)
的範例可以看到,在沒有修改回傳值的情況下,會取得toString()
的回傳值。陣列
也是物件,所以valueOf()
同樣是自己本身(非原始型別),會回傳toString()
的字串。// 空陣列轉字串 "",空物件轉字串 "[object Object]" console.log([] + {}); // "[object Object]" // 陣列轉字串 "1,2,3",後方數字配合轉字串 console.log([1,2,3] + 2 + 1); // "1,2,321"
{} + x
這種類型牽涉到兩個觀念:{}
在運算元前方時會被視為區塊語句
,而不是物件,所以實際執行的只有後面的+ x
+ x
是前一段落提到的顯性轉型
轉數字的方法,所以後方的運算元轉為 number 型別。// 強制轉型 console.log(+{}); // NaN // 表面上是物件加陣列,實際上是後方的陣列強制轉型為數字 console.log({} + []); // 0 // 上方程式實際執行如下面兩行: {} +[]; // 宣告一個物件變數 x 來存放空物件,才能被當成物件來計算 let x = {}; console.log(x + []); // "[object Object]"
{} + {}
:
這又是一個特別狀況所以被獨立出來,因為不同的瀏覽器會有不同的執行結果:- NaN - 第一個 {} 被視為區塊
- "[object Object][object Object]" - 第一個 {} 被視為物件
🔸 邏輯運算子
邏輯運算中的運算元都會被轉為 boolean,差別在於其運算後回傳的結果。
- || (or) 會回傳第一個結果為 true 的運算元,若無,則是最後一個。
console.log(0 || false || null || undefined); // undefined console.log(0 || false || null || {} || undefined); // {}
- && (and) 若運算結果為 true,會回傳最後一個運算元,若運算結果為 false,回傳第一個結果為 false 的運算元。
## 💎 結語console.log(true && {} && -2 && ['a']); // ['a'] console.log(1 && 0 && true & false); // 0
關於轉型的細節實在太多,族繁不及備載,一些極端的範例在實際撰寫時並不會用到,不求成為行走的 MDN,但求踩坑的時候能順利 debug 就好!
參考文章
MDN-BigInt
sunnyhuang-何謂強制轉型(coercion)以及如何作到轉換型別
MDN-Number.prototype.toString()
淺談JS中String()與.toString()的區別 - 程式前沿
MDN-Symbol.toPrimitive。
Eddy 思考與學習-JS中的 {} + {} 與 {} + [] 的結果是什麼?