Event Loop 其實也就這點事

劉凱里發表於2018-09-02

前段時間在網上陸續看了很多關於 Event loop 的文章,看完也就混個眼熟,可能內心深處對這種偏原理的知識有一些牴觸心情,看完後也都沒有去深入理解。最近在看 Vue 的原始碼,在讀到關於 nextTick 的實現時,總有一種似曾相識的感覺,於是去網上查了下資料,原來 nextTick 的實現正是基於 Event loop 機制(引起重視了)。

Call Stack

眾所周知,JavaScript 是 one-threaded,也就意味著在執行 JavaScript 的過程中,是 One thing at a time,而這樣的特性,正是由一個叫 Call Stack 的東西決定的(有且僅有一個)。
既然是棧,就滿足 FILO 的原則。故 Call Stack 在函式執行時的表現為:

  • 當有函式執行時,該函式被 push 到 Call Stack
  • 當函式執行結束時,該函式從 Call Stack 內被 pop 出
  • 如果函式內有呼叫到其他函式(執行結束前),則將其他函式再 push 到 Call Stack 中,等到呼叫結束時 pop 出

由此可見,如果一個函式定義如下:

const dead = () => {
    return dead();
}
複製程式碼

那麼當其被執行時,就會向 Call Stack 中不斷的 push 同一個函式(dead),導致整個頁面掛掉。

When Call Stack Meets Sync Request

眾所又周知了,在 jQuery 提供的 Ajax 函式中,可供開發者選擇請求是 sync 還是 async,我們先討論 Call Stack 遇到 sync 請求時會發生什麼。

const name = $.ajaxSync(URL_1);
const info = $.ajaxSync(URL_2);
const work = $.ajaxSync(URL_3);

console.log(name);
console.log(info);
console.log(work);
複製程式碼

屋漏偏逢連陰雨,此時的網路狀態又極差,每一個網路請求從發出到成功要經歷五秒,想象一下上面這段程式碼如果跑起來了,會發生什麼?
這是一件讓人絕望的事情:
隨著程式的推進,ajaxSync 函式會先後三次被 push 並 pop 出 Call Stack,而每一次從 push 到 pop 的過程需要耗費五秒鐘的時間。
無論從工程效率還是使用者體驗的角度來說,這都是不被允許的一件事情。

Async & Callback

為了杜絕上面的問題,瀏覽器提供給了開發者一個叫做非同步 + Callback 的解決方案。先看一段程式碼:

console.log('kyrieliu');

setTimeout(function(){
    console.log('about Event Loop');
}, 5000);

console.log(' is writing an article ');
複製程式碼

執行結果顯而易見。
ok,那麼這段程式碼在 Call Stack 中的表現又是怎樣的呢?
基於上面文章所述,我推測:
首先,第一行程式碼入棧,執行完畢後出棧;緊接著,setTimeout 入棧,然後emmm,事情有點不對勁了:如果 setTimeout 入棧執行後立刻出棧,那麼它內部的 console 為什麼五秒後才列印出來?

Task Queue

問題的關鍵,是一個叫做 Task Queue 的東西。
緊接著剛才的步驟:setTimeout入棧後執行並觸發了一個五秒的 timer,這個 timer 由 Web api 維護,至此,setTimeout執行完畢並出棧,第三個 console 入棧執行並出棧。五秒後,timer 結束計時,將回撥函式 callback 下放到 task queue 中。
但 callback 還未執行,它什麼時候執行呢?Call Stack 為空的時候。
此時的 call stack 已經為空,所以 callback 被 push 進棧執行並 pop 出,這樣一來就解釋得通了。 至此,正式引出 Event Loop 的概念。

Event Loop

If the call stack is clear and there's something in the task queue, push the first thing on the queue onto the stack.

setTimeout(callback, 0)

在最開始接觸 JavaScript 的時候,看到上面這行程式碼的我是懵蔽的,0ms 後執行 callback, WTF?
在瞭解了 Event Loop 的執行機制後,再回過頭來嘗試解釋一下這行程式碼,即:在 setTimeout 入棧執行時,內部的 callback 會立即被下放到 task queue 中,但它無法執行,因為此時的 call stack 不為空,等到 call stack 為空時,callback 才得以執行。

Thanks

廣而告之

個人公眾號,不止於前端

個人公眾號:劉凱里

相關文章