1. 程式人生 > >關於尾呼叫和尾遞迴

關於尾呼叫和尾遞迴

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官方教程)