說說JavaScript的類型轉換

最近在重讀《JavaScript高級程序設計》,讀到數據類型這一節,想到了JavaScript裏令程序員抓狂的一個問題——類型轉換。由於JS是一門弱類型的語言,在運行時系統會根據須要進行類型轉換,而類型轉換的規則又使人迷惑。因而寫篇博文嘗試本身總結來加深印象。javascript

基本概念

首先,咱們知道JavaScript裏有7種數據類型:前端

boolean number null string symbol undefined objectjava

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() 只處理字符串類型,若是接受的參數不是字符串類型,會先將其轉換爲字符串類型(稍後介紹字符串的強制轉換)ui

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"
複製代碼

對象轉換爲基本類型

一、對象轉換爲布爾值時,根據上文所說的 Boolean() 假值可知,轉換後全部的對象都爲true;

二、對象轉換爲字符串:

  • 判斷對象是否有 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
複製代碼

三、對象轉換爲數值:

  • 對象轉換爲數值的操做與轉換爲字符串基本類似,只是轉換時先調用 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轉換爲數值後爲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 做爲引用類型,內存地址不一樣
複製代碼

總結起來就是一下幾條:

  1. null和undefined互相比較時,結果爲true,其他任何類型與這兩個值比較都爲false;
  2. 操做數中存在數值,則將另外一個操做數轉換爲數值再比較;
  3. 操做數中沒有數值但有字符串,則將另外一個操做數轉換爲字符串再比較;
  4. 操做數中的布爾值都轉換爲數值。非基本類型都先進行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了。

結語

JavaScript做爲一門弱類型語言,其中的類型轉換規則總結起來真是讓人頭疼。固然,熟練掌握這些規則,並非爲了在實際開發中寫出這些晦澀代碼,而是經過理解,使得咱們可以避免在代碼編寫的過程當中避免觸碰到沒必要要的類型轉換,提升代碼的穩定性和可維護性。

筆者做爲前端菜鳥,文中若有錯誤,歡迎指出,共同交流、進步。

參考連接

《JavaScript高級程序設計》

MDN JavaScript參考文檔

ECMAScript5.1中文版

相關文章
相關標籤/搜索