JavaScript 中的定時數組處理
不久前,我在博客中介紹了一種異步處理 JavaScript 數組以避免鎖定瀏覽器(並進一步避免顯示長時間運行的腳本對話框)的方法。 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);
}
此方法是一個示例實現,並且有幾個性能問題。首先,延遲的大小對於大型數組來說太長了。對包含 100 個項目的數組使用 100 毫秒延遲意味著處理至少需要 10,000 毫秒或 10 秒。延遲實際上應該減少到 25 毫秒。這是我建議避免瀏覽器計時器分辨率問題的最小延遲。 Internet Explorer 的計時器分辨率為 15 毫秒,因此指定 15 毫秒將為 0 或 15,具體取決於上次設置系統時間的時間。你真的不想要 0,因為在下一批 JavaScript 代碼開始處理之前,這沒有給 UI 更新足夠的時間。指定 25 毫秒的延遲可以保證至少延遲 15 毫秒,最多延遲 30 毫秒。
儘管如此,延遲 25 毫秒,處理一個包含 100 個項目的數組至少需要 2,500 毫秒或 2.5 秒,仍然相當長。實際上,chunk()
的全部意義 是為了確保您不會達到長時間運行的腳本限制。問題是長時間運行的腳本限制在用戶體驗到 UI 凍結之後很久才開始。
改進空間
Jakob Nielsen 在他的論文中指出,響應時間:三個重要限制 ,即 0.1 秒(100 毫秒)“大約是讓用戶感覺到系統正在即時做出反應的極限,這意味著除了顯示結果之外不需要特殊的反饋。”由於在 JavaScript 執行時 UI 無法更新,這意味著您的 JavaScript 代碼的連續執行時間不應超過 100 毫秒。此限制遠小於網絡瀏覽器中的長時間運行腳本限制。
我實際上會更進一步,並說任何腳本都不應該連續運行超過 50 毫秒。在此之上,您的趨勢接近極限,可能會無意中影響用戶體驗。我發現 50 毫秒對於大多數 JavaScript 操作來說已經足夠了,並且在代碼執行時間過長時是一個很好的截止點。
使用這些知識,您可以創建 chunk()
的更好版本 功能:
//Copyright 2009 Nicholas C. Zakas. All rights reserved.
//MIT Licensed
function timedChunk(items, process, context, callback){
var todo = items.concat(); //create a clone of the original
setTimeout(function(){
var start = +new Date();
do {
process.call(context, todo.shift());
} while (todo.length > 0 && (+new Date() - start < 50));
if (todo.length > 0){
setTimeout(arguments.callee, 25);
} else {
callback(items);
}
}, 25);
}
這個新版本的函數插入了一個 do-while
循環將持續處理項目,直到沒有其他項目要處理或代碼已執行 50 毫秒。一旦該循環完成,邏輯完全相同:如果要處理更多項目,則創建一個新計時器。添加回調函數可以在處理完所有項目時進行通知。
我設置了一個測試來比較這兩種方法,因為它們處理了一個包含 500 個項目的數組,結果非常驚人:timedChunk()
經常花費不到 chunk()
10% 的時間 完全處理所有項目。自己試試吧。請注意,這兩個進程都不會導致瀏覽器顯示為凍結或鎖定。
結論
即使原來的 chunk()
方法對於處理小型數組很有用,但在處理大型數組時會影響性能,因為完全處理數組需要花費大量時間。新的 timedChunk()
方法更適合在不影響用戶體驗的情況下,在最短的時間內處理大型數組。