尾調用優化(Tail Call Optimization) 尾調用是指函數的最後一條語句是函數調用,比如下麵的代碼: 在ES5中,尾調用和其他形式的函數調用一樣:腳本引擎創建一個新的函數棧幀並且壓在當前調用的函數的棧幀上面。也就是說,在整個函數棧上,每一個函數棧幀都會被保存,這有可能造成調用棧占用內 ...
尾調用優化(Tail Call Optimization)
尾調用是指函數的最後一條語句是函數調用,比如下麵的代碼:
function doSomething() { return doSomethingElse(); }
在ES5中,尾調用和其他形式的函數調用一樣:腳本引擎創建一個新的函數棧幀並且壓在當前調用的函數的棧幀上面。也就是說,在整個函數棧上,每一個函數棧幀都會被保存,這有可能造成調用棧占用記憶體過大甚至溢出。
尾遞歸在ES6中有何不同(How Tail Call are Different in ECMAScript6)
在ES6中,strict模式下,滿足以下條件,尾調用優化會開啟,此時引擎不會創建一個新的棧幀,而是清除當前棧幀的數據並復用:
(1、尾調用函數不需要訪問當前棧幀中的變數
(2、尾調用返回後,函數沒有語句需要繼續執行
(3、尾調用的結果就是函數的返回值
下麵例子中的函數就可以使用尾調用優化:
"use strict"; function doSomething() { return doSomethingElse(); }
然而下麵例子中的函數不能使用尾調用優化,因為尾調用的結果不是函數的返回值:
"use strict"; function doSomething() { doSomethingElse(); }
尾調用返回後,如果函數仍然有語句要執行,也是不能使用尾調用優化的:
"use strict"; function doSomething() { return 1 + doSomethingElse(); }
尾調用函數如果是閉包函數,也不能使用尾調用優化:
"use strict"; function doSomething() { var num = 1; var func = () => num; return func(); }
如何利用尾調用優化(How to Harness Tail Call Optimization)
尾調用優化主要用在遞歸函數中,通常能帶來顯著的效果。看一個遞歸計算階乘的函數:
function factorial(n) { if (n <= 1) { return 1; } else { return n * factorial(n - 1); } }
很明顯,上面的代碼並不能使用尾調用優化,因為factorial(n-1)返回後,仍然有語句要繼續執行。但是我們可以改寫這個函數,使其能夠利用尾調用優化特性:
function factorial(n, p = 1) { if (n <= 1) { return 1 * p; } else { let result = n * p; return factorial(n - 1, result); } }
如和改寫遞歸函數以便利用尾調用優化是需要技巧的。如果可以,應該充分利用引擎的尾調用優化特性來優化遞歸函數。