之前分享過遞迴,其中有一個優化就是尾呼叫。
先明確尾呼叫的概念:
尾呼叫(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()
}
複製程式碼
之前分享過呼叫棧,如果不是尾呼叫,那麼會生成一個呼叫棧,直到棧頂的執行完畢,才會釋放之前形成的呼叫棧的記憶體。尾呼叫因為是最後一步操作,所以不需要保留之前的棧,也就不需要儲存之前的記憶體,就是遞迴裡面計算階乘那兩個函式。
注意,並不是所有的函式都能尾呼叫優化,要看你這個函式需不需要使用某些上個函式的變數或者什麼的。
尾呼叫優化其實很大一部分就是遞迴函式在使用,因為遞迴函式呼叫的時候非常耗費記憶體,可能需要儲存成百上千呼叫棧,很容易記憶體溢位。如果是尾遞迴就只有一個呼叫棧,能把複雜度O(n)的變成O(1)。
至於怎麼改寫遞迴變成可以使用尾呼叫就比較複雜了,需要根據不同函式去修改。比如阮一峰大神的例子:
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對尾呼叫有什麼優化?就是函式預設值,在一些場景下,比如階乘的遞迴,採用預設值實現尾遞迴優化。