讀古今文學網 > 編寫高質量代碼:改善JavaScript程序的188個建議 > 建議4:注意JavaScript數據類型的特殊性 >

建議4:注意JavaScript數據類型的特殊性

1.防止浮點數溢出

二進制的浮點數不能正確地處理十進制的小數,因此0.1+0.2不等於0.3。


num=0.1+0.2;//0.30000000000000004


這是JavaScript中最經常報告的Bug,並且這是遵循二進制浮點數算術標準(IEEE 754)而導致的結果。這個標準適合很多應用,但它違背了數字基本常識。幸運的是,浮點數中的整數運算是精確的,所以小數表現出來的問題可以通過指定精度來避免。例如,針對上面的相加可以這樣進行處理:


a=(1+2)/10;//0.3


這種處理經常在貨幣計算中用到,在計算貨幣時當然期望得到精確的結果。例如,元可以通過乘以100而全部轉成分,然後就可以準確地將每項相加,求和後的結果可以除以100轉換回元。

2.慎用JavaScript類型自動轉換

在JavaScript中能夠自動轉換變量的數據類型,這種轉換是一種隱性行為。在自動轉換數據類型時,JavaScript一般遵循:如果某個類型的值被用於需要其他類型的值的環境中,JavaScript就自動將這個值轉換成所需要的類型,具體說明見表1.1。

如果把非空對像用在邏輯運算環境中,則對像被轉換為true。此時的對象包括所有類型的對象,即使是值為false的包裝對象也被轉換為true。

如果把對像用在數值運算環境中,則對像會被自動轉換為數字,如果轉換失敗,則返回值為NaN。

當數組被用在數值運算環境中時,數組將根據包含的元素來決定轉換的值。如果數組為空數組,則被轉換為數值0。如果數組僅包含一個數字元素,則被轉換為該數字的數值。如果數組包含多個元素,或者僅包含一個非數字元素,則返回NaN。

當對像用於字符串環境中時,JavaScript能夠調用toString方法把對像轉換為字符串再進行相關計算。當對象與數值進行加號運算時,則會嘗試將對像轉換為數值,然後參與求和運算。如果不能夠將對像轉換為有效數值,則執行字符串連接操作。

3.正確檢測數據類型

使用typeof運算符返回一個用於識別其運算數類型的字符串。對於任何變量來說,使用typeof運算符總是以字符串的形式返回以下6種類型之一:

❑\"number\"

❑\"string\"

❑\"boolean\"

❑\"object\"

❑\"function\"

❑\"undefined\"

不幸的是,在使用typeof檢測null值時,返回的是「object」,而不是「null」。更好的檢測null的方式其實很簡單。下面定義一個檢測值類型的一般方法:


function type(o){

return(o===null)?\"null\":(typeof o);

}


這樣就可以避開因為null值影響基本數據的類型檢測。注意:typeof不能夠檢測複雜的數據類型,以及各種特殊用途的對象,如正則表達式對像、日期對像、數學對像等。

對於對像或數組,可以使用constructor屬性,該屬性值引用的是原來構造該對象的函數。如果結合typeof運算符和constructor屬性,基本能夠完成數據類型的檢測。表1.2所示列舉了不同類型數據的檢測結果。

使用constructor屬性可以判斷絕大部分數據的類型。但是,對於undefined和null特殊值,就不能使用constructor屬性,因為使用JavaScript解釋器會拋出異常。此時可以先把值轉換為布爾值,如果為true,則說明不是undefined和null值,然後再調用constructor屬性,例如:


var value=undefined;

alert(typeof value);//\"undefined\"

alert(value&&value.constructor);//undefined

var value=null;

alert(typeof value);//\"object\"

alert(value&&value.constructor);//null


對於數值直接量,也不能使用constructor屬性,需要加上一個小括號,這是因為小括號運算符能夠把數值轉換為對象,例如:


alert((10).constructor);


使用toString方法檢測對像類型是最安全、最準確的。調用toString方法把對像轉換為字符串,然後通過檢測字符串中是否包含數組所特有的標誌字符可以確定對象的類型。toString方法返回的字符串形式如下:


[object class]


其中,object表示對象的通用類型,class表示對象的內部類型,內部類型的名稱與該對象的構造函數名對應。例如,Array對象的class為「Array」,Function對象的class為「Function」,Date對象的class為「Date」,內部Math對象的class為「Math」,所有Error對像(包括各種Error子類的實例)的class為「Error」。

客戶端JavaScript的對象和由JavaScript實現定義的其他所有對象都具有預定義的特定class值,如「Window」、「Document」和「Form」等。用戶自定義對象的class值為「Object」。

class值提供的信息與對象的constructor屬性值相似,但是class值是以字符串的形式提供這些信息的,而不是以構造函數的形式提供這些信息的,所以在特定的環境中是非常有用的。如果使用typeof運算符來檢測,則所有對象的class值都為「Object」或「Function」,所以此時的class值不能夠提供有效信息。

但是,要獲取對象的class值的唯一方法是必須調用Object對像定義的默認toString方法,因為不同對象都會預定義自己的toString方法,所以不能直接調用對象的toString方法。例如,下面對象的toString方法返回的就是當前UTC時間字符串,而不是字符串「[object Date]」。


var d=new Date;

alert(d.toString);//當前UTC時間字符串


要調用Object對像定義的默認toString方法,可以先調用Object.prototype.toString對象的默認toString函數,再調用該函數的apply方法在想要檢測的對象上執行。結合上面的對象d,具體實現代碼如下:


var d=new Date;

var m=Object.prototype.toString;

alert(m.apply(d));//\"[object Date]\"


下面是一個比較完整的數據類型安全檢測方法。


//安全檢測JavaScript基本數據類型和內置對像

//參數:o表示檢測的值

/*返回值:返回字符串\"undefined\"、\"number\"、\"boolean\"、\"string\"、\"function\"、\"regexp\"、\"array\"、\"date\"、\"error\"、\"object\"或\"null\"*/

function typeOf(o){

var_toString=Object.prototype.toString;

//獲取對象的toString方法引用

//列舉基本數據類型和內置對像類型,可以進一步補充該數組的檢測數據類型範圍

var_type={

\"undefined\":\"undefined\",

\"number\":\"number\",

\"boolean\":\"boolean\",

\"string\":\"string\",

\"[object Function]\":\"function\",

\"[object RegExp]\":\"regexp\",

\"[object Array]\":\"array\",

\"[object Date]\":\"date\",

\"[object Error]\":\"error\"

}

return_type[typeof o]||_type[_toString.call(o)]||(o?\"object\":\"null\");

}


應用示例:


var a=Math.abs;

alert(typeOf(a));//\"function\"


上述方法適用於JavaScript基本數據類型和內置對象,而對於自定義對象是無效的。這是因為自定義對像被轉換為字符串後,返回的值是沒有規律的,並且不同瀏覽器的返回值也是不同的。因此,要檢測非內置對象,只能夠使用constructor屬性和instaceof運算符來實現。

4.避免誤用parseInt

parseInt是一個將字符串轉換為整數的函數,與parseFloat(將字符串轉換為浮點數)對應,這兩種函數是JavaScript提供的兩種靜態函數,用於把非數字的原始值轉換為數字。

在開始轉換時,parseInt會先查看位置0處的字符,如果該位置不是有效數字,則將返回NaN,不再深入分析。如果位置0處的字符是數字,則將查看位置1處的字符,並重複前面的測試,依此類推,直到發現非數字字符為止,此時parseInt函數將把前面分析合法的數字字符轉換為數值並返回。


parseInt(\"123abc\");//123

parseInt(\"1.73\");//1

parseInt(\".123\");//NaN


浮點數中的點號對於parseInt來說屬於非法字符,因此它不會被轉換並返回,這樣,在使用parseInt時,就存在潛在的誤用風險。例如,我們並不希望parseInt(\"16\")與parseInt(\"16 tons\")產生相同的結果。如果該函數能夠提醒我們出現額外文本就好了,但它不會那麼做。

對於以0為開頭的數字字符串,parseInt函數會把它作為八進制數字處理,先把它轉換為數值,然後再轉換為十進制的數字返回。對於以0x開頭的數字字符串,parseInt函數則會把它作為十六進制數字處理,先把它轉換為數值,然後再轉換為十進制的數字返回。例如:


var d=\"010\";//八進制

var e=\"0x10\";//十六進制

parseInt(d);//8

parseInt(e);//16


如果字符串的第一個字符是0,那麼該字符串將基於八進制而不是十進制來求值。在八進制中,8和9不是數字,所以parseInt(\"08\")和parseInt(\"09\")的結果為0,這個錯誤導致了在程序解析日期和時間時經常會出現問題。幸運的是,parseInt可以接受一個基數作為參數,這樣parseInt(\"08\",10)結果為8,parseInt(\"09\",10)結果為9。因此,建議讀者在使用parseInt時,一定要提供這個基數參數。

通過在parseInt中提供基數參數,可以把二進制、八進制、十六進制等不同進制的數字字符串轉換為整數。例如,下面把十六進制數字字符串\"123abc\"轉換為十進制整數。


parseInt(\"123abc\",16);//1194684


再如,把二進制、八進制和十進制數字字符串轉換為整數:


parseInt(\"10\",2);//把二進制數字10轉換為十進制整數為2

parseInt(\"10\",8);//把八進制數字10轉換為十進制整數為8

parseInt(\"10\",10);//把十進制數字10轉換為十進制整數為10