ES6包含了一個性能領域的特殊要求,這與一個涉及函式呼叫的特定優化形式相關:即尾呼叫優化(Tail Call Optimization,TCO),簡單地說,尾呼叫就是一個出現在另一個函式“結尾”處的函式呼叫,這個呼叫結束之后就沒有其余事情要做了(除了可能要回傳結果值)
什么尾呼叫
舉個例子,下面是一個非遞回的尾呼叫:
function foo(x) {
return x
}
// 尾呼叫
function bar(y) {
return foo(y + 1)
}
// 非尾呼叫
function baz() {
return 1 + bar(40)
}
baz() // 輸出42
說明:foo(y+1) 是 bar(...) 中的尾呼叫,因為在 foo(...) 完成后,bar(...) 也完成了,并且只需要回傳 foo(...) 呼叫的結果,然而,bar(40) 不是尾呼叫,因為在它完成后,它的結果需要加上1才能由 baz() 回傳,
在JavaScript里,呼叫一個新的函式需要額外的一塊預留內容來管理呼叫堆疊,成為堆疊幀,所以前面的代碼一般會同時需要為每個
baz()、bar(...)、foo(...)保留一個堆疊幀,
然而,如果支持TCO的引擎能夠意識到foo(y+1) 呼叫位于尾部,這意味著 bar(...) 基本上已經完成了,那么在呼叫foo(...)時,它就不需要創建一個新的幀堆疊,而是可以重用已有的 bar(...) 的幀堆疊,這樣不僅速度快,而且節省記憶體,
什么是尾遞回
在計算機科學里,尾呼叫是指一個函式里的最后一個動作是一個函式呼叫的情形:即這個呼叫的回傳值直接被當前函式回傳的情形,這種情形下稱該呼叫位置為尾位置,若這個函式在尾位置呼叫本身(或是一個尾呼叫本身的其他函式等等),則稱這種情況為尾遞回,是遞回的一種特殊情形,尾呼叫不一定是遞回呼叫,但是尾遞回特別有用,也比較容易實作,
TCO的意義
在程式運行時,計算機會為應用程式分配一定的記憶體空間;應用程式則會自行分配所獲得的記憶體空間,其中一部分被用于記錄程式中正在呼叫的各個函式的運行情況,這就是函式的呼叫堆疊,常規的函式呼叫總是會在呼叫堆疊最上層添加一個新的堆疊幀(stack frame,也翻譯為“堆疊幀”或簡稱為“幀”),這個程序被稱作“入堆疊”或“壓堆疊”(意即把新的幀壓在堆疊頂),當函式的呼叫層數非常多時,呼叫堆疊會消耗不少記憶體,甚至會撐爆記憶體空間(堆疊溢位),造成程式嚴重卡頓或意外崩潰,尾呼叫的呼叫堆疊則特別易于優化,從而可減少記憶體空間的使用,也能提高運行速度,其中,對尾遞回情形的優化效果最為明顯,尤其是遞回演算法非常復雜的情形,
在簡單的代碼片段中,這類優化算不了什么,但是在處理遞回時,這就解決了大問題,特別是如果遞回可能會導致成千上百個堆疊幀的時候,有了TCO,引擎可以用同一個堆疊幀執行所有的這類呼叫!
遞回是 JavaScript 中一個紛繁復雜的主題,因為如果沒有TCO的話,引擎需要實作一個隨意的限制來界定遞回堆疊的深度,達到了就得停止,以防止記憶體耗盡,有了TCO,尾呼叫的遞回函式本質上就可以任意運行,因為再也不需要使用額外的記憶體,也沒有了記憶體溢位的問題,
下面用尾遞回實作一個典型的階乘函式:
// 用回圈實作
function factorial(n) {
if (n<2) return 1
var res = 1
for (var i = n; i > 1; i--) {
res *= i
}
return res
}
// 用尾遞回實作
function factorial(n) {
function fact(n, res) {
if (n < 2) return res
return fact(n-1, n*res)
}
return fact(n, 1)
}
factorial(5) // 輸出120
注意:TCO只用于有實際的尾呼叫的情況,如果你寫了一個沒有尾遞呼叫的函式,那么性能還是會回到普通幀堆疊分配的情形,引擎對這樣的遞回呼叫堆疊的限制也仍然有效,
總結
一般來說,尾呼叫消除是可選的,可以用,也可以不用,然而,在函式編程語言中,語言標準通常會要求編譯器或運行平臺實作尾呼叫消除,這讓程式員可以用遞回取代回圈而不喪失性能,ES6之所以要求引擎實作TCO而不是將其留給引擎自由決定,一個原因是缺乏TCO會導致一些JavaScript演算法因為害怕呼叫堆疊限制而降低了通過遞回實作的概率,
如果在所有的情況下引擎缺乏TCO只是降低了性能,那它就不會成為ES6所要求的東西,但是,由于缺乏TCO確實可以使一些程式變得無法實作,所以它就成為了一個重要的語言特性而不是隱藏的實作細節,ES6確保了JavaScript開發者從現在開始可以在所有符合ES6+的瀏覽器中依賴這個優化,這對JavaScript性能來說是一個勝利,
參考文獻
- 《你不知道的JavaScript-中卷》
本文由博客一文多發平臺 OpenWrite 發布!
轉載請註明出處,本文鏈接:https://www.uj5u.com/qiye/5978.html
標籤:JavaScript
