天天看點

[Effective JavaScript筆記]第3條:當心隐式的強制轉換

js對類型錯誤出奇的寬容

3+true;  //4

3*””;  //0

3+[]; //3

3+[3]; //33

以上表達式在許多語言早就變紅了。而js不但不報錯還給你個結果。

極少情況會産生即時錯誤,非函數對象或試圖選擇null的屬性。

“hello”(1);//error:not a function

null.x; //error:cannot read property ‘x’of null

大多數情況是不會抛錯的,按照多種多樣的方式進行着轉換。

運算操作

算術運算符在計算之前會嘗試将參數轉換為數字。

“17”* 3 =>51

18 / “3” =>6

“10”-“ 4”=>6

”+  ”号比較特殊除了數字還能連接配接字元串。取決于其參數的類型。

2+3; //5

“hello”+”world”; //”hello world”

“2”+ 3  ; // “23”

2+”3”;//23

合并數字和字元串時,js偏愛字元串。是以上面的

“2”+(3).toString();//”23”

(2).toString()+”3”;//”23”

1+2+”3”;//”33”

1+”2”+3;//”123”

為什麼呢。

加法運算是自左向右的,(也就是看到了才算,+是兩元操作符,隻看到左邊和右邊。)

1+2+”3”=> (1+2)+”3”=>3+”3”=>”33”

1+”2”+3=>(1+”2”)+3=>”12”+3=>”123”

位運算符(~,&,^和|)以及移位運算符(<<,>>和>>>)不僅會将操作數轉換為數字,而且還會将操作數轉換為32位整數。

“8”|“1”=>9

“8”& true => 0

  ~“8” => –9

8^2 => 10

8<<1=>16 相當于8*2

8>>1=>4 相當于8/2

-8>>>1=>2147483644

8>>>1=>4

(>>>和>>差別在于,符号位動不動,>>>是連符号位都向右移動,是以負數會變很大,正數值不變,負數的符号位為1,正數的符号位為0。(具體的查書去))

NaN

強制轉換也會隐藏錯誤。結果為null的變量在算術中不會導緻錯誤因為null會轉化為0,一個未定義的變量将被轉換為特殊的浮點數NaN。在測試NaN時也會遇到問題因為NaN不等于本身。測試一個值是否等于NaN行不通。

var x=NaN;

x===NaN; //false

isNaN也不可靠,也會帶有隐式轉換。

isNaN(“2”); //false

isNaN(“2s”); //false

isNaN(null); //false

isNaN([]); //false

對于絕對不是NaN,但會被轉化為NaN的值,使用isNaN是無法區分的。

isNaN(“s2”); //true

isNaN(undefined); //true

isNaN({}); //true

isNaN({valueOf:”s2”}); //true

下面還有兩個要注意的值

isNaN(NaN);//false

isNaN(Infinity); //false

NaN和Infinity會先轉化為0

NaN是唯一一個不和自身相等的值,可以通過這個寫出下面這個函數

function isReallyNaN(x){

    return x!==x;

}

調試

強制轉換使得調試很難,因為沒法确定一些類型錯誤。當計算出問題,最好的調試是在檢查計算的過程中,回到出錯的“最後一點”。檢查每個操作參數,檢視錯誤類型的參數。根據不同的錯誤,可能是邏輯錯誤,也可能是類型錯誤。

對象

對象也會被轉換為原始值。最常見的是轉換為字元串。

'我的類型是:'+Math;//"我的類型是:[object Math]"

對象通過隐式調用自身的toString方法進行轉換。可以直接調用一下試試。

Math.toString(); //"[object Math]"

對象也可以通過valueOf()方法轉換為數字

2*{valueOf:function(){return 100;}};//200

2+{valueOf:function(){return 100;}};//102

"2"+{toString:function(){return "000"}};//”2000”

"2"+{toString:function(){return "000"},valueOf:function(){return 1000}}; //”21000”

上面這個運算沒法說明+号是在做字元串連接配接還是加法根據參數的類型,因為存在隐式類型轉換,是以類型并不明顯,js中優先選擇valueOf方法而不是toString方法來解決含糊的問題

(如果字元串連接配接應該調用toString方法,但上面并沒有。而是調用了valueOf方法,是以這裡很含糊)

valueOf是專門為那些代表數值的對象設計的。對于數值對象toString和valueOf方法應該傳回相同的值,隻是不同的類型。不管是對象的連接配接和相加+運算的結果都是相同的。最好避免使用valueOf方法,除非對象真的是數字的抽象,并且保證toString方法産生一個valueOf方法結果的字元串表示。

真值運算

if,||和&&等邏輯運算上需要布爾值作為參數,但實際上可以接受任何值。js按照簡單的隐式轉換規則将其它值解釋為布爾值。大多數js的值都為真,隐式都可轉化為true。對于字元串與數字以外的對象,真值運算不會隐式調用任何強制轉換方法。js中有7個假值:false,0,-0,””,NaN,null和undefined。其他所有值都為真值。

使用真值去檢查函數參數或者對象屬性是否已定義不是絕對安全的。

如:

function point(x,y){

    if(!x){

      x=320;

    }

    if(!y){

       y=240;

     }

     return {x:x,y:y}

}

此函數忽略任何為假值的參數,包括0

point(0,0); //{x:320,y:240}

檢查參數是否為undefined更為嚴格,可以使用typeof

    if(typeof x === ‘undefined’){

       x=320;

    if(typeof y === ‘undefined’){

這種方式可以正确識别0和undefined

point(); //{x:320,y:240}

point(0,0); //{x:0,y:0}

另一種方式和值undefined比較

if(x===undefined){….}

效果和第二種方式相同

總結

1、類型錯誤會被隐式的強制轉換隐藏

2、運算符+是進行加法運算還是字元串連接配接操作取決于參數類型。

3、valueOf強制轉換為數字,toString強制轉換為字元串

4、實作valueOf方法的對象,應該實作一個toString方法傳回valueOf方法傳回值的字元串表示

5、測試一個值是否未定義,應該使用typeof或者直接與undefined比較,不應該使用真值運算

附錄:補碼的計算方法(高3中拿來的)

負值是以二進制補碼的方式存儲。計算一個數值的二進制補碼,經過3個步驟(以-18為例)

(1)求這個數值的絕對值的二進制碼

18的二進制碼

0000 0000 0000 0000 0000 0000 0001 0010

(2)求二進制反碼,即将0替換為1,将1替換為0

18的二進制反碼

1111 1111 1111 1111 1111 1111 1110 1101

(3)得到的二進制反碼加1

                                                                     1

--------------------------------------------------

1111 1111 1111 1111 1111 1111 1110 1110

這個是-18的存儲在記憶體中的二進制表示。

版權聲明

翻譯的文章,版權歸原作者所有,隻用于交流與學習的目的。

原創文章,版權歸作者所有,非商業轉載請注明出處,并保留原文的完整連結。

繼續閱讀