js是弱類型語言。許多標準的操作符和代碼庫會把輸入參數強制轉換為期望的類型而不是拋出錯誤。如果未提供額外的邏輯,使用內置操作符的程式會繼承這樣的強制轉換行為。functin square(x){ return x*x; } square("3");//9 強制轉換 強制轉換可以帶來方便性,但也會帶來... ...
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方法中引用。
當遇到錯誤時,這些測試能幫助我們更早地捕獲錯誤,使得它們更容易診斷。但,這也可能擾亂代碼庫並潛在地影響應用程式的性能。是否使用防禦性編程是一個成本和收益的問題。
提示
-
避免強制轉換和重載的混用
-
考慮防禦性地監視非預期的輸入