JavaScript需要避免的問題總結
最近看了Douglas Crockford的《JavaScript 語言精粹》,對於JavaScript有了新的瞭解,本文主要總結一下JavaScript語言特性引起的一些常見問題,以及避免方法。
1.檢索
JavaScript檢索對象包含值的方法有兩種,一種是”[]”,另一種是”.”,一般兩種方式都可用。但在檢索的字符串是一個非法JavaScript標示符或者爲保留字時,”.”是不可用的,只能使用name[“first-name”]
(在對象字面量定義時也是如此,由於“first-name”是非法標示符,所以定義時必須用引號包含——“first-name:”“Joe”)。
書中建議使用”.”方法檢索,也就是name.first_name。所以就需要編碼時注意標示符的合法性,以及不要和保留字衝突。另外一個更重要的問題是,檢索一個不存在的成員屬性時,將返回undefined,而從undefined中取值會導致TypeError異常,例:age不爲name對象的屬性,name.age爲undefined,name.age.child則會拋出異常。解決方法爲 設置默認值var age = name.age || “unknown”, 也可以通過“&&”運算避免錯誤: name.age&&name.age.child。
2.枚舉
for in 語句可以遍歷一個對象中所有屬性名,但同時還包括函數和原型中的屬性。解決方法一就是,過濾掉不需要的屬性:
var name;
for(name in names){
if(names.hasOwnProperty(name) && typeof names[name] !=='function'){
document.writeln(name + ' : ' + names[name]);
}
}
其中hasOwnProperty方法檢測對象是否有指定屬性,且不檢查原型鏈;若屬性值爲函數,typeof返回’function’類型。
另一種解決方法就是,完全避免使用for in語句,使用常規for語句。
因爲for in語句除了上述問題以外,枚舉的屬性名出現的順序是不確定的,若要使順序確定,則可以創建一個數組,以正確的順序包含屬性名,然後用for語句遍歷:
var i;
var properties =[
'first_name',
'middle_name',
'last_name'
];
for(i=0; i<properties.length; i+=1){
document.writeln(properties[i] + ':' + names[properties[i]]);
}
這樣既保證了順序,又直接確定了需要的屬性。(但這種方法需要自己創建數組,且需要自己確定屬性名,雖然書中推薦這種方法,但還要看具體情況)
3.減少全局污染
Douglas Crockford在書中指出,JavaScript對全局變量的依賴是所有糟糕特性中最糟糕的一個,因爲全局變量可以被程序的任何部分在任意時間修改,降低了程序的可靠性,且全局變量名稱會和子程序變量名稱產生衝突,導致程序無法運行且難以調試。
共有3種方式定義全局變量:
- 在任何函數之外聲明: var foo = value;
- 直接給全局對象添加屬性: window.foo = value; (window爲web瀏覽器的全局對象)
- 直接使用未經聲明的變量: foo = value; (即隱式的全局變量)
減少全局污染的方法一:最小化使用全局變量 ,一個應用只創建一個唯一全局變量
var MYAPP = {};
MYAPP.name = {
...
};
MYAPP.method={
...
};
另一種減少全局污染的方法是,使用函數和“閉包”構建模塊來進行信息隱藏:
“內部函數可以訪問它們外部函數的參數和變量(除了this和arguments),內部函數擁有比它的外部函數更長的生命週期,內部函數訪問外部函數的實際變量而無需複製。”
- 所謂“閉包”即是函數可以訪問它被創建時所處的上下文環境(可以簡單理解爲內部函數可以訪問外部函數定義的變量)。
- 模塊即利用函數作用域和“閉包”構建的只提供接口,隱藏其中狀態信息的函數或對象。
這裏舉書中的一個例子:
var serial_maker = function(){
var prefix = '';
var seq = 0;
return {
set_prefix: function(p){
prefix=String(p);
},
set_seq: function(s){
seq = s;
},
gensym: function(){
var result = prefix + seq;
seq +=1;
return result;
}
};
};
var seqer = serial_maker();
seqer.set_prefix('Q');
seqer.set_seq(1000);
var unique = seqer.gensym();
seqer只提供接口方法set_prefix、set_seq和gensym,由於函數作用域,私有變量對其他程序是不可見的,只能通過這些方法改變prefix 和seq的值。
也可以構造一個對象:
var myObj = (function() {
var value=0;
return {
increment: function(inc){
value += typeof inc === 'number'?inc:1;
},
getValue: function(){
return value;
}
};
}());
myObj.increment(1);
myObj.getValue();
注意“()”,以上返回的是函數運行的結果,即包含兩個方法的對象。
模塊模式的一般形式爲第一種方式:定義了私有變量和函數的函數;利用閉包創建訪問私有變量和函數的接口函數;最後返回這個函數,或者放到可以訪問的地方。
利用模塊模式可以摒棄全局變量的使用。
可參考另外一篇文章JavaScript的幾點編碼規範提高代碼質量