以前分享過遞歸,其中有一個優化就是尾調用。編程
先明確尾調用的概念: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對尾調用有什麼優化?就是函數默認值,在一些場景下,好比階乘的遞歸,採用默認值實現尾遞歸優化。