這是阮一峯大神的《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信息!
好了,解釋完畢!其實,本片文章只是阮大神那本書那節的一個詳細解讀啦~