1. 程式人生 > 程式設計 >關於ES6尾呼叫優化的使用

關於ES6尾呼叫優化的使用

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-中卷》

到此這篇關於關於ES6尾呼叫優化的使用的文章就介紹到這了,更多相關ES6尾呼叫優化內容請搜尋我們以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援我們!