天天看點

[Effective JavaScript 筆記]第59條:避免過度的強制轉換

js是弱類型語言。許多标準的操作符和代碼庫會把輸入參數強制轉換為期望的類型而不是抛出錯誤。如果未提供額外的邏輯,使用内置操作符的程式會繼承這樣的強制轉換行為。

functin square(x){
    return x*x;
}
square("3");//9           

強制轉換

強制轉換可以帶來友善性,但也會帶來相關的麻煩,一些錯誤無法顯露出來,導緻程式行為的不穩定和難以調試。

當強制轉換與重載的函數一起工作的時候,結果會更難了解。上一節講的位向量類的enable方法。該方法使用其參數的類型來決定其行為。如果enable方法嘗試将其參數強制轉換為一個期望的類型,那麼方法簽名可能會變更難了解。将方法的參數強制轉換為一個數字完全破壞了重載。

BitVector.prototype.enable=function(x){
    x=Number(x);//轉化為數字
    if(typeof x=== 'number'){//一直是正确的
        this.enableBit(x);
    }else{//這裡永遠不會執行
        for(var i=0,n=x.length;i < n;i++){
           this.enableBit(x[i]); 
        }
    }
};
           

一般規則,在那些使用參數類型來決定重載函數行為的函數中避免強制轉換參數。強制轉換使用很難識别出參數的變量。

bits.enable('100');//數字還是位數組值           

調用者可以合理地認為參數可以是一個數字或一個位數組值,然而我們的構造函數并不是為字元串設計的,是以無法識别它。

防禦性程式設計

可能是調用者沒有用對,但如果設計API時,強制隻接收數字和對象,則可以避免出現上面的錯誤。

BitVector.prototype.enable=function(x){
    if(typeof x=== 'number'){
        this.enableBit(x);
    }else if(typeof x==='object' && object){
        for(var i=0,n=x.length;i < n;i++){
           this.enableBit(x[i]); 
        }
    }else{
        throw new TypeError('請輸入一個數或類數組對象');
    }
};
           

enable方法的最終版本是一種風格更加謹慎的示例,被稱為防禦性程式設計。防禦性程式設計試圖以額外的檢查來抵禦潛在的錯誤。抵禦所有可能的錯誤是不可能的。如,我們可能使用檢查來確定如果x具有length屬性,那麼它應該是一個對象,然而這并不是安全的,比如,一個意外使用的String對象。

監視函數

js除了提供實作檢查的基本工具外,比如typeof操作符,還可以編寫簡潔的工具函數來監視函數簽名。如,可以使用一個預先檢查來監視BitVector的構造函數。

function BitVector(x){
    uint32.or(arrayLike).guard(x);
    //...
}
           

借助于共享原型對象來實作guard方法以建構一個監視對象的工具庫。

var guard={
    guard:function(x){
        if(!this.test(x)){
            throw new TypeError('expected '+this);
        }
    }
};
           

每個監視對象實作自己的test方法和錯誤消息的字元串描述。

uint32監視對象

var uint32=Object.create(guard);
uint32.test=function(x){
    return typeof x === 'number' && x === (x >>> 0); 
};
uint32.toString=function(){
    return 'uint32';
};
           

uint32的監視對象使用js位操作符的一個訣竅來實作32位無符号整數的轉換。無符号右移位運算符在執行移位運算前會将其第一個參數轉換為一個32位的無符号整數。移入零位對整數值沒有影響。實際上uint32.test是把一個數字與該數字轉換為32位無符号整數的結果做比較。

arrayLike監視對象

下面實作arrayLike的監視對象。

var arrayLike=Object.create(guard);
arrayLike.test=function(x){
    return typeof x==='object' && x && uint32.test(x.length);
};
arrayLike.toString=function(){
    return 'array-like object';
};
           

這裡又進一步地采取了防禦性程式設計來確定一個類數組對象應該具有一個無符号整數的length屬性。

“鍊”方法

最後,實作一些原型方法的“鍊”方法,比如or方法。

guard.or=function(other){
    var res=Object.create(guard);
    var self=this;
    res.test=function(x){
        return self.test(x)||other.test(x);
    };
    var description=this+' or '+other;
    res.toString=function(){
        return description;
    };
    return res;
}
           

該方法合并接受者監視對象和另一個監視對象,産生一個新的監視對象。新監視對象的test和toString方法合并了這兩個輸入對象的方法。這裡用局部的self來儲存this的引用,以確定能在合成的監視對象的test方法中引用。

當遇到錯誤時,這些測試能幫助我們更早地捕獲錯誤,使得它們更容易診斷。但,這也可能擾亂代碼庫并潛在地影響應用程式的性能。是否使用防禦性程式設計是一個成本和收益的問題。

提示

  • 避免強制轉換和重載的混用
  • 考慮防禦性地監視非預期的輸入

版權聲明

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

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

繼續閱讀