可維護(hù)性
JS從完成小的網(wǎng)頁特效和驗證到現(xiàn)在要處理各種復(fù)雜的邏輯,頁面中代碼的數(shù)量也是成倍的增長。這就讓編寫可維護(hù)的代碼變得越來越重要了。
可維護(hù)代碼的特點
可理解性
直觀性
可適應(yīng)性
可拓展性
可調(diào)試性
代碼約定
注釋和縮進(jìn)。
變量命名以名詞開始,函數(shù)名命名以動詞開始,返回布爾值的函數(shù)以is開頭。
松散耦合
不同語言間的緊耦合
CSS,JS,HTML他們天生就需要交互。有時我們不得不在一個里面修改另一個,這就造成了緊耦合,出了錯調(diào)試起來非常麻煩。盡量做到分開。
- HTML里不要有JS這個比較好做到。
- JS中有時需要動態(tài)添加HTML, 這個盡量交給JSP,PHP等頁面來做,最后用JS加進(jìn)去就好。
- 盡量不要在HTML中寫CSS
- JS中修改樣式最好使用類的方式,這樣所有樣式問題都可以追溯到CSS文件中。
事件處理和應(yīng)用邏輯間的緊耦合
如果將應(yīng)用邏輯直接寫在事件處理函數(shù)中,不管是調(diào)試還是重用都會很不方便,將應(yīng)用邏輯提出來,在事件處理中調(diào)用會是一個比較好的方法。有這么幾個原則:
- 不要將event對象傳給其他方法,只傳來自event對象中所需的數(shù)據(jù)
- 任何應(yīng)用邏輯層面的操作都應(yīng)該可以在不執(zhí)行任何事件處理程序的情況下執(zhí)行
- 任何事件處理程序都應(yīng)該處理事件,然后將時間轉(zhuǎn)交給應(yīng)用邏輯
編程實踐
尊重對象所有權(quán)
在其他語言中,在沒有源碼的時候,類和對象一般是不可變的,就算可以也是添加屬性和方法,不可能覆蓋已有的。但是在JS中,一切都可以修改,覆蓋,重寫,想干啥干啥。這有時就會造成很嚴(yán)重的問題。
尊重對象的所有權(quán)就是說不是屬于你的對象不要隨便修改,即便你覺得這樣會很方便。擁有對象的意思是,這些對象由你創(chuàng)建,由你維護(hù)。像Array,document這些對象顯然不是你的,就不要嘗試修改他們。
避免全局變量
var name = "Nicholas";
function sayName(){
alert(name);
}
在這里創(chuàng)建了兩個全局變量name和sayName。
這樣看起來沒什么,但是是有問題的,比如變量name覆蓋了window的name屬性,其他需要這個屬性的功能就會因此獲得錯誤的信息。
var MyApplication = {
name: "Nicholas",
sayName: function(){
alert(this.name);
}
};
MyApplication.sayName();
這樣大家都使用一個全局變量把自己封裝起來,使用和共存和調(diào)試都很方便:
var Exia = {};
Exia.ProJS = {};
Exia.ProJS.EventUtil = {};
Exia.ProJS.CookieUtil = {};
避免與null進(jìn)行比較
比如:
function sortArray(values){
if (values != null){
values.sort(comparator);
}
}
function sortArray(values){
if (values instanceof Array){
values.sort(comparator);
}
}
這時明顯是應(yīng)該使用下面這種判斷更加合理。大多數(shù)情況也應(yīng)該這樣判斷。如果是引用類型就使用instanceof,基本類型就使用typeof。
使用常量
比如URL,設(shè)置的量之類的,放到一個常量里,以后要修改就只需要改一次就好。
var Constants = {
INVALID_VALUE_MSG: "Invalid value!",
INVALID_VALUE_URL: "/errors/invalid.php"
};
重復(fù)值
用戶界面字符串
URLs
任意可能會更改的值
以上這些值提出來會比較方便
性能
曾經(jīng)JS是一種解釋型語言,執(zhí)行速度比編譯型語言慢的多,Chrome率先實現(xiàn)了將JS編為本地機(jī)器碼執(zhí)行的瀏覽器。后來主流的瀏覽器都這么做了。即便是這樣,我們還是要避免寫出低效率的代碼。
注意作用域
隨著作用域鏈中作用域的增加,訪問當(dāng)前作用域以外的變量所花的時間也越多,越遠(yuǎn)越耗時。
避免全局查找
當(dāng)訪問全局變量時就是最慢的,如果在一個局部環(huán)境中要多次訪問全局的變量,先用一個局部變量把它保存起來比較好。
function updateUI(){
var doc = document;
var imgs = doc.getElementsByTagName("img");
for (var i=0, len=imgs.length; i < len; i++){
imgs[i].title = doc.title + " image " + i;
}
var msg = doc.getElementById("mydiv");
msg.innerHTML = "Update complete.";
}
避免with語句
with起初的目的是為了讓開發(fā)人員少寫點字
function updateBody(){
alert(document.body.tagName);
document.body.innerHTML = "Hello world!";
}
function updateBody(){
with(document.body){
alert(tagName);
innerHTML = "Hello world!";
}
}
function updateBody(){
var body = document.body;
alert(body.tagName);
body.innerHTML = "Hello world!";
}
雖然確實方便了,不過這樣會增加作用域鏈的長度,會更慢,使用新添加局部變量的辦法一般可以達(dá)到目的。
選擇正確方法
和其他語言一樣,性能問題的一部分是和用于解決問題的算法或方法有關(guān)的。
避免不必要的屬性查找
對于變量和數(shù)組中元素的訪問都是O(1)級別的操作,但是要訪問對象屬性的操作則是O(n)級別的。
一旦要多次用到同一個對象的同一個屬性就建議把它保存起來,這樣第一次是O(n),以后的就是O(1)了。
優(yōu)化循環(huán)
減值迭代,從最大值開始減到0,這樣的循環(huán)條件一般都會比增值迭代要高效。因為每次循環(huán)和終止條件對比時減值對比都是和常量0進(jìn)行對比,而增量都是和一個變量甚至是一個對象的屬性做對比,這樣每次循環(huán)累計起來就會有一定的差距了。
簡化終止條件,每次循環(huán)都會和終止條件對比,如果你的終止條件直接是一個對象的一個屬性,那每次都要進(jìn)行O(n)的查找。
簡化循環(huán)體,這個不用說啥了。
展開循環(huán)
當(dāng)循環(huán)次數(shù)確定時,使用多次函數(shù)調(diào)用而不是循環(huán)會更快。
不過一般循環(huán)的次數(shù)都是不確定的呢,
在處理大數(shù)據(jù)集時,可以使用一個叫Duff裝置的技術(shù)。這個技術(shù)將循環(huán)拆成每8次一組。在處理大量數(shù)據(jù)時提升效果顯著。畢竟每8次才有一次處理循環(huán)的開銷。當(dāng)然數(shù)據(jù)量小的時候就并不劃算了。
var iterations = Math.floor(values.length / 8);
var leftover = values.length % 8;
var i = 0;
if (leftover > 0){
do {
process(values[i++]);
} while (--leftover > 0);
}
do {
process(values[i++]);
process(values[i++]);
process(values[i++]);
process(values[i++]);
process(values[i++]);
process(values[i++]);
process(values[i++]);
process(values[i++]);
} while (--iterations > 0);
其他提升性能的方法
原生方法較快
Switch語句較快
位運算符較快
最小化語句數(shù)
簡單來講,就是完成多個操作的單個語句要比完成單個操作的多個語句快。
多個變量聲明
var count = 5,
color = "blue",
values = [1,2,3],
now = new Date();
使用數(shù)組和對象字面量
優(yōu)化DOM交互
最小化現(xiàn)場更新
一旦你需要訪問的DOM部分是已經(jīng)顯示頁面的一部分,那么你就是在進(jìn)行一次現(xiàn)場更新。每次現(xiàn)場更新都有一個很大的性能懲罰,瀏覽器要重新計算各種尺寸和屬性。所以更新次數(shù)要盡可能少,動作要盡可能的小。
比如你要給一個ul添加多個li,那把所有l(wèi)i準(zhǔn)備好一起塞到ul里就比一個一個塞要好。
使用innerHTML
用innerHTML來創(chuàng)建DOM節(jié)點,不僅寫起來舒服,執(zhí)行起來也快。這個同樣,把字符串拼好了再一次性加到頁面里,這樣才高效。
使用事件代理
就是在整個文檔上添加事件,文檔中各個事件冒泡到文檔上統(tǒng)一處理。
注意HTMLCollection
任何時候訪問這種類型的變量都是在文檔上進(jìn)行了一次查詢,這是個很昂貴的操作。尤其是在循環(huán)中。。。
var images = document.getElementsByTagName("img"),
image,
i,
len;
for (i=0, len=images.length; i < len; i++){
image = images[i];
//其他操作
}
這里既在循環(huán)終止條件中避免了對HTMLCollection的多次訪問,又在循環(huán)體里使用image儲存了當(dāng)前要使用的元素,從而避免了多次訪問HTMLCollection
部署
構(gòu)建過程
我們開發(fā)Web應(yīng)用時JS文件是按照可維護(hù)性優(yōu)先的原則組織的,但是這樣會存在一些問題:
- 把帶有自己注釋的代碼放上去,別人就更容易知道你的意圖,有可能被人找到漏洞
- 書寫代碼時我們保證代碼簡單易讀,但是對于性能是不利的。瀏覽器并不能從空格和各式各樣的變量中獲得什么有用的信息
所以使用構(gòu)建工具將多個JS合并壓縮就很必要了。這樣的工具有很多,挑順手的用。
驗證
JSLint可以在線驗證JS代碼中的潛在語法錯誤。
在開發(fā)周期中添加這個環(huán)節(jié)可以作為發(fā)現(xiàn)代碼潛在問題的辦法。
把驗證添加到構(gòu)建過程中是很常用且推薦的做法。
壓縮
文件壓縮
如果每次都把帶有各種空格和注視的JS通過網(wǎng)絡(luò)傳送到瀏覽器,顯然有太多不必要的開銷。
在部署前使用壓縮工具壓縮JS文件也是必要的。
HTTP壓縮
對于服務(wù)器向瀏覽器傳送文件這個過程,也是可以優(yōu)化的,服務(wù)器和瀏覽器現(xiàn)在都有壓縮和解壓縮功能。