Nicholas為您講解如何提升JavaScript函數的運行速度!
這篇是Nicholas討論如果防止指令碼失控的第二篇,主要討論了如何重構嵌套循 環、遞迴,以及那些在函數內部同時執行很多子操作的函數。基本的思想和上一節trunk()那個例子一致,如果幾個操作沒有特定的執行順序,而且互相不是依賴關係,我們就可以通過非同步呼叫的方式加以執行,不止可以減少執行的次數,還可以防止指令碼失控。本文還介紹了通過memoization技術取代遞迴的 方法。
【原文標題】Speed up your JavaScript, Part 2
【原文作者】Nicholas C. Zakas
上周我在《too much happening in a loop》(譯文)這篇文章中介紹了JavaScript已耗用時間過長的第一個原因。相似的情況有時也出現在函數的定義上,函數也可能因為使用不當而過載使用。通常情況是函數內包含了過多的迴圈(不是在迴圈中執行了過多的內容),太多的遞迴,或者只不過是太多不相干但又要一起執行的操作。
太 多的迴圈經常是以嵌套的形式出現,這種代碼會一直佔用JavaScript引擎直至迴圈結束。這方面有一個非常著名的例子,就是使用冒泡演算法排序。由於 JavaScript有內建的sort()方法,我們沒有必要使用這種方式進行排序,但我們可以藉助這個演算法理解嵌套迴圈佔用資源的癥結所在,從而避免類 似情況的發生。下面是一個在JavaScript使用冒泡排序法的典型例子:
function bubbleSort(items) {
for (var i = items.length - 1; i >= 0; i--) {
for (var j = i; j >= 0; j--) {
if (items[j] < items[j - 1]) {
var temp = items[j];
items[j] = items[j - 1];
items[j - 1] = temp;
}
}
}
}
回 憶一下你在學校學習的電腦知識,你可能記得冒泡排序法是效率最低的排序演算法之一,原因是對於一個包含n個元素的數組,必須要進行n的平方次的迴圈操作。 如果數組中的元素數非常大,那麼這個操作會持續很長時間。內迴圈的操作很簡單,只是負責比較和交換數值,導致問題的最大原因在於迴圈執行的次數。這會導致 瀏覽器運行異常,潛在的直接結果就是那個指令碼失控的警告對話方塊。
幾年前,Yahoo的研究員Julien Lecomte寫了一篇題為《Running CPU Intensive JavaScript Computations in a Web Browser》的文章,在這篇文章中作者闡述了如何將很大的javaScript操作分解成若干小部分。其中一個例子就是將冒泡排序法分解成多個步驟,每個步驟只遍曆一次數組。我對他的代碼做了改進,但方法的思路還是一樣的:
function bubbleSort(array, onComplete) {
var pos = 0; (function() {
var j, value;
for (j = array.length; j > pos; j--) {
if (array[j] < array[j - 1]) {
value = data[j];
data[j] = data[j - 1];
data[j - 1] = value;
}
}
pos++;
if (pos < array.length) {
setTimeout(arguments.callee, 10);
} else {
onComplete();
}
})();
}
這 個函數藉助一個非同步管理器來實現了冒泡演算法,在每次遍曆數組以前暫停一下。onComplete()函數會在數組排序完成後觸發,提示使用者資料已經準備 好。bubbleSort()函數使用了和chunk()函數一樣的基本技術(參考我的上一篇文章),將行為封裝在一個匿名函數中,將 arguments.callee傳遞給setTimeout()以達到重複操作的目的,直至排序完成。如果你要將嵌套的迴圈拆解成若干個小步驟,以達到 解放瀏覽器的目的,這個函數提供了不錯的指導意見。
相似的問題還包括過多的遞迴。每個額外的遞迴調用都會佔用更多的記憶體,從而減慢瀏覽器的運行。惱人的是,你可能在瀏覽器發出指令碼失控警告之前,就耗盡了系統的記憶體,導致瀏覽器處於停止回應的狀態。Crockford在部落格上曾經對這個問題進行過深入的討論。他當時使用的例子,就是用遞迴產生一個斐波那契數列。
function fibonacci(n) {
return n < 2 ? n: fibonacci(n - 1) + fibonacci(n - 2);
};