遞歸尾調用優化

以前分享過遞歸,其中有一個優化就是尾調用。編程

先明確尾調用的概念:bash

尾調用(Tail Call)是函數式編程的一個重要概念,就是指某個函數的最後一步是return調用另外一個函數。函數式編程

function fn() {  

  return gn()

}
複製代碼

下面幾種不是尾調用:函數

function fn() {  

  return gn() + 10//調用以後又賦值

}

function fn() {  

  gn()//沒有return,或者說是return了undefined

}

function fn() {  

  var gnn = gn();  

  return gnn//調用後還有操做

}
複製代碼

注意,是最後一步操做,不是放在最末尾,下面也算尾調用:優化

function fn(a) {  

  if(a > 10){ 

   return gn(10)  

  }else{    

    return gn(20)  

  }  

  return gn()

}

複製代碼

以前分享過調用棧,若是不是尾調用,那麼會生成一個調用棧,直到棧頂的執行完畢,纔會釋放以前造成的調用棧的內存。尾調用由於是最後一步操做,因此不須要保留以前的棧,也就不須要保存以前的內存,就是遞歸裏面計算階乘那兩個函數。ui

注意,並非全部的函數都能尾調用優化,要看你這個函數需不須要使用某些上個函數的變量或者什麼的。spa

尾調用優化其實很大一部分就是遞歸函數在使用,由於遞歸函數調用的時候很是耗費內存,可能須要保存成百上千調用棧,很容易內存溢出。若是是尾遞歸就只有一個調用棧,能把複雜度O(n)的變成O(1)。code

至於怎麼改寫遞歸變成可使用尾調用就比較複雜了,須要根據不一樣函數去修改。好比阮一峯大神的例子:cdn

function sum(x, y) {

  if (y > 0) {

    return sum(x + 1, y - 1);

  } else {

    return x;

  }

}

sum(1, 100000)
複製代碼

改爲:遞歸

function sum(x, y) {

  if (y > 0) {

    return sum.bind(null, x + 1, y - 1);

  } else {

    return x;

  }

}
複製代碼

而後使用蹦牀函數:

function trampoline(f) {

  while (f && f instanceof Function) {

    f = f();

  }

  return f;

}
複製代碼

執行:

trampoline(sum(1, 100000))
複製代碼

你會發現,不少遞歸函數都能改爲相似的,而後使用蹦牀函數實現尾調用優化。

而ES6對尾調用有什麼優化?就是函數默認值,在一些場景下,好比階乘的遞歸,採用默認值實現尾遞歸優化。

相關文章
相關標籤/搜索