如何提升JavaScript的遞迴效率

來源:互聯網
上載者:User
Nicholas為您講解如何提升JavaScript的遞迴效率! 影響JavaScript效能的另外一個殺手就是遞迴,在上一節中提到採用 memoization技術可以最佳化計算數值的遞迴函式,但memoization不是萬能的,不是所有的遞迴函式都可以用memoization技術優 化,本文介紹了這些情況,並介紹瞭解決辦法,就是將遞迴轉換為迭代,同時需要注意,本文末尾介紹的方案不是最終的方案,還需要和上一節最佳化迴圈的方案綜合 起來才能達到最佳效果。 【原文】Speed up your JavaScript, Part 3 【作者】Nicholas C. Zakas 【譯者】明達 以下是對原文的翻譯: 遞迴是拖慢指令碼運行速度的大敵之一。太多的遞迴會讓瀏覽器變得越來越慢直到死掉或者莫名其妙的突然自動結束,所以我們一定要解決在JavaScript中 出現的這一系列效能問題。在這個系列文章的第二篇中,我曾經簡短的介紹了如何通過memoization技術來替代函數中太多的遞迴調用。 memoization是一種可以緩衝之前運算結果的技術,這樣我們就不需要重新計算那些已經計算過的結果。對於通過遞迴來進行計算的函 數,memoization簡直是太有用了。我現在使用的memoizer是由 Crockford寫的,主要應用在那些返回整數的遞迴運算中。當然並不是所有的遞迴函式都返回整數,所以我們需要一個更加通用的memoizer()函 數來處理更多類型的遞迴函式。 function memoizer(fundamental, cache) { cache = cache || {}; var shell = function(arg) { if (! (arg in cache)) { cache[arg] = fundamental(shell, arg); } return cache[arg]; }; return shell; } 這個版本的函數和Crockford寫的版本有一點點不同。首先,參數的順序被顛倒了,原有函數被設定為第一個參數,第二個參數是緩衝對象,為選擇性參數, 因為並不是所有的遞迴函式都包含初始資訊。在函數內部,我將緩衝對象的類型從數群組轉換為對象,這樣這個版本就可以適應那些不是返回整數的遞迴函式。在 shell函數裡,我使用了in操作符來判斷參數是否已經包含在緩衝裡。這種寫法比測試類型不是undefined更加安全,因為undefined是一 個有效傳回值。我們還是用之前提到的費伯納西數列來做說明: var fibonacci = memoizer(function(recur, n) { return recur(n - 1) + recur(n - 2); }, { "0": 0, "1": 1} ); 同樣的,執行fibonacci(40)這個函數,只會對原有的函數調用40次,而不是誇張的331,160,280次。memoization對於那些 有著嚴格定義的結果集的遞迴演算法來說,簡直是棒極了。然而,確實還有很多遞迴演算法不適合使用memoization方法來進行最佳化。 我在學校時的一位教授一直堅持認為,任何使用遞迴的情況,如果有需要,都可以使用迭代來代替。實際上,遞迴和迭代經常會被作為互相彌補的方法,尤其是在另 外一種出問題的情況下。將遞迴演算法轉換為迭代演算法的技術,也是和開發語言無關的。這對JavaScript來說是很重要的,因為很多東西在執行環境中是受 到限制的(the importance in JavaScript is greater, though, because the resources of the execution environment are so restrictive.)。讓我們回顧一個典型的遞迴演算法,比如說歸併排序,在JavaScript中實現這個演算法需要下面的代碼: function merge(left, right) { var result = []; while (left.length > 0 && right.length > 0) { if (left[0] < right[0]) { result.push(left.shift()); } else { result.push(right.shift()); } } return result.concat(left).concat(right); } //採用遞迴實現的歸併排序演算法 function mergeSort(items) { if (items.length == 1) { return items; } var middle = Math.floor(items.length / 2), left = items.slice(0, middle), right = items.slice(middle); return merge(mergeSort(left), mergeSort(right)); } 調用mergeSort()函數處理一個數組,就可以返回經過排序的數組。注意每次調用mergeSort()函數,都會有兩次遞迴調用。這個演算法不可以 使用memoization來進行最佳化,因為每個結果都只計算並使用一次,就算緩衝了結果也沒有什麼用。如果你使用mergeSort()函數來處理一個 包含100個元素的數組,總共會有199次調用。1000個元素的數組將會執行1999次調用。在這種情況下,我們的解決方案是將遞迴演算法轉換為迭代算 法,也就是說要引入一些迴圈(關於演算法,可以參考這篇《List Processing: Sort Again, Naturally》): // 採用迭代實現的歸併排序演算法 function mergeSort(items) { if (items.length == 1) { return items; } var work = []; for (var i = 0, len = items.length; i < len; i++) { work.push([items[i]]); } work.push([]); //in case of odd number of items for (var lim = len; lim > 1; lim = (lim + 1) / 2) { for (var j = 0, k = 0; k < lim; j++, k += 2) { work[j] = merge(work[k], work[k + 1]); } work[j] = []; //in case of odd number of items } return work[0]; } 這個歸併排序演算法實現使用了一系列迴圈來代替遞迴進行排序。由于歸並排序首先要將數組拆分成若干只有一個元素的數組,這個方法更加明確的執行了這個操作, 而不是通過遞迴函式隱晦的完成。work數組被初始化為包含一堆只有一個元素數組的數組。在迴圈中每次會合并兩個數組,並將合并後的結果放回 work數組中。當函數執行完成後,排序的結果會通過work數組中的第一個元素返回。在這個歸併排序的實現中,沒有使用任何遞迴,同樣也實現了這個算 法。然而,這樣做卻引入了大量的迴圈,迴圈的次數基於要排序的數組中元素的個數,所以我們可能需要使用在上篇討論過的技術來進行修訂,處理這些額外開銷。 總結一下基本原則,不管是什麼時候使用遞迴的時候都應該小心謹慎。memoization和迭代是代替遞迴的兩種解決方案,最直接的結果當然就是避免那個 提示指令碼失控的對話方塊。

 

未知來源 歡迎告之

 

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.