掌握 Javascript 類型轉換:從清晰的規則開始

Javascript 裏的類型轉換是一個你永遠繞不開的話題,不管你是在面試中還是工作寫代碼,總會碰到這類問題和各種的坑,所以不學好這個那是不行滴。關於類型轉換我也看過不少的書和各種博客、帖子,也查過規範和做過各種測試,這裏就談談我的總結和理解吧。

首先,爲了掌握好類型轉換,我們要理解一個重要的抽象操作:ToPrimitive

ToPrimitive

爲什麼說這是個抽象操作呢?因爲這是 Javascript 內部纔會使用的操作,我們不會顯示調用到。當需要將對象轉換爲相應的基本類型值時,ToPrimitive 就會調用對象的內部方法 [[DefaultValue]] 來完成。

ToPrimitive 操作接收兩個參數,一個是 input 需要轉換的值,第二個是可選參數 hint 代表期望的轉換類型。並且在調用 [[DefaultValue]] 的時候 hint 會傳遞過去。

這裏我們首先只需要知道 [[DefaultValue]] 會調用 valueOf()toString() 來完成基本類型值的轉換。但是請注意:valueOf()toString() 的調用邏輯順序並不是固定的取決於 hint 參數,這個我們下面會講到。

基本規則

JavaScript 中的類型轉換總是返回基本類型值,如字符串、數字和布爾值,不會返回對象和函數。那麼這也對應了三種抽象操作:ToStringToNumberToBoolean,下面就來逐一說明。

ToString

var a = {};
console.log(String(a));
// 顯式類型轉換,輸出爲:"[object Object]"

以上代碼我們通常稱爲顯示類型轉換,這裏面就包含 ToString 抽象操作,也就是把非字符串值轉換爲字符串的操作。

先來看看非對象的基本類型值的 ToString 轉換規則:

輸入類型 輸出結果
Undefined "undefined"
Null "null"
Boolean 輸入 true,輸出 "true"
輸入 false,輸出 "false"
Number 輸入 Nan,輸出 "NaN"
輸入 +0-0,輸出 "0"
如果輸入小於 0 的數字,如:-2,輸出將包括負號:"-2"
輸入 Infinity,輸出 "Infinity"

接着我們重點來看一下輸入是對象的轉換規則。

這個時候 ToPrimitive 就出場了,並且 hint 參數是 String。還記得 ToPrimitive 內部是調用的[[DefaultValue]]嗎,並且這個時候 hint 是 String 。下面來看下這種情況下 ToPrimitive 的調用邏輯:

  1. toPrimitive 調用 [[DefaultValue]] 並傳遞 hint,然後返回調用結果
  2. [[DefaultValue]] 根據 hint 是 String 執行以下調用順序:

    • 如果對象存在 toString() 並返回一個基本類型值,即返回這個值
    • 如果 toString() 不存在或返回的不是一個基本類型值,就調用 valueOf()
    • 如果 valueOf() 存在並返回一個基本類型值,即返回這個值
    • 如果 valueOf() 不存在或返回的不是一個基本類型值,則拋出 TypeError 異常

那麼這裏就可以總結爲:對象在類型轉換爲字符串時, toString() 的調用順序在 valueOf() 之前,並且這兩個方法如果都沒有返回一個基本類型值,則拋出異常;如果返回了基本類型值 primValue,則返回 String(primValue)

基本類型值的 ToString 結果參看前面那個表格

我們來測試一下。先看下這節開頭的例子:

var a = {};
console.log(String(a));

字面量對象的原型是 Object.prototypeObject.prototype.toString() 返回內部屬性 [[Class]] 的值,那麼結果就是 [object Object]。OK 沒有問題

然後測試一下 ToPrimitive 的調用邏輯。來看下這段代碼:

var a = Object.create(null);

上面的意思是創建一個沒有原型的對象(沒有原型就沒有繼承的 toString()valueOf() 了)。接下來:

console.log(String(a));
// Uncaught TypeError: Cannot convert object to primitive value

這裏因爲沒有 toString()valueOf() 所以就拋出 TypeError 異常了。OK,跟前面的總結一致。

下面來測試一下 toString()valueOf() 的調用順序邏輯,上代碼:

a.toString = function() {
  return 'hello';
};

a.valueOf = function() {
  return true;
};

console.log(String(a)); // "hello"

我們加入了 toString()valueOf() ,並且跟前面的總結一致,確實是 toString() 先返回結果。接着做一下變化:

a.toString = function() {
  return {};
};
// 或是直接去掉這個方法,a.toString = undefined;

a.valueOf = function() {
  return true;
};

console.log(String(a)); // "true"

當 toString() 返回的不是一個基本類型值或不存在 toString() 時,返回 valueOf() 的結果,並且遵循基本類型值的 ToString 轉換結果。OK,驗證沒有問題 其他的情況也可以根據前面的總結邏輯自己驗證下。

在《Javascript 高級程序設計(第 3 版)》和《你不知道的 Javascript(中卷)》上均未提到類型轉換到字符串會與 valueOf() 有關係

ToNumber

首先照例先來看下非對象的基本類型值的 ToNumber 轉換規則:

輸入類型 輸出結果
Undefined NaN
Null 0
Boolean 輸入 true,輸出 1
輸入 false,輸出 0
Number 輸入 "",輸出 0
輸入 "Infinity",輸出 Infinity
輸入有效數字的字符串(包括二、八和十六進制),輸出數字的十進制數值
如果輸入包含非數字格式的字符串,輸出 NaN
字符串轉數字上面只說了一些常用的情況,更多細節請看 這裏

然後來看看對象 ToNumber 的情況。這裏與對象轉字符串的情況類似,也會調用 ToPrimitive 來轉換(hint 是 Number)。但細節與 ToString 稍有不同,這裏直接給出結論:

對象在類型轉換爲數字時, valueOf() 的調用順序在 toString() 之前,並且這兩個方法如果都沒有返回一個基本類型值,則拋出異常;如果返回了基本類型值 primValue,則返回 Number(primValue)

這裏驗證了 ToPrimitive 裏面說到的,[[DefaultValue]] 會根據 hint 參數決定 toString() 和 valueOf() 的調用順序

接着來用代碼說話:

var a = Object.create(null);
console.log(Number(a));
// Uncaught TypeError: Cannot convert object to primitive value

這裏因爲沒有 toString()valueOf() 所以就拋出 TypeError 異常了。OK,跟前面的總結一致。

我們先加入 valueOf() 方法:

a.valueOf = function() {
  return 123;
}
console.log(Number(a)); // 123

valueOf() 返回了數字 123,所以輸出沒問題。再修改一下:

a.valueOf = function() {
  return true;
}
console.log(Number(a)); // 1

valueOf() 返回了 true,這也是一個基本類型,然後根據基本類型轉換規則 true 轉換爲 1,也是對的。

再來:

a.valueOf = function() {
  return NaN;
}
console.log(Number(a)); // NaN

NaN 是一個特殊的數值,所以也是基本類型。OK,也是對的。

這裏的結果說明了《Javascript 高級程序設計(第 3 版)》關於對象轉換爲數字的解釋是有錯誤的,書上是這麼說的:如果轉換的結果是 NaN,則調用對象的 toString() 方法

再來驗證一下 toString() 的調用順序:

a.valueOf = function() {
  return {};
}
a.toString = function() {
  return '123';
}
console.log(Number(a)); // 123

因爲 valueOf() 返回了對象非基本類型值,轉而執行 toString(),返回的 "123" 根據字符串轉換數字的規則就是 123,對於 valueOf() 和 toString() 的執行順序驗證也是 OK 的。

ToBoolean

最後我們來看看轉換爲布爾值。這個比較簡單,一個列表可以全部歸納了:

輸入類型 輸出結果
Undefined false
Null false
Number 輸入 +0,-0,NaN,輸出 false
輸入其他數字,輸出 true
String 輸入 length 爲 0 的字符串(如:""),輸出 false
輸入其他字符串,輸出 true
Object 輸入任何對象類型,輸出 true

ToBoolean 轉換規則比較簡單,只有一個需要注意的地方,那就是封箱操作:

var a = new Boolean(false);
console.log(Boolean(a));
// 輸出是 true 不是 false 喔

new Boolean(false) 返回的是對象不是布爾值,所以最好避免進行類似的操作。

總結

以上即是我總結的 JS 類型轉換基本規則,當你明顯感知類型轉換即將發生時可以拿上面的規則去套(也就是我們通常說的顯式類型轉換,以上轉換規則面試時特別有用喔)。

既然規則有了,下一篇準備聊一下隱式類型轉換,有了這篇的基礎掌握隱式轉換會容易很多。

歡迎 star 和關注我的 JS 博客:小聲比比 Javascript

參考資料

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章