DIV CSS 佈局教程網

 DIV+CSS佈局教程網 >> 網頁腳本 >> JavaScript入門知識 >> 關於JavaScript >> JavaScript如何重構嵌套循環和遞歸
JavaScript如何重構嵌套循環和遞歸
編輯:關於JavaScript     

網頁制作Webjx文章簡介:這篇是Nicholas討論如果防止腳本失控的第二篇,主要討論了如何重構嵌套循環、遞歸,以及那些在函數內部同時執行很多子操作的函數。基本的思想和上一節trunk()那個例子一致,如果幾個操作沒有特定的執行順序,而且互相不是依賴關系,我們就可以通過異步調用的方式加以執

這篇是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()以達到重復操作的目的,直至排序完成。如果你要將嵌套的循環拆解成若干個小步驟,以達到解放浏覽器的目的,這個函數提供了不錯的指導意見。

網頁制作Webjx文章簡介:這篇是Nicholas討論如果防止腳本失控的第二篇,主要討論了如何重構嵌套循環、遞歸,以及那些在函數內部同時執行很多子操作的函數。基本的思想和上一節trunk()那個例子一致,如果幾個操作沒有特定的執行順序,而且互相不是依賴關系,我們就可以通過異步調用的方式加以執

相似的問題還包括過多的遞歸。每個額外的遞歸調用都會占用更多的內存,從而減慢浏覽器的運行。惱人的是,你可能在浏覽器發出腳本失控警告之前,就耗盡了系統的內存,導致浏覽器處於停止響應的狀態。Crockford在博客上曾經對這個問題進行過深入的討論。他當時使用的例子,就是用遞歸生成一個斐波那契數列。

function fibonacci(n) {
return n < 2 ? n: fibonacci(n - 1) + fibonacci(n - 2);
};

按照Crockford的說法,執行fibonacci(40)這條語句將重復調用自身331160280次。避免使用遞歸的方案之一就是使用memoization技術,這項技術可以獲取上一次調用的執行結果。Crockford介紹了下面這個函數,可以為處理數值的函數增加這項功能:

function memoizer(memo, fundamental) {
var shell = function (n) {
var result = memo[n];
if (typeof result !== 'number') {
result = fundamental(shell, n);
memo[n] = result;
}
return result;
};
return shell;
};

接下來將這個函數應用在斐波那契數列生成器上:

var fibonacci = memoizer([0, 1],
function(recur, n) {
return recur(n - 1) + recur(n - 2);
}); 

這時如果我們再次調用fibonacci(40),只會重復調用40次,和原來相比提高得非常多。memoization的原理,概括起來就一句話,同樣的結果,你沒有必要計算兩次。如果一個結果你可能會再次使用,把這個結果保存起來,總比重新計算一次來的快。

最後一個可能讓函數執行緩慢的原因,就是我們之前提到過的,函數裡面執行了太多的內容,通常是因為使用了類似下面的開發模式:

function doAlot() {
  doSomething(); 
  doSomethingElse(); 
  doOneMoreThing();
}

在這裡要執行三個不同的函數,請注意,無論是哪個函數,在執行過程中都不依賴其他的函數,他們在本質是相對獨立的,只是需要在一個特定時間逐一執行而已。同樣,你可以使用類似chunk()的方法來執行一系列函數,而不會導致鎖定浏覽器。

function schedule(functions, context) {
setTimeout(function() {
   var process = functions.shift();
   process.call(context);
   if (functions.length > 0) {
       setTimeout(arguments.callee, 100);
   }
},
100);
}

schedule函數有兩個參數,一個是包含要執行函數的數組,另外一個是標明this所屬的上下文對象。函數數組以隊列方式實現,Timer事件每次觸發的時候,都會將隊列最前面的函數取出並執行,這個函數可以通過下面的方式執行一系列函數:

schedule([doSomething, doSomethingElse, doOneMoreThing], window);

很希望各個JavaScript的類庫都增加類似這樣的進程處理函數。YUI在3.0時就已經引入了Queue對象,可以通過timer連續調用一組函數。

無論現有的技術可以幫助我們將復雜的進程拆分到什麼程度,對於開發者來說,使用這種方法來理解並確定腳本失控的瓶頸是非常重要的。無論是太多的循環、遞歸還是其他的什麼,你現在應該知道如果處理類似的情況。但要記住,這裡提到的技術和函數只是起到拋磚引玉的作用,在實際的應用中,你應該對它們加以改進,這樣才能發揮更大的作用。

XML學習教程| jQuery入門知識| AJAX入門| Dreamweaver教程| Fireworks入門知識| SEO技巧| SEO優化集錦|
Copyright © DIV+CSS佈局教程網 All Rights Reserved