首先我們要知道 JS 是個弱型別的語言,弱型別和強型別的差異為編譯器或直譯器對型別檢查的寬容程度,弱型別在型別檢查上較為寬鬆,容許隱性型別轉換。強型別在型別檢查上較為嚴格,比較不容許隱性型別轉換。
由於 JS 容許隱性轉換,導致在型別轉換方面有相當多的情況和技巧,所以這篇就來做個整理瞭解各型別的轉換。
以下會介紹幾個型別的轉換,並且分成兩大類,顯性強制轉型 (explicit coercion),也就是有明說要轉成什麼型別,以及隱性強制轉型 (implicit coercion)。
顯性轉型常使用 Number 函式,以下示範各型別的轉換結果。
console.log(Number(undefined)) // NaN
console.log(Number(null)) // 0
console.log(Number(true)) // 1
console.log(Number(false)) // 0
console.log(Number('123')) // 123
console.log(Number('Hi')) // NaN
console.log(Number(function () {})); // NaN
console.log(Number({ name: 'Harry' })) // NaN
另外 parseInt() 也是很常用的一個函式,可以將字串轉為數字,不過 parseInt()
會解析到非數字的字元就停止,因此以下範例輸出 7,並且非字串的值無法轉換。
console.log(parseInt("7*6", 10)); // 7
console.log(parseInt(function() {})); // NaN
常用的技巧是用 +
、-
、*
、%
、/
等運算符號去進行轉型。
console.log(+'123') // 123
console.log(-'-123') // 123
console.log('123' * 1) // 123
// 運算中,將非 Number 的值轉為 Number
console.log(1 - true) // 0
console.log(1 - null) // 1
console.log(1 * undefined) // NaN
console.log(2 * ['5']) // 10
// + 號比較特別
console.log(123 + '123') // 123123,number + 字串,先轉字串再相連
console.log(123 + null) // 123,number + 原始型別,轉 number
console.log(123 + true) // 124,number + 原始型別,轉 number
console.log(123 + {}) // 123[object Object],number + 物件型別,都轉字串後相連
三個轉為字串的函式: String 函式、toString()
、JSON.stringify()
。
toString()
在後面章節有更詳細的說明,而 JSON.stringify()
能將絕大部分的型別轉成字串,但以下三個例外,都會變成 undefined。
console.log(JSON.stringify(undefined)); // undefined
console.log(JSON.stringify(function() {})); // undefined
console.log(JSON.stringify(Symbol())); // undefined
常使用的技巧是在要轉換的值前面加上 ''
:
const stringOrNum = '' + 2;
console.log(typeof stringOrNum); // string
const arrOrNum = '' + [1, 2];
console.log(typeof arrOrNum); // string
console.log(arrOrNum); // 1,2
其他型別的值轉成布林值還蠻常發生的,例如在 if
、for 迴圈
、while 迴圈
、三元運算子 條件 ? 值1 : 值2
、邏輯運算子的 ||
和 &&
等語法的條件判斷部分都會發生隱性轉型,轉換的結果可以看這個網站的表格。
Logical NOT (!),用在布林值上具有反轉的作用,例如將一個為 true 的值轉為 false,反之亦然。
雙驚嘆號(!!)則是"反轉再反轉",經過雙驚嘆號運算後原本的 falsy/truthy value 只會很單純出現 true 和 false 兩種,可以減少某些特別情況時的出錯,在開發上有時會用到。
console.log(!!'Hello World'); // true
console.log(!![]); // true
console.log(!!{}); // true
console.log(!!null); // false
console.log(!!undefined); // false
console.log(!!NaN); // false
console.log(!!0); // false
console.log(!!''); // false
==
和 ===
和 Object.is()
的比較這幾種比較也是在 JS 轉型時常討論到的東西,它們的差異在於 ==
做值的比較時會自動隱性轉型成比較的值相同型別,而 ===
不會。
==
的轉型規則valueOf()
或是 toString()
做轉型為原始型別。const a = '1,2,3';
const b = [1, 2, 3];
console.log(a === b); // false
console.log(a == b); // true,JS 底層調用了 toString() 將 b 陣列轉為字串
===
的比較原則不同型別,回傳 false。
例外: 浮點數 0 為正和負做比較,在解決某些數學問題時,+0 和 -0 是不同的,但在大部分情況下我們不需要考慮這種情境,因此嚴格比較將他們視為相同的。
===
vs Object.is()
兩個都是嚴格比較,但在比較特殊值不一樣:
Object.is(NaN, NaN); // true
Object.is(-0, +0); // false
NaN === NaN; // false
-0 === +0; // true
valueOf()
& toString()
最後這裡介紹兩個會觸發型別轉換的函式 valueOf()
和 toString()
。
這兩個函式都可以在物件型別上使用,當物件型別的值碰到需要型轉的時機時,JS 會透過 valueOf()
或 toString()
方法先自動型轉,變成原始型別物件後才比較。開發者也可以自訂 valueOf()
和 toString()
去控制型轉的結果。
valueOf()
通常會返回呼叫它的值本身,我們可以透過覆寫 valueOf()
去返回想要的值。
const a = 3;
const b = '3';
const c = true;
const d = { test: '123', example: 123 };
const e = function () {
console.log('example');
};
const f = ['test', 'example'];
console.log(a.valueOf()); // 3
console.log(b.valueOf()); // "3"
console.log(c.valueOf()); // true
console.log(d.valueOf()); // { test:'123', example:123 }
console.log(e.valueOf()); // function(){ console.log('example'); }
console.log(f.valueOf()); // ['test', 'example']
toString()
則可以將各型別的值轉為字串。
const a = 3;
const b = '3';
const c = true;
const d = { test: '123', example: 123 };
const e = function () {
console.log('example');
};
const f = ['test', 'example'];
console.log(a.toString()); // "3"
console.log(b.toString()); // "3"
console.log(c.toString()); // "true"
console.log(d.toString()); // "[object Object]"
console.log(e.toString());
// "function () {
// window.runnerWindow.proxyConsole.log('example');
// }"
console.log(f.toString()); // "test,example"
雖然兩個函式都可以型轉,但在和不同型別比較時,優先序會有所不同:
valueOf()
,若 valueOf()
返回的不是原始型別值,會再呼叫 toString()
嘗試獲得字串再做進一步處理。toString()
,若 toString()
返回的不是字串,會呼叫 valueOf()
嘗試獲取一個原始型別值並轉為字串。強制字符串轉換的情形可參考 MDN 文件。
讀者可以從以下範例更加了解轉換規則:
const obj = {
valueOf() {
return 100;
},
toString() {
return "Custom String";
},
};
console.log(Number(obj)); // 100 (調用 valueOf())
console.log(String(obj)); // "Custom String" (調用 toString())
console.log(obj + 200); // 300 (valueOf() 用於數值計算)
console.log(obj + "!!!"); // "Custom String!!!" (toString() 用於字串連接)
const obj = {
toString() {
return '2'
},
valueOf() {
return 1;
}
};
console.log(obj == '2'); // false,先調用 valueOf() 轉成 1,1 和 '2' 比較,'2' 轉為 2,結果不相等
console.log(obj == 1); // true
join()
方法把所有元素轉成字串。所以以下程式碼片段會印出 false,console.log(arr === true);
,為物件型別和原始型別比較,arr 會呼叫 toString()
轉成字串,'0' 和 true 會轉成 0 和 1 比較
var arr = [0];
if (arr) {
console.log(arr == true);
} else {
console.log(arr);
}
valueOf()
練習題此題取自 CodeWars Javascript Magic Function,題目要求寫一個 MagicFunction,它會接收無數個不固定的參數並相加,最後加總的會和一個數字做比對,並回傳 true/false。
MagicFunction(3) == 3; // should return true
MagicFunction(1, 2) == 3; // should return true
MagicFunction(1, 3)(2) == 6; // should return true
MagicFunction(1, 2)(3, 4, 5)(6)(7, 10) == 38; // should return true
valueOf()
做轉換。.map(Number)
將加總的值都轉為數字。function MagicFunction(...args) {
let result = 0;
let func = (...args) => {
result += args.filter((num) => !isNaN(num)).map(Number).reduce((sum, acc) => sum += acc, 0);
return func;
}
func.valueOf = () => result;
return func(...args);
}
另外在 StackOverflow 上也有個和 valueOf()
& toString()
使用相關的討論,有興趣的讀者也可以看一下:
Can (a== 1 && a ==2 && a==3) ever evaluate to true?
Object.prototype.toString()
這個方法能識別各種值的型別:
var number = 1; // [object Number]
var string = '123'; // [object String]
var boolean = true; // [object Boolean]
var und = undefined; // [object Undefined]
var nul = null; // [object Null]
var obj = {a: 1} // [object Object]
var array = [1, 2, 3]; // [object Array]
var date = new Date(); // [object Date]
var error = new Error(); // [object Error]
var reg = /a/g; // [object RegExp]
var func = function a(){}; // [object Function]
function checkType() {
for (var i = 0; i < arguments.length; i++) {
console.log(Object.prototype.toString.call(arguments[i]));
}
}
checkType(number, string, boolean, und, nul, obj, array, date, error, reg, func);
前端工程研究:關於 JavaScript 中物件的 valueOf 方法
Object to primitive conversion
add(2)(3)(4) and add(2,3,4) usage in same function.
你懂 JavaScript 嗎?#8 強制轉型(Coercion)