尾遞歸以及在瀏覽器中的實現

這裏寫圖片描述

這是阮一峯大神的《ES6標準入門》一書中寫道的,沒看過這節的童鞋可以狠戳這裏

當時我就很不理解:除了最後一次調用不是尾遞歸,其餘 y 大於 0 的時候,都是尾遞歸;那麼,按照書“尾調用優化”上所說的,尾遞歸本身已經是“尾調用優化”過了,爲什麼還沒報Uncaught RangeError: Maximum call stack size exceeded(…)——調用棧溢出這種錯誤?

於是,“我曾經問遍整個世界”,然而並沒有“從來沒得到答案”,這裏我要感謝我的胡桃夾子給了我靈感��!至於誰是胡桃夾子,嘿嘿,我不告訴你,相信你以後會知道他的!

好了,廢話不多說,開始分析吧。。

首先,先來了解一下什麼叫棧溢出

這裏寫圖片描述

“同一時刻只能有一個函數在運行”主要是由於JS單線程的。

另外,值得提醒的是firstFn並不是尾調用!因爲,這個函數最後執行的是return undefined,只有一個函數內最後執行的語句是return xxx()纔算尾調用,因此firstFn的棧對象,在secondFn執行時,並沒有被釋放掉。

其實,圖一里面的這個函數是尾遞歸的,但是很坑爹的是瀏覽器沒有實現尾遞歸,產生省內存的效果

你不信,你可以複製下面這段代碼到你的編輯器,打開Chrome瀏覽器的Sources看看調用棧中每次debugger的變化。

function sum(x, y) {
  if (y > 0) {
    debugger;
    return sum(x + 1, y - 1);
  } else {
    return x;
  }
}

sum(1, 100);

你會發現了每次debugger都會產生添加新的調用棧信息,是不是很神奇?!按正常來說,我尾遞歸了,就應該省內存!但沒有,事實上是瀏覽器發現尾遞歸之後,沒有進行尾遞歸優化!

於是,在阮大神的書裏面附上了本應該瀏覽器內置的“尾遞歸優化的實現”,其思路是遞歸執行轉爲循環執行

再次,複製下面這段代碼到你的編輯器,打開Chrome瀏覽器的Sources看看調用棧中每次debugger的變化。

function tco(f) {
  var value;
  var active = false;
  var accumulated = [];

  return function accumulator() {
    accumulated.push(arguments);
    if (!active) {
      active = true;
      while (accumulated.length) {
        value = f.apply(this, accumulated.shift());
      }
      active = false;
      return value;
    }
  };
}

var sum = tco(function(x, y) {
  if (y > 0) {
    debugger;
    return sum(x + 1, y - 1)
  }
  else {
    return x
  }
});

sum(1, 100000)

這次就可以看到Sources的調用棧在每次debugger後,只保留了一條sum信息!

好了,解釋完畢!其實,本片文章只是阮大神那本書那節的一個詳細解讀啦~

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章