首先我們要知道 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)