加速你的 JavaScript,第 1 部分
在我的上一篇文章中,我談到了可怕的長時間運行的腳本對話框在瀏覽器中顯示的條件。當瀏覽器執行了太多語句(Internet Explorer)或 JavaScript 引擎已經運行了特定時間(其他)時,瀏覽器將停止執行腳本。當然,問題不在於瀏覽器檢測長時間運行腳本的方式,而在於腳本執行時間過長。
腳本執行時間過長的主要原因有四個:
- 一個循環中發生了太多事情。
- 函數中發生了太多事情。
- 遞歸過多。
- 過多的 DOM 交互。
在這篇文章中,我將關注第一個問題:循環中發生的事情太多了。循環迭代同步發生,因此完全執行循環所需的時間與迭代次數直接相關。因此,有兩種情況會導致循環運行時間過長並鎖定瀏覽器。第一個是循環體在每次迭代中做的太多,第二個是循環運行了太多次。這些可能會導致瀏覽器鎖定並顯示長時間運行的腳本警告。
解開這個問題的秘訣是評估循環來回答兩個問題:
- 循環是否必須同步執行?
- 處理循環數據的順序重要嗎?
如果這兩個問題的答案都是“否”,那麼您可以選擇一些方法來拆分循環中完成的工作。關鍵是仔細檢查代碼以回答這些問題。一個典型的循環如下所示:
for(var i=0; i < items.length; i++){
process(items[i]);
}
這看起來還不錯,但可能需要很長時間,具體取決於運行 process()
所需的時間 功能。如果在循環之後沒有立即取決於循環執行結果的代碼,那麼第一個問題的答案是“否”。您可以清楚地看到,循環中的每次迭代都不依賴於前一次迭代,因為它一次只處理一個值,所以第二個問題的答案是“否”。這意味著可以以一種可以釋放瀏覽器並避免長時間運行的腳本警告的方式拆分循環。
在 Professional JavaScript, Second Edition 中,我介紹了以下函數來處理可能需要大量執行時間的循環:
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()
函數旨在處理小塊數組(因此得名),並接受三個參數:“待辦事項”列表,處理每個項目的函數,以及用於設置 this
process()
內 功能。計時器用於延遲每個項目的處理(在這種情況下為 100 毫秒,但可以根據您的具體用途隨意更改)。每次通過,數組中的第一項被刪除並傳遞給 process()
功能。如果仍有待處理的項目,則使用另一個計時器重複該過程。可以重寫前面描述的循環來使用這個函數:
chunk(items, process);
請注意,該數組用作隊列,因此每次循環都會更改。如果要保持數組的原始狀態,有兩種選擇。首先,您可以使用 concat()
在將數組傳遞給函數之前克隆數組的方法:
chunk(items.concat(), process);
第二個選項是更改 chunk()
自動執行此操作的函數:
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);
}
請注意,這種方法比僅保存索引並在現有數組中移動更安全,因為傳入的數組的內容可能會在下一個計時器運行之前發生變化。
chunk()
這裡介紹的方法只是如何處理循環性能的一個起點。您當然可以更改它以提供更多功能,例如,在處理完所有項目後執行的回調方法。無論您可能需要或不需要對函數進行更改,它都是一種通用模式,可以幫助優化數組處理以避免長時間運行的腳本警告。
翻譯
- 中文(簡體)