最近在重讀《JavaScript高階程式設計》,讀到資料型別這一節,想到了JavaScript裡令程式設計師抓狂的一個問題——型別轉換。因為JS是一門弱型別的語言,在執行時系統會根據需要進行型別轉換,而型別轉換的規則又令人迷惑。於是寫篇博文嘗試自己總結來加深印象。
基本概念
首先,我們知道JavaScript裡有7種資料型別:
boolean
number
null
string
symbol
undefined
object
object稱為引用型別,其餘的資料型別統稱為“基本型別”。
顯示強制型別轉換
轉換為Boolean型別
布林值的強制型別轉換使用的方法主要有: Boolean() 。其實布林值的轉換規則很好記住,因為轉換後為false的值有限,只有下列幾種:
null
undefined
false
+0
-0
NaN
""
轉換為Number型別
數字的強制型別轉換使用的方法主要有:Number() parseInt() parseFloat() 和一元操作符。
Number() 函式的轉換規則如下:
- 如果是 Boolean 值, true 和 false 將分別被轉換為 1 和 0。
- 如果是數字值,只是簡單的傳入和返回。
- 如果是 null 值,返回 0。
- 如果是 undefined ,返回 NaN 。
- 如果是字串,遵循下列規則:
- 如果字串中只包含數字(包括前面帶正號或負號的情況),則將其轉換為十進位制數值,即 “1”會變成 1, “123” 會變成 123,而 “011” 會變成 11(注意:前導的零被忽略了);
- 如果字串中包含有效的浮點格式,如 “1.1” ,則將其轉換為對應的浮點數值(同樣,也會忽略前導零);
- 如果字串中包含有效的十六進位制格式,例如 “0xf” ,則將其轉換為相同大小的十進位制整數值;
- 如果字串是空的(不包含任何字元),則將其轉換為 0;
- 如果字串中包含除上述格式之外的字元,則將其轉換為 NaN 。
細說parseInt
parseInt() 只處理字串型別,如果接受的引數不是字串型別,會先將其轉換為字串型別(稍後介紹字串的強制轉換)
parseInt() 函式在轉換字串時,更多的是看其是否符合數值模式。它會忽略字串前面的空格,直至找到第一個非空格字元。如果第一個字元不是數字字元或者負號,parseInt() 就會返回 NaN 。如果第一個字元是數字字元,parseInt() 會繼續解析第二個字元,直到解析完所有後續字元或者遇到了一個非數字字元。例:
var num1 = parseInt("123iuuan");
// 123(字母不是數字字元,被忽略)var num2 = parseInt("");
// NaNvar num3 = parseInt("0xA");
// 10(十六進位制數)var num4 = parseInt(22.5);
// 22)(小數點並不是有效的數字字元)複製程式碼
parseInt() 函式可以接收兩個引數,第一個引數是需轉換字串,第二個引數是轉換是使用的基數(即多少進位制),例如:
var num1 = parseInt("AF", 16);
//175var num2 = parseInt("AF");
//NaN複製程式碼
當指定基數時,字串可以被成功轉換,而第二個轉換時,按之前說的轉換規則,第一個字元不是數字字元,所以直接返回了NaN。
對於同一個字串,如果指定的基數不同,轉換的結果也會受影響,例如:
var num1 = parseInt("10", 2);
//2 (按二進位制解析)var num2 = parseInt("10", 8);
//8 (按八進位制解析)var num3 = parseInt("10", 10);
//10 (按十進位制解析)var num4 = parseInt("10", 16);
//16 (按十六進位制解析)複製程式碼
綜上所述,當不指定基數時,parseInt() 會自行決定如何解析輸入的字串,所以為了避免錯誤的解析,使用 parseInt() 時都應該指定基數。
轉換為String型別
要把一個值轉換為一個字串有兩種方式,第一種是使用 toString() 方法,除了null和undefined之外,其餘的資料型別都有這個方法,它返回相應值的字串表現。在呼叫數值的 toString() 方法時,可以傳遞一個引數:輸出數值的基數。預設的輸出值與指定基數10時的輸出值相同。
var iuuan = true;
alert(iuuan.toString());
// 'true'var num = 7;
alert(num.toString());
// '7'alert(num.toString(2));
// '111'alert(num.toString(10));
// '7'複製程式碼
在不知道要轉換的值是不是 null 或 undefined 的情況下,還可以使用轉型函式 String() ,這個函式能夠將任何型別的值轉換為字串。
- 當值有 toString() 方法是,呼叫該方法並返回結果;
- 值是null時,返回”null”;
- 值是undefined時,返回”undefined”。
var value1 = 10;
var value2 = true;
var value3 = null;
var value4;
alert(String(value1));
// "10"alert(String(value2));
// "true"alert(String(value3));
// "null"alert(String(value4));
// "undefined"複製程式碼
物件轉換為基本型別
1、物件轉換為布林值時,根據上文所說的 Boolean() 假值可知,轉換後所有的物件都為true;
2、物件轉換為字串:
- 判斷物件是否有 toString() 方法,如果有 toString() 方法且返回的結果是基本型別值,就返回這個結果並轉換為字串;
- 如果物件沒有 toString 方法或者該方法返回的不是原始值,就判斷該物件是否有 valueOf 方法。如果存在 valueOf 方法且返回值是基本型別值,就返回並轉換為字串;
- 否則就丟擲錯誤。
var objtostring1 = {
//toString返回基本型別值 toString:function(){
return null
}
}var objtostring2 = {
//toString方法返回不是基本型別值,valueOf返回基本型別值 toString:function(){
return {
}
}, valueOf:function(){
return undefined
}
}var objtostring3 = {
//toString方法返回不是基本型別值,valueOf返回的也不是基本型別值 toString:function(){
return {
}
}, valueOf:function(){
return {
}
}
}String(objtostring1);
//'null'String(objtostring2);
//'undefined'String(objtostring3);
//Uncaught TypeError: Cannot convert object to primitive value複製程式碼
3、物件轉換為數值:
- 物件轉換為數值的操作與轉換為字串基本相似,只是轉換時先呼叫 valueOf ,不存在或返回值不是基本型別值時,再呼叫 toString 方法。
var objtonum1 = {
//valueOf返回基本型別值 valueOf:function(){
return null
}
}var objtonum2 = {
//valueOf方法返回不是基本型別值,toString返回基本型別值 valueOf:function(){
return {
}
}, toString:function(){
return 1
}
}var objtonum3 = {
//valueOf方法返回不是基本型別值,toString返回的也不是基本型別值 valueOf:function(){
return {
}
}, toString:function(){
return {
}
}
}Number(objtonum1);
//0 null轉換為數值後為0Number(objtonum2);
//1Number(objtonum3);
//Uncaught TypeError: Cannot convert object to primitive value複製程式碼
隱式強制型別轉換
與顯示型別轉換使用函式方法不同,隱式型別轉換髮生在是使用操作符或者語句中間。
+ 操作符
當 + 操作符作為一元操作符時,對非數值進行 Number() 轉型函式一樣的轉換;
var s1 = "01",s2 = "1.1",s3 = "z";
,b = false,f = 1.1;
var o = {
valueOf: function() {
return -1;
}
};
s1 = +s1;
// 值變成數值 1s2 = +s2;
// 值變成數值 1.1s3 = +s3;
// 值變成 NaNb = +b;
// 值變成數值 0f = +f;
// 值未變,仍然是 1.1o = +o;
// 值變成數值-1複製程式碼
當 + 操作符作為加法運算子時,會應用如下規則:
- 如果兩個運算元都是字串,則進行簡單的字串拼接;
- 如果只有一個運算元是字串,則將另一個轉換為字串再進行拼接,轉換為字串的操作與顯示轉換時規則相同;
- 如果有一個運算元是物件、數值或布林值,則呼叫它們的 toString 方法取得相應的字串值,然後再應用前面關於字串的規則
var s1 = "01",s2 = "1.1",b = false,f = 1.1;
var o = {
valueOf: function() {
return -1;
}
};
s1 + s2 //'011.1's1 + b //'01false's2 + f //'1.11.1's1 + o //'01-1'複製程式碼
– 操作符
當 – 操作符作為一元操作符時,對非數值進行 Number() 轉型函式一樣的轉換之後再取負;
var s1 = "01",s2 = "1.1",s3 = "z";
,b = false,f = 1.1;
var o = {
valueOf: function() {
return -1;
}
};
s1 = -s1;
// 值變成了數值-1s2 = -s2;
// 值變成了數值-1.1s3 = -s3;
// 值變成了 NaNb = -b;
// 值變成了數值 0f = -f;
// 變成了-1.1o = -o;
// 值變成了數值 1複製程式碼
當 – 操作符作為加法運算子時,會應用如下規則:
- 如果運算元存在非數值的基本型別,則先轉換為數值在進行減法計算;
- 如果運算元中存在物件,則按照物件轉換為數值的規則將物件轉換為數值後進行減法計算。
布林操作符
邏輯非 !
邏輯非操作符會將它的運算元轉換為一個布林值,然後再對其求反。所以使用兩個邏輯非操作符,實際上會模擬 Boolean() 轉型函式的行為。
邏輯與 &
&
和邏輯或 ||
這兩個操作符產生的值不是必須為Boolean型別,產生的值始終未兩個運算表示式的結果之一。
對於邏輯與 &
&
來說,如果第一個運算元條件判斷為 false 就返回該運算元的值,否則就返回第二個運算元的值。
對於邏輯或 || 來說,如果第一個運算元條件判斷為 true 就返回該運算元的值,否則就返回第二個運算元的值。
看個例子:
var a = 'hello',b = '';
a &
&
b;
// '' a是真值,所以返回bb &
&
a;
// '' b是假值,所以直接返回b,不對a進行判斷a || b;
// 'hello' a是真值,所以直接返回ab || a;
// 'hello' b是假值,所以返回a複製程式碼
可以看得出來,兩個操作符在執行時都有一個特點:當第一個運算元能決定操作結果時,則不會對第二個運算元進行判斷,並且直接返回第一個運算元的值。這種操作又稱為短路操作。
非嚴格相等 ==
等操作符比較兩個值是否相等,在比較前將兩個被比較的值轉換為相同型別。在轉換後(等式的一邊或兩邊都可能被轉換),最終的比較方式等同於全等操作符 === 的比較方式。
ECMAScript5文件中關於非嚴格相等的比較演算法,列出了有11中情況,文中就不一一列出了,可以自行去文件檢視學習:抽象相等比較演算法
這裡說明一下ToPrimitive操作,這個操作是ECMAScript執行時系統進行自動型別轉換的一種抽象操作,用於將物件型別轉換為基本型別,轉換規則如下:
- 檢查該值是否有 valueOf 方法。如果有且返回基本型別值,則使用該值;
- 如果沒有就使用 toString 方法的返回值(如果存在)來進行強制型別轉換;
- 如果 valueOf 或者 toString 都不返回基本型別值,則會報錯 TypeError。
如此繞的一串規則,不如來看幾個例子:
7 == '7' // true 字串與數字比較時,字串轉數值後比較1 == true // true 運算元中有布林值,布林值轉數值後比較,true為17 == true // false 原理同上相當於 7 == 1[] == 0 // true []先呼叫valueOf,返回值非基本型別,再呼叫toString,返回為'',空字串轉數值後為0[] == [] // false 作為引用型別,記憶體地址不同複製程式碼
總結起來就是一下幾條:
- null和undefined互相比較時,結果為true,其餘任何型別與這兩個值比較都為false;
- 運算元中存在數值,則將另一個運算元轉換為數值再比較;
- 運算元中沒有數值但有字串,則將另一個運算元轉換為字串再比較;
- 運算元中的布林值都轉換為數值。非基本型別都先進行ToPrimitive操作,按上述三條順序進行比較。
比較關係符
同樣的,文件中的規則非常長,就不列出來了,抽象關係比較演算法
//兩邊均為字串'7' >
'20';
// true 按字元編碼進行比較//兩邊不全是字串7 >
'20';
// false 字串轉為數值後進行比較//兩邊全不是基本型別[7] >
[20];
// true 陣列呼叫valueOf返回非基本型別,再呼叫toString方法返回字串。var obj = {
},obj1 = {
};
obj >
obj1;
// false複製程式碼
總結起來,比較關係符的型別轉換比較規則就是:
- 如果運算元中存在非基本型別,先進行ToPrimitive操作;
- ToPrimitive操作轉換後如果運算元出現數值,那麼將運算元轉換為數值進行比較;
- ToPrimitive操作轉換後如果運算元均為字串,那麼按照字元編碼值進行比較。
最後來說說 obj >
= obj1 的特殊現象
var obj = {
},obj1 = {
};
obj <
obj1;
// falseobj == obj1;
// falseobj >
obj1;
// falseobj >
= obj1;
// trueobj <
= obj1;
// true複製程式碼
前面三個結果不難理解,非嚴格相等判斷時,均為空物件,但引用地址不同,返回false。比較兩個物件時,先進行ToPrimitive操作,均返回 ''[object Object]''
,所以不存在大小關係,也返回false。那為什麼a <
= b和a >
= b的結果返回的是true呢?
因為根據規範,a <
= b 實際上執行的是 !(a >
b),即我們理解的<
=是“小於或等於”,但JavaScript執行的是“不大於”的操作,所以 a >
b 為false,那麼 a <
= b 自然為true了。
結語
JavaScript作為一門弱型別語言,其中的型別轉換規則總結起來真是讓人頭疼。當然,熟練掌握這些規則,並不是為了在實際開發中寫出這些晦澀程式碼,而是通過理解,使得我們能夠避免在程式碼編寫的過程中避免觸碰到不必要的型別轉換,提高程式碼的穩定性和可維護性。
筆者作為前端菜鳥,文中如有錯誤,歡迎指出,共同交流、進步。
參考連結
《JavaScript高階程式設計》