Javascript運行機制之Event Loop

一、四個概念

1、Javascript是單線程

單線程意味著我們的js代碼隻能從上往下同步執行,同一時間隻能執行一個任務,這會導致某些執行時間較長或者執行時間不確定的任務會卡住其它任務的正常執行,Event Loop出現的原因正是為瞭解決此問題。

2、任務隊列

為瞭解決上述的排隊問題,有瞭任務隊列,瀏覽器在異步任務有瞭結果後,將其添加到任務隊列,以待將來執行,其他任務就在主線程上同步執行。

這裡要註意的是,向任務隊列中添加任務的時機是異步任務有結果後。其實任務隊列中存在的就是異步任務的回調函數。

3、同步任務、異步任務

Js程序中的同步任務是指在主線程中執行的任務,異步任務是指進入任務隊列中的任務

4、Javascript執行棧

所有的同步任務都在主線程上執行,行成一個執行棧。當主線程上任務執行完畢後,從任務隊列中取出任務執行。

var name = "zhouwei";

setTimeout(() => {
    console.log(1);
}, 1000);

console.log(name);


上面代碼在瀏覽器中執行如下,我們將程序全局執行環境的代碼理解為包裹在一個main函數中的代碼,這段代碼的執行棧變化如下圖:

  • 開始執行代碼,將main任務(全局代碼入棧執行),當遇到異步任務(setTimeout後)。
  • 瀏覽器接管異步任務,並在1s後將異步任務的結果(回調函數)添加到任務隊列。
  • 執行棧中的同步任務執行完畢,此時任務隊列為空(未到1s),執行棧也為空
  • 異步任務有結果後,首先進入任務隊列排隊(因為可能有很多異步任務)。
  • 執行棧從任務隊列中取出任務開始同步執行。
  • 重復執行第5步。

二、Event Loop

Js執行棧不斷的從任務隊列中讀取任務並執行的過程就是Event Loop

我們知道任務隊列中存放的是異步任務的結果,那麼異步任務都有哪些瞭?

  • 1、事件

Javascript中的事件有很多,都是屬於異步任務。由瀏覽器接管,當事件觸發時,將事件的回調加入的任務隊列中,在Js執行棧中沒有任務時執行。

  • 2、Http請求
  • 3、定時器
  • 4、requestAnimationFrame等

宏任務(macrotask)和微任務(microtask)
在瞭解瞭任務隊列和Event Loop後,我們知道瞭Js執行棧從任務隊列中讀取任務執行,但這個具體工程我們任務不清楚,這裡引出瞭宏任務和微任務的的概念,幫助我們理解Event Loop。

進入任務隊列中的異步任務回調分為瞭宏任務和微任務, Js執行棧執行宏任務和微任務的規則如下圖所示。

Js執行棧首先執行一個宏任務(全局代碼) -> 從任務隊列中讀取所有微任務執行 -> UI rendering(瀏覽器渲染界面) -> 從任務隊列讀取一個宏任務 -> 所有微任務 -> UI rendering -> …

在每一輪的Event Loop結束後(1個宏任務 + 所有微任務),瀏覽器開始渲染界面(如果有需要渲染的UI,否則不執行UI rendering),在UI rendering結束後,開始下一輪Event Loop。

哪些是宏任務?

  • setTimeout
  • setInterval
  • setImmediate (Node)
  • requestAnimationFrame (瀏覽器)
  • I/O (事件回調)
  • UI rendering (瀏覽器渲染)

哪些是微任務?

  • Promise
  • process.nextTick (Node)
  • MutationObserver (現代瀏覽器提供的用來檢測 DOM 變化的網頁接口)

setTimeout延時問題

一般來說在代碼中setTimeout中回調的執行時間都是大於設置的時間。 這是因為在setTimeout指定時間到達後,雖然回調函數被添加到瞭任務隊列,但是此時Js執行棧中可能有正在執行的任務,此回調需要等待Js執行棧的任務執行完畢後才有機會執行,這就是setTimeout延時問題。

三、實戰

練習下下方代碼輸出結果吧:

console.log(1);

setTimeout(() => {
    console.log(2);
    Promise.resolve().then(() => {
        console.log(3)
    });
});

new Promise(resolve => {
    console.log(4);
    setTimeout(() => {
        console.log(5);
    });
    resolve(6)
}).then(data => {
    console.log(data);
})

setTimeout(() => {
    console.log(7);
})

console.log(8);

用上方的我們說過的js執行機制來分析這道題:

1: 執行全局任務中的同步代碼輸出:

1
4
8

這裡需要註意的是Promise接受的handle函數是同步任務,而then方法是異步任務,所以會直接輸出4。

2: 這時的任務隊列中有三個setTimeout的宏任務,和一個Promise的微任務

// 此時的宏任務是

setTimeout(() => {
    console.log(2);
    Promise.resolve().then(() => {
        console.log(3)
    });
});

setTimeout(() => {
    console.log(5);
});


setTimeout(() => {
    console.log(7);
})

// 此時微任務是
then(data => {
    console.log(data);
})

執行一個微任務, 輸出:6

3: 接著執行第一個宏任務

setTimeout(() => {
    console.log(2);
    Promise.resolve().then(() => {
        console.log(3)
    });
});


輸出:2

在此宏任務中,向任務 隊列添加瞭一個微任務。此時任務隊列有瞭新的微任務。

4:執行一個微任務,輸出:3

then(() => {
   console.log(3)
});


5: 繼續按照規則執行任務, 輸出: 5、7

整體輸出情況是:

1、4、8、6、2、3、5、7

你的答案是不是這樣呢?

總結:

  • javascritp的任務分為同步任務和異步任務
  • 同步任務在主線程(Js執行棧)中執行,異步任務被其他線程接管,並在異步任務有結果後,將其回調添加到任務隊列。
  • 任務隊列中的任務分為瞭宏任務和微任務。Js執行棧總是先執行一個宏任務,再執行完所有微任務…

到此這篇關於Javascript運行機制之Event Loop的文章就介紹到這瞭,更多相關Javascript運行機制Event Loop內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!

推薦閱讀: