說說JavaScript的型別轉換
最近在重讀《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(""); // NaN var num3 = parseInt("0xA"); // 10(十六進位制數) var num4 = parseInt(22.5); // 22)(小數點並不是有效的數字字元) 複製程式碼
parseInt() 函式可以接收兩個引數,第一個引數是需轉換字串,第二個引數是轉換是使用的基數(即多少進位制),例如:
var num1 = parseInt("AF", 16); //175 var 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);//0null轉換為數值後為0 Number(objtonum2);//1 Number(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;// 值變成數值 1 s2 = +s2;// 值變成數值 1.1 s3 = +s3;// 值變成 NaN b = +b;// 值變成數值 0 f = +f;// 值未變,仍然是 1.1 o = +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;// 值變成了數值-1 s2 = -s2;// 值變成了數值-1.1 s3 = -s3;// 值變成了 NaN b = -b;// 值變成了數值 0 f = -f;// 變成了-1.1 o = -o;// 值變成了數值 1 複製程式碼
當 - 操作符作為加法運算子時,會應用如下規則:
- 如果運算元存在非數值的基本型別,則先轉換為數值在進行減法計算;
- 如果運算元中存在物件,則按照物件轉換為數值的規則將物件轉換為數值後進行減法計算。
布林操作符
邏輯非 !
邏輯非操作符會將它的運算元轉換為一個布林值,然後再對其求反。所以使用兩個邏輯非操作符,實際上會模擬 Boolean() 轉型函式的行為。
邏輯與 && 和邏輯或 ||
這兩個操作符產生的值不是必須為Boolean型別,產生的值始終未兩個運算表示式的結果之一。
對於邏輯與 && 來說,如果第一個運算元條件判斷為 false 就返回該運算元的值,否則就返回第二個運算元的值。
對於邏輯或 || 來說,如果第一個運算元條件判斷為 true 就返回該運算元的值,否則就返回第二個運算元的值。
看個例子:
var a = 'hello',b = ''; a && b;// '' a是真值,所以返回b b && a;// '' b是假值,所以直接返回b,不對a進行判斷 a || b;// 'hello' a是真值,所以直接返回a b || a;// 'hello' b是假值,所以返回a 複製程式碼
可以看得出來,兩個操作符在執行時都有一個特點:當第一個運算元能決定操作結果時,則不會對第二個運算元進行判斷,並且直接返回第一個運算元的值。這種操作又稱為短路操作。
非嚴格相等 ==
等操作符比較兩個值是否相等,在比較前將兩個被比較的值轉換為相同型別。在轉換後(等式的一邊或兩邊都可能被轉換),最終的比較方式等同於全等操作符 === 的比較方式。
ECMAScript5文件中關於非嚴格相等的比較演算法,列出了有11中情況,文中就不一一列出了,可以自行去文件檢視學習:抽象相等比較演算法
這裡說明一下ToPrimitive操作,這個操作是ECMAScript執行時系統進行自動型別轉換的一種抽象操作,用於將物件型別轉換為基本型別,轉換規則如下:
- 檢查該值是否有 valueOf 方法。如果有且返回基本型別值,則使用該值;
- 如果沒有就使用 toString 方法的返回值(如果存在)來進行強制型別轉換;
- 如果 valueOf 或者 toString 都不返回基本型別值,則會報錯 TypeError。
如此繞的一串規則,不如來看幾個例子:
7 == '7'// true 字串與數字比較時,字串轉數值後比較 1 == true// true 運算元中有布林值,布林值轉數值後比較,true為1 7 == 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;// false obj == obj1;// false obj > obj1;// false obj >= obj1;// true obj <= obj1;// true 複製程式碼
前面三個結果不難理解,非嚴格相等判斷時,均為空物件,但引用地址不同,返回false。比較兩個物件時,先進行ToPrimitive操作,均返回''[object Object]''
,所以不存在大小關係,也返回false。那為什麼a <= b和a >= b的結果返回的是true呢?
因為根據規範,a <= b 實際上執行的是 !(a > b),即我們理解的<=是“小於或等於”,但JavaScript執行的是“不大於”的操作,所以 a > b 為false,那麼 a <= b 自然為true了。