<本方譯自http://www.asp.net/ajaxlibrary/act_contribute_codingStandards_Programming.ashx。原文主要針對ASP.net的Ajax 控件工具箱,但多數準則亦普遍適用於javascript編程>
本節的主題是幫助程序員防止一些常見的錯誤,或者強調一些可以明顯提高性能、可維護性或者可支持性的慣例。
5.1 變量聲明
所有變量都應該使用var來聲明,以減少遍歷作用域鏈(scope chain)的額外開銷。除非絕對必要,不要使用全局變量,因爲它們查找起來總是最慢的。變量應該被初始化爲缺省值或者null。例:
var counter = 0;
var empty = null;
建議將所有變量聲明都放在函數頭部。將變量分組聲明以減少腳本的大小。如:
var counter = 0, empty = null;
Javascript沒有塊作用域的概念;因此定義在函數內部的"if"或"for"塊中的變量,可以在該函數內的任意地方訪問。因此,對javascript來說是享受不到任何變量延遲定義的好處的。
<譯註:
當必須使用全局變量時,可以使用Global Import 模式,將全局變量導入成爲局部變量,這樣在需要使用原全局變量的時候改爲使用導入的局部變量,從而減少了作用域鏈查找的代價。
在變量分組聲明上,Dojo的觀點是,只能將邏輯上相關聯的一組變量放在一起聲明。如果沒有邏輯上的關聯,則變量必須每個單獨成行聲明。可讀性高於一切。畢竟,這種做法並不會顯著提升性能。>
避免使用"with"語句。它不只是降低了代碼的可讀性,而且對性能亦有損傷。
<譯註:在VBA中使用with語句倒是加快性能的方法。此一時,彼一時也。不過此前根本就不知道javascript還支持with語法>
5.2 函數快捷方式
<譯註: 不理解何爲function shourtcuts。 疑爲IE獨門絕技?>
Avoid using function shortcuts such as $get within script that must coexist on a page with other scripts, or may do in the future. This is to avoid other scripts reassigning a global shortcut to an alternative function, which may cause your script to fail.
Instead use fully qualified function calls whenever a script may be reused in uncertain or shared circumstances, or for non-global functions use a closure as described in section 3.7.1.
5.3 函數別名
當需要在一個循環中調用某個函數多次時,可以使用函數別名以減少作用域鏈的查找,例:
function doSomething() {
var get = getDataByIndex;
for (var counter = 0; counter < 10000; counter++) {
var current = get(counter);
// ...code removed
}
}
注意在使用別名時,函數一定要設計成允許這樣使用。比如依賴於"this"的函數可能就不能被成功地通過別名來引用。
5.4 比較和相等
任何時候只要可能,都使用嚴格相等符號("===")來決定兩個值是否相等。這樣可以避免隱藏的類型轉換,而且確保精度的責任也不由程序員來承擔。例:
如果"x"和"y"都有數值型值:
錯誤: if (x==5 && y != 4)
正確: if (x === 5 && y !== 4)
5.5 Undefined和null
不要在使用undefined和null關鍵字上搖擺不定,確保任何字段或變量都初始化爲確定值或者爲null;
this._result = null;
如果不象上面這樣聲明,意味着_result將是undefined。
當測試一個類實例變量是否有值,或者一個可選參數是否提供給一個函數時,且該變量/參數確定爲一個對象是,使用下面的語法:
if (message) {
// message was provided and not null
}
5.6 循環和遞歸
循環和遞歸都會放大低性能代碼的影響力。因此必須從一開始就堅持好的慣例,而且在必要時進行優化。一些好的編程習慣如:
將一些代價昂貴的調用從重複代碼中移到外層來。如不變的try/catch塊和if-else分支就應該移到外層去。
錯誤:
for (var counter = 0; counter < 10000; counter++) {
try {
performAction();
} catch (e) {
alert('Failure: ' + e);
break;
}
}
正確:
try {
for (var counter = 0; counter < 10000; counter++) {
performAction();
}
} catch (e) {
alert('Failure: ' + e);
}
如果退出條件的計算很昂貴,也將它移出來。
錯誤:
try {
for (var counter = 0; counter < 10000; counter++) {
performAction();
}
} catch (e) {
alert('Failure: ' + e);
}
正確:
var target = document.getElementsByTagName('div').length;
for (var counter = 0; counter < target; counter++) {
performAction();
}
在上面的例子中,對函數getElementsByTagName的調用會導致每個迭代時都重新計算DOM樹一次。這應該並不是假想中必須完成的行爲。如果將這個調用移出循環的話,也要確保performAction調用不會修改循環的集合。
在同一個函數的多個循環中使用同一個循環變量,以避免多次聲明同一變量。
錯誤:
for (var counter = 0; counter < 10; counter++) {
performAction();
};
for (var counter = 0; counter < 10; counter++) {
performOtherAction();
};
正確:
var counter;
for (counter = 0; counter < 10; counter++) {
performAction();
};
for (counter = 0; counter < 10; counter++) {
performOtherAction();
};
<譯註: 改正後的代碼在第一個循環前即聲明瞭變量var counter,並在後面兩個循環中都使用同一變量。比錯誤的例子小了一次聲明。注意這個改動能提升的性能十分有限,如果這樣做會犧牲代碼的可讀性/可維護性,則顯見得不償失。>
5.7 DOM操作
5.7.1 批量修改
通過Javascript來操作DOM是十分昂貴的,而且爲引起瀏覽器重新生成文檔流。爲減少DOM樹更新和屏幕重繪次數,應該對DOM進行批量修改,一次更新。
避免拼接HTML到文檔元素的innerHTML屬性(例如使用 += 操作符),這會在DOM樹更新之前就產生一次大的字符串拼接。
5.7.2 循環引用
在DOM元素和Javascript對象之間的循環引用會造成一些老舊的瀏覽器的內存泄漏。儘管這一問題現在已大爲好轉,但對internet應用來講仍然必須仔細考慮。請參見"Understanding and Solving Internet Explorer Leak Patterns"