根據(jù)Nicholas的說法,有四種代碼會拖慢腳本的運行,并最終導(dǎo)致腳本失控。分別是次數(shù)過多的同步循環(huán)、龐大的函數(shù)體、不恰當(dāng)?shù)倪f歸和不合理的DOM調(diào)用。這篇著重講第一個原因。最后給出了一個開發(fā)模式,替換傳統(tǒng)的循環(huán)結(jié)構(gòu),可以完全避免腳本失控的狀況發(fā)生。
原文標(biāo)題:Speed up your JavaScript, Part 1 原文作者:Nicholas C. Zakas
在我 上一篇帖子(譯文)中,談到了各個瀏覽器究竟會在什么情況下彈出腳本失控提示,對于Internet Explorer來說,當(dāng)瀏覽器執(zhí)行了數(shù)量過多的語句時就會停止執(zhí)行腳本,而其他的瀏覽器,則是持續(xù)執(zhí)行腳本超過一定時間的時候就會給出提示。而我們要探討的核心問題,不是這些瀏覽器如果探測失控的腳本,而是我們?nèi)绾尾趴梢宰屇_本運行的更快一些,從而避免這些警告。
腳本失控基本上有以下四個方面的原因:
- 在循環(huán)中執(zhí)行了太多的操作。
- 臃腫的函數(shù)體
- 過多的遞歸
- 過多的DOM調(diào)用
在這篇帖子中,我將會把重點放到第一條上:循環(huán)中的過多操作。循環(huán)的操作是同步進(jìn)行的,所以執(zhí)行一個循環(huán)所花費的時間完全取決于循環(huán)的次數(shù)。因此有兩種情況會導(dǎo)致循環(huán)執(zhí)行的時間過長,并直接導(dǎo)致鎖定瀏覽器。一是循環(huán)體中包含了太多的操作,二是循環(huán)的次數(shù)過多。這兩種情況都能直接導(dǎo)致鎖定瀏覽器,并顯示腳本失控的提示。
解決這個問題的訣竅就是用下面這兩個問題來評估每個循環(huán):
- 這個循環(huán)必須要同步執(zhí)行么?
- 循環(huán)里面的數(shù)據(jù),必須要按順序執(zhí)行么?
如果兩個問題的答案都是否定的話,你就可以選擇將循環(huán)里的操作進(jìn)行分解。關(guān)鍵是要根據(jù)代碼的具體環(huán)境確定上面兩個問題的答案。一個典型的循環(huán)可能像下面這個樣子:
for(var i=0; i < items.length; i++){ process(items[i]); }
乍一看這個循環(huán)并沒有太大的問題,是不是會運行很長時間完全取決于循環(huán)的次數(shù)。如果緊接循環(huán)后沒有其他代碼在執(zhí)行的時候需要依賴于循環(huán)的結(jié)果,那么對于第一個問題的答案就是“不”。你還可以發(fā)現(xiàn),循環(huán)每次只處理一個數(shù)值,而且不依賴于上一次循環(huán)的結(jié)果,所以對于第二個問題的答案同樣也是否定的。這就意味著,循環(huán)可以通過某種方式進(jìn)行拆解,不會導(dǎo)致鎖定瀏覽器而顯示腳本失控的提示。
在《Professional JavaScript, Second Edition》這本書中,對于那些執(zhí)行次數(shù)非常巨大的虛幻,我推薦使用下面的方式來處理:
function chunk(array, process, context){ setTimeout(function(){ var item = array.shift(); process.call(context, item);
if (array.length > 0){ setTimeout(arguments.callee, 100); } }, 100); }
chunk()函數(shù)的用途就是將一個數(shù)組分成小塊處理(這也是名字的由來),我們可以傳遞三個參數(shù)。要處理的數(shù)組對象、處理函數(shù)以及一個可選的上下文變量,用于設(shè)置process()函數(shù)中對應(yīng)的this對象。第一個timer用于處理操作之間的延時(這里設(shè)置為100毫秒,大家可以根據(jù)實際需要自行修改)。每次執(zhí)行這個函數(shù),都會將數(shù)組中的第一個對象取出,并傳給process()函數(shù)進(jìn)行操作,如果這時process()中還有未處理完的對象,另外一個timer就會啟動,用于重復(fù)等待。上面提到的循環(huán),可以通過下面的方法使用這個函數(shù):
chunk(items, process);
需要注意的是,在這里數(shù)組采用了隊列(queue)的形式,而且在循環(huán)的過程中,每次都會發(fā)生修改。如果你要修改數(shù)組的原始狀態(tài),這里介紹兩種途徑:一種是通過concat()函數(shù),在傳遞之前,建立一個當(dāng)前數(shù)組的副本:
chunk(items.concat(), process);
另外一種選擇是直接修改chunk()函數(shù),直接在函數(shù)內(nèi)部進(jìn)行修改:
function chunk(array, process, context){ var items = array.concat(); //clone the array setTimeout(function(){ var item = items.shift(); process.call(context, item);
if (items.length > 0){ setTimeout(arguments.callee, 100); } }, 100); }
注意這種方法要比只保存一個索引安全的多,因為數(shù)組的內(nèi)容在下次計時器生效之前可能會發(fā)生變化。
這里提到的chunk()函數(shù),只是優(yōu)化循環(huán)性能的一個起點。你可以根據(jù)需要不斷改進(jìn)它,讓它擁有更多的功能。比如說,在數(shù)組中所有對象都處理完成以后,可以增加一個函數(shù)回調(diào)。無論你是否會按照這種方式對函數(shù)進(jìn)行修改,這只是一種JavaScript的代碼開發(fā)模式,可以幫助優(yōu)化數(shù)組的處理性能,還可以避免那個腳本失控的警告。
本文鏈接:http://m.95time.cn/tech/web/2009/6434.asp
出處:七月佑安
責(zé)任編輯:bluehearts
◎進(jìn)入論壇網(wǎng)頁制作、WEB標(biāo)準(zhǔn)化版塊參加討論,我還想發(fā)表評論。
|