圖解尾呼叫優化
尾呼叫
啥是尾呼叫?
尾呼叫就是函式的最後一個步驟呼叫另一個函式
比方說:
函式在呼叫的時候會在呼叫棧中 push 一個呼叫幀,每次執行完函式都會逐一彈出呼叫幀知道所有函式執行完畢,呼叫棧被清空:
呼叫棧中的同步程式碼
function f1() { console.log('?') }
function f2() { f1() }
function f3() { f2() }
f3()
複製程式碼
呼叫棧如下圖:
- 首先執行 script ,將 main 主程式推入呼叫棧中並執行,發現需要呼叫 f3
- 將 f3 函式推入呼叫棧中,執行 f3,發現需要呼叫 f2
- 將 f2 函式推入呼叫棧中,執行 f2, 發現需要呼叫 f1
- 將 f1 推入呼叫棧中,執行 f1,發現需要呼叫 console.log 方法
- 推入 console.log 並列印結果執行完畢
- 彈出 consoole.log
- 彈出 f1
- 彈出 f2
- 彈出 f3
- 彈出 main,程式碼執行完畢
呼叫棧中的非同步程式碼
以 setTimeout 為例:
console.log('1')
setTimeout(function() {
console.log('3)
}, 5000)
console.log('2')
複製程式碼
呼叫棧見下圖:
- 執行程式碼推入 script 程式碼 main 並執行,發現需要執行 console.log
- 將 console.log 推入呼叫棧
- 執行 console.log 列印 1 彈出呼叫棧
- 發現 setTimeout 將等待執行的回撥函式推入巨集任務列表,將 setTimeout 彈出呼叫棧
- 繼續執行程式碼發現需要執行 console.log 將任務推入呼叫棧
- 執行 console.log 列印 2 並彈出呼叫棧
- script 程式碼 main 執行完畢,彈出呼叫棧
- 同步程式碼執行完畢,檢視巨集任務列表發現需要執行 console.log
- 延遲 5s 將 console.log 推入呼叫棧
- 執行 console.log 並列印 2
- 最後將 console.log 彈出呼叫棧,程式碼執行完畢
尾呼叫優化
每次在函式被呼叫的時候,記憶體都會儲存呼叫幀。尾呼叫因為是函式的最後一步,因此並不需要外層函式的呼叫幀。我們只需要將最後需要執行另外一個函式之前用 return 操作符顯式表明"不再需要此函式"即可
before
after
注意必須使用嚴格模式
在執行過程中,呼叫棧的呼叫幀永遠只有一條,這樣就可以節省很大一部分記憶體
參考:
- https://vaibhavgupta.me/2018/01/20/understanding-event-loop/
- http://www.ruanyifeng.com/blog/2015/04/tail-call.html
- https://juejin.im/post/5acdd7486fb9a028ca53547c