原文地址:落明的部落格
一、 前言
說實話,JavaScript 的型別轉換是個相當頭疼的問題,無論是對於初學者還是有經驗的老司機。它的難處並不在於概念多難理解,而是情況多且雜,看似相同的情況結果卻又出人意料,很少有人能保證時刻都能做出正確的判斷。
因此,這篇文章希望能講的足夠細緻和明確,讓大家能夠在日常使用中,能夠儘快的搞清楚型別轉換的順序和結果。
長文預警,建議先 mark, 分多次檢視。
二、型別轉換
1. 什麼叫型別轉換?
我們知道,JavaScript 中存在七種資料型別,在必要的時候,我們會對不同型別的值進行相互間的轉換。比如說,在進行條件判斷時,我們需要將其他型別的值轉為布林型別值,在使用 console.log()
列印內容時,需要將其轉為字串輸出。
2. JavaScript 中的型別轉換方式有哪些?
在 JavaScript 中,分為顯式型別轉換和隱式型別轉換。
其中,顯式型別轉換是我們為了功能需要,人為的將一種型別的值轉換為另一中型別,轉換的時機和結果都是我們預期的;而隱式型別轉換則是 JavaScript 在程式碼執行時,未經我們允許而進行的強制型別轉換。
三、顯式型別轉換
1. 其他型別轉換為字串( ToString )
值型別 | 例子 | 轉換後 | 呼叫法則 |
---|---|---|---|
number | 34 | '34' | String(34) |
boolean | true | 'true' | String(true) |
boolean | false | 'false' | String(false) |
undefiend | undefined | 'undefined' | String(undefined) |
null | null | 'null' | String(null) |
object | { a: 'fa' } | "[object Object]" | String({a: 'fa'}) |
object | new String(45) | '45' | String(new String(45)) |
object | [1, 2] | '1,2' | String([1,2]) |
object | function() {var d;} | "function() { var d; }" | String(function() {var d;}) |
其他型別的值轉換為字串,是通過呼叫原生函式String()
實現,但不同型別值的實現卻有明顯的差異。
對於基本型別的值,直接將其轉化為值的字串形式。而對於物件型別來說,便有些複雜了。
首先,每個物件內部都有一個 [[Class]]
屬性,我們通過Object.prototype.toString()
方法可以得到這個屬性的字串值。
對於物件(如{ a: 'ff'; }
)而言,除非自己定義 toString()
方法,否則,呼叫 String()
方法將返回和呼叫 Object.prototype.toString()
相同的值。(如 : "[object Object]"
)。
const obj_1 = {
b: 'lalala'
};
const obj_2 = {
toString() {
return "fasfa";
}
};
String(obj_1); // '[object Object]'
String(obj_2); // 'fasfa'
複製程式碼
其次, JavaScript 中,除了普通物件,還有以下幾種:
-
封裝物件
對於基本型別值
string
、number
、boolean
是沒有.length
及toString()
方法的,因此,JavaScript 提供了內建函式String()
、Number()
、Boolean()
,通過new
呼叫後會將基本型別值封裝為一個物件。如果想要取到封裝物件中的基本型別值,可以使用
valueOf()
方法。// string 型別 const a = 'i am string'; typeof a; // 'string' // string 封裝物件 const b = new String('i am sringObject'); typeof b; // 'object' // 拆封 b.valueOf(); // i am sringObject 複製程式碼
那對於封裝物件,
String()
會返回什麼值呢?事實上,封裝物件對於
toString()
方法進行了封裝,因此,對封裝物件呼叫String()
方法,將會返回封裝物件呼叫toString()
方法返回的值。const numObj = new Number(false); // Number {0} numObj.toString(); // '0' String(numObj); // '0' 複製程式碼
-
函式
對於函式來說,它也包裝了自己的
toString()
方法,因此,呼叫String()
方法時將返回函式字串化後的值。function bar() { console.log('bar'); } String(bar); // "function bar() {↵ console.log('bar');↵}" bar.toString(); // "function bar() {↵ console.log('bar');↵}" Object.prototype.toString.call(bar); // "[object Function]" 複製程式碼
從上例可以看到,
String()
與toString()
方法呼叫的是函式自己封裝的toSring()
,如果呼叫物件的toString()
方法,則函式與普通物件一樣,返回的是函式物件內部的[[Class]]
屬性。 -
陣列
陣列同函式一樣,同樣包裝了自己的
toString()
方法。此方法會將陣列中的每一項用逗號連線成一個字串。const arr = [1,4,6]; String(arr); // "1,4,6" arr.toString(); // "1,4,6" Object.prototype.toString.call(arr); // "[object Array]" 複製程式碼
2. 其他型別值轉為數字( ToNumber )
同樣,先感受一下什麼叫絕望?~~
值型別 | 例子 | 轉換後 | 呼叫法則 |
---|---|---|---|
string | '34' | 34 | Number('34') |
string | '' | 0 | Number('') |
string | '34fad' | NaN | Number('34fad') |
string | '34fad'、'34.24'、'34' | 34 | parseInt('34fad') |
string | '34fad'、'34' | 34 | parseFloat(值) |
string | '34.34' | 34.34 | parseFloat(值) |
boolean | true | 1 | Number(true) |
boolean | false | 0 | Number(false) |
undefiend | undefined | NaN | Number(undefined) |
null | null | 0 | Number(null) |
object | { a: 'fa' } | NaN | Number({a: 'fa'}) |
object | new String('fff') | NaN | Number(new String('fff')) |
object | [] | 0 | Number([]) |
object | [1, 2] | NaN | Number([1,2]) |
object | function() {var d;} | NaN | Number(function() {var d;}) |
看完一臉懵逼有沒有?!哈哈,不用害怕,乍看上去,大概會覺得異常混亂,其實稍加整理,不外乎以下幾種情況:
-
轉換後值為 NaN
數字與字串不同,並不是任何型別值都能轉為數字,因此,就會有 NaN,意思就是
not a number
。諸如包含非數字的字串、undefined、非空陣列,部分物件,都是我們知道無法轉化為一個數字的。
-
boolean 型別值
對於
true
和false
,true
轉換為 1,false
轉為 0。 -
帶有數字的字串
從上面我們可以看到,對於帶有數字的字串,有三種方法進行轉換,但規則不同。
-
Number()
方法會對字串整體進行轉換, 它會先判斷這個字串是否是個正確的數字字串,如果不是,則會返回NaN
。 -
parseInt()
方法則會對字串從左往右依次解析,直到遇到第一個非數字字元(包括小數點),如果最左邊的字元是非數字字元,則返回NaN
。 -
parseFloat()
方法解析順序同parseInt()
相同,不同的是它遇到第一個小數點時會正常往右繼續解析,直至遇到非數字字元停止。
其實嚴格來講,只有
Number()
方法是進行轉換操作,而後兩者屬於將字串解析 為數字,但為了講解方便,我將它們放在一起講述。 -
-
物件
對於物件而言,會先將物件轉為基本型別值( ToPrimitive ),再對基本型別值呼叫
Number()
方法。那如何將物件轉為基本型別值?首先會呼叫物件的
valueOf()
方法,如果沒有此方法或者此方法返回值不是基本型別值,則會呼叫toString()
方法,如果toString()
方法不存在或者返回值也不是基本型別值,會產生TypeError
錯誤。// 普通物件 const nomalObj = { a: '56' }; nomalObj .valueOf(); // { a: '56'} nomalObj.toString(); // "[object Object]" // Number(nomalObj) 相當於Number("[object Object]") Number("[object Object]"); // NaN Number(nomalObj); // NaN // valueOf() 返回基本型別值的物件 const obj_1 = { a: '56', valueOf: function() { return '23'; } }; obj_1.valueOf(); // '23' // Number(obj_1) 相當於 Number('23'); Number('23'); // 23 Number(obj_1); // 23 // valueOf() 返回非基本型別值,toString() 返回基本型別值的物件 const obj_2 = { a: '56', valueOf: function() { return {b: 34} }, toString: function() { return false; } }; obj_2.valueOf(); // {b: 34} obj_2.toString(); // false // Number(obj_2) 相當於 Number(false) Number(obj_2); // 0 Number(false); // 0 複製程式碼
上面的規則,適用於我們所說的所有物件,比如陣列,封裝物件和函式。
3. 其他型別轉換為 boolean 值( ToBoolean )
我們可以通過 Boolean()
方法 或!!
運算子來顯式的將一個值轉換為布林值。
相對來說,判斷一個值是 true
還是 false
則比較容易,我們只需要記住以下幾種值會轉換為 false
,而其他值,均為 true
。
- undefined
- null
- false
- +0、-0 和 NaN
- ""
當我們看到 []、{}
甚至是 "''"
時,也一定要記住,它們是真值。
Boolean(false); // fasle
Boolean([]); //true
Boolean({}); //true
Boolean(''); // false
Boolean('""'); // true
Boolean('false'); // true
複製程式碼
四、隱式強制型別轉換
除了進行強制型別轉換,JavaScript 會在執行時根據需要,自動進行型別的轉換,儘管這個特點飽受爭議,但不得不承認,某些情況下我們仍舊更喜歡使用某些隱式轉換規則。
一旦某些隱式的規則被接受並廣泛使用,從某種意義上來講,這些規則便同顯式轉換一樣。
1. 奇怪的 +
號
先看一一個最常見的例子:
const a = 5;
const b = '6';
console.log(a+a); // 10
console.log(a+b); // '56'
console.log(b+b); // '66'
複製程式碼
之所以會產生上例中的狀況,原因就在於在JavaScript 中,+
運算子既可以作用於number
型別值,也可以作用於 string
型別值。前者進行數字相加,後者則進行字串的拼接。
這就是為什麼5 + 5 = 10
而 '6' + '6' = '66'
。而當 +
號兩邊既有數字也有字串時,則會隱式的將數字轉換為字串,然後進行字串的拼接。
那兩邊沒有字串的情況呢?比如:
const a = [1,4];
const b = [2,3];
const c = 4;
console.log(a+c); // '1,44'
console.log(a+b); // '1,42,3'
複製程式碼
為什麼會這樣?原來只要+
的其中一個運算元可以通過某種方式(toPrimitive
)轉換為字串,就會進行字串的拼接。
我們知道,陣列[1,4]
可以通過 toString()
方法返回字串 '1,4'
,因此,[1,4] + 4
就相當於 '1,4' + 4
。
因為這個特性,我們在想將一個數字 a 轉換為字串時,便可以直接使用 a + ''
的形式即可。相對於顯式使用String(a)
,隱式轉換則更加簡潔。
從陣列的例子我們可以看到,除了數字,其他型別的值也可以通過 + ' '
的形式轉化為字串。
const a = {b: '2'}
console.log( a+ ''); // "[object Object]"
複製程式碼
但有一點需要注意,對於物件而言,使用 String()
方法是直接取這個物件 toString()
方法的返回值,而 + ' '
,則會對這個物件呼叫 valueOf
反法,然後對 valueOf
的返回值呼叫 toString()
,將其轉換為字串。
const a = {
toString: function() { return 45 },
valueOf: function() { return 4}
};
String(a); // '45'
a + ' '; // // '4'
複製程式碼
好在除非我們特意去改變一個物件的 valueOf
及 'toString()' 方法,通過上述兩個方式的轉換後的結果都是一致的。
2. 有用的 -
號
與 +
號不同的是,-
號只能用於數字的相減,對於它兩邊的運算元,都會經過隱式型別轉換轉為數字。
const a = '34';
const b = '4';
console.log(a - b); // 30
const c = 'dd';
console.log(a - c); // NaN
const d = [4];
console.log(a - d); // 30
複製程式碼
根據上例,我們可看到,如果 -
號兩邊是字串,則會將他們強制轉換為數字,如果 -
兩邊不是字串,則會先將其轉為字串,再將這個字串轉為數字。
3. 隱式轉換為布林值
將其他型別值隱式轉換為布林值是我們最常用的一種轉換。因為程式的編寫實質上就是不停的進行判斷。
在以下場景中,都是進行判斷,而只要傳入的值不是布林值,都會通過隱式型別轉換轉為布林值。
if (..) {}
語句中的條件判斷表示式。for ( .. ; .. ; ..)
語句中的條件判斷表示式。while (..)
和do ... while ( ..)
中的條件判斷表示式。? :
中的條件判斷表示式。- 邏輯或
||
或邏輯與&&
左邊的運算元。
在這些情況下,都將會進行其他型別值到布林型別值的隱式轉換,規則同顯式呼叫 Boolean()
。
五、最後
上面就是不同資料型別直接顯式或隱式的轉換規則,我們不需要將每一種情況都牢記在心,但有必要對他們進行充分的瞭解,這可以保證我們在實際寫程式碼時避免不少奇怪又難以排查的 bug 。