Javascript 裏的類型轉換是一個你永遠繞不開的話題,不管你是在面試中還是工作寫代碼,總會碰到這類問題和各種的坑,所以不學好這個那是不行滴。關於類型轉換我也看過不少的書和各種博客、帖子,也查過規範和做過各種測試,這裏就談談我的總結和理解吧。
首先,爲了掌握好類型轉換,我們要理解一個重要的抽象操作:ToPrimitive
ToPrimitive
爲什麼說這是個抽象操作呢?因爲這是 Javascript 內部纔會使用的操作,我們不會顯示調用到。當需要將對象轉換爲相應的基本類型值時,ToPrimitive
就會調用對象的內部方法 [[DefaultValue]]
來完成。
ToPrimitive
操作接收兩個參數,一個是 input 需要轉換的值,第二個是可選參數 hint 代表期望的轉換類型。並且在調用 [[DefaultValue]]
的時候 hint 會傳遞過去。
這裏我們首先只需要知道 [[DefaultValue]]
會調用 valueOf()
和 toString()
來完成基本類型值的轉換。但是請注意:valueOf()
和 toString()
的調用邏輯順序並不是固定的取決於 hint 參數,這個我們下面會講到。
基本規則
JavaScript 中的類型轉換總是返回基本類型值,如字符串、數字和布爾值,不會返回對象和函數。那麼這也對應了三種抽象操作:ToString
、ToNumber
和 ToBoolean
,下面就來逐一說明。
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
的調用邏輯:
- toPrimitive 調用 [[DefaultValue]] 並傳遞 hint,然後返回調用結果
-
[[DefaultValue]] 根據 hint 是 String 執行以下調用順序:
- 如果對象存在
toString()
並返回一個基本類型值,即返回這個值 - 如果
toString()
不存在或返回的不是一個基本類型值,就調用valueOf()
- 如果
valueOf()
存在並返回一個基本類型值,即返回這個值 - 如果
valueOf()
不存在或返回的不是一個基本類型值,則拋出 TypeError 異常
- 如果對象存在
那麼這裏就可以總結爲:對象在類型轉換爲字符串時, toString()
的調用順序在 valueOf()
之前,並且這兩個方法如果都沒有返回一個基本類型值,則拋出異常;如果返回了基本類型值 primValue,則返回 String(primValue)
基本類型值的 ToString 結果參看前面那個表格
我們來測試一下。先看下這節開頭的例子:
var a = {};
console.log(String(a));
字面量對象的原型是 Object.prototype
,Object.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