關於尾呼叫和尾遞迴
1.
(1)尾呼叫:指某個函式的最後一步是呼叫另一個函式。 例如:
function a(n){
return b(n);
}
(最後一步呼叫並不意味著在函式的尾部,只要是最後一步即可)
function a(n){
if(n>1){
return b(n);
}
return c(n);
}
(2)什麼樣的情況不是尾呼叫:
情況一:
function f(x){
let y = g(x);
return y;
}
解釋:呼叫g之後,還有賦值操作,故不屬於尾呼叫。
情況二:
function f(x){ return g(x) + 1; }
解釋:也是在呼叫g之後,還有後續別的操作,故也不屬於尾呼叫。
情況三:
function f(x){
g(x);
}
解釋:情況三相當於:
function f(x){
g(x);
return undefined;
}
(3)尾呼叫的優化:(只保留記憶體函式的呼叫幀)
《1》:尾呼叫不同於其他呼叫的點在於他特殊的呼叫位置。
《2》:函式呼叫會在記憶體形成一個“呼叫記錄”,又稱“呼叫幀”(call frame),儲存呼叫位置和內部變數等資訊。如果在函式A的內部呼叫函式B,那麼在A的呼叫幀上方,還會形成一個B的呼叫幀。等到B執行結束,將結果返回到A,B的呼叫幀才會消失。如果函式B內部還呼叫函式C,那就還有一個C的呼叫幀,以此類推。所有的呼叫幀,就形成一個“呼叫棧”(call stack)
《3》:尾呼叫由於是函式的最後一步操作,所以不需要保留外層函式的呼叫幀,因為呼叫位置、內部變數等資訊都不會再用到了,只要直接用內層函式的呼叫幀,取代外層函式的呼叫幀就可以了.
《4》:如果所有函式都是尾呼叫,那麼完全可以做到每次執行時,呼叫幀只有一項,這將大大節省記憶體。這就是“尾呼叫優化”的意義。
《5》只有不再用到外層函式的內部變數時,內層函式的呼叫幀才會取代外層函式的呼叫幀,否則無法進行尾呼叫優化。
function foo(a){ var one="I'm the first"; function add(b){ return b+one; }; return add(a); };
備註:上述程式碼不會進行尾呼叫優化,因為內層函式用到了外層函式的內部變數。
2.
(1)尾遞迴:函式呼叫自身稱為遞迴,如果尾呼叫自身,則是尾遞迴。
function tail(n) {
if (n === 1) return 1;
return n * tail(n - 1);
}
tail(3);
上述程式碼的時間複雜度為o(n),最多需要儲存n個呼叫記錄;
function tail(n,total){
if(n===1){
return total;
}
return tail(n-1,n*total);
}
上述程式碼的時間複雜度為o(1),只保留一個呼叫記錄. (2)遞迴函式的改寫:
尾遞迴的實現,往往需要改寫遞迴函式,確保最後一步只調用自身。做到這一點的方法,就是把所有用到的內部變數改寫成函式的引數。
備註:參考資料(ES6官方教程)