1. 程式人生 > >JS事件迴圈機制(event loop)

JS事件迴圈機制(event loop)

一 前言

相信所有學過 JavaScript 都知道它是一門單執行緒的語言,這也就意味著 JS 無法進行多執行緒程式設計,但是 JS 當中卻有著無處不在的非同步概念 。在初期許多人會把非同步理解成類似多執行緒的程式設計模式,其實他們中有著很大的差別,要完全理解非同步,就需要了解 JS 的執行核心——事件迴圈(event loop)。

二 JS引擎的兩大特點:單執行緒和非阻塞

單執行緒

JS引擎是基於單執行緒(Single-threaded)事件迴圈的概念構建的。同一時刻只執行一個程式碼塊在執行,與之相反的是像JAVA和C++一樣的語言,它們允許多個不同的程式碼塊同時執行。對於基於執行緒的軟體而言,當多個程式碼塊同時訪問並改變狀態時,程式很難維護並保證狀態不出錯。

非阻塞

非阻塞則是當代碼需要進行一項非同步任務(無法立刻返回結果,需要花一定時間才能返回的任務,如I/O事件)的時候,主執行緒會掛起(pending)這個任務,然後在非同步任務返回結果的時候再根據一定規則去執行相應的回撥。非阻塞是通過事件迴圈機制實現的。
JS通常是非阻塞的,除了某些特殊情況,JS會停止程式碼執行:

  • alert, confirm, prompt(除了Opera)
  • “頁面上的程式正忙”的系統對話方塊彈出

三 事件佇列

首先,我們先來看一段簡單的程式碼:

console.log("script start"); 

setTimeout(function () {
    console.log("setTimeout");
}, 1000);

console.log("script end");

我們可以看到,首先,程式輸出 ‘script start’ 和 ‘script end’,在大約1s之後輸出了 ‘setTimeout’ 。該程式的 ‘script end’ 並沒有等待1s之後輸出,而是立即輸出。這是因為 setTimeout 是一個非同步的函式。意思也就是說當我們設定一個延遲函式的時候,當前指令碼並不會阻塞,它只是會在瀏覽器的事件表中進行記錄,程式會繼續向下執行。當延遲的時間結束之後,事件表會將回調函式新增至事件佇列(task queue)中,事件佇列拿到了任務過後便將任務壓入執行棧(stack)當中,執行棧執行任務,輸出 ‘setTimeout’。

事件佇列是一個儲存著待執行任務的佇列,其中的任務嚴格按照時間先後順序執行,排在隊頭的任務將會率先執行,而排在隊尾的任務會最後執行。事件佇列每次僅執行一個任務,在該任務執行完畢之後,再執行下一個任務。執行棧則是一個類似於函式呼叫棧的執行容器,當執行棧為空時,JS 引擎便檢查事件佇列,如果不為空的話,事件佇列便將第一個任務壓入執行棧中執行。

四 事件迴圈機制

由於 JS 是單執行緒的,同步執行任務會造成瀏覽器的阻塞,所以我們將 JS 分成一個又一個的任務,通過不停的迴圈來執行事件佇列中的任務。這就使得當我們掛起某一個任務的時候可以去做一些其他的事情,而不需要等待這個任務執行完畢。
所以事件迴圈的執行機制大致分為以下步驟:

1.檢查事件佇列是否為空,如果為空,則繼續檢查;如不為空,則執行 2;
2.取出事件佇列的首部,壓入執行棧;
3. 執行任務;
4.檢查執行棧,如果執行棧為空,則跳回第 1 步;如不為空,則繼續檢查;

五 總結

關於事件迴圈,我們需要記住以下幾點:

(1)事件佇列嚴格按照時間先後順序將任務壓入執行棧執行;
(2)當執行棧為空時,瀏覽器會一直不停的檢查事件佇列,如果不為空,則取出第一個任務;
(3)在每一個任務結束之後,瀏覽器會對頁面進行渲染;