圖解尾呼叫優化

JS菌發表於2019-03-08

圖解尾呼叫優化

尾呼叫

啥是尾呼叫?

尾呼叫就是函式的最後一個步驟呼叫另一個函式

比方說:

20190307171547.png

函式在呼叫的時候會在呼叫棧中 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 操作符顯式表明"不再需要此函式"即可

20190309013011.png

before

20190309013038.png

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

圖解尾呼叫優化

相關文章