JavaScript:併發模型與Event Loop

1、JavaScript的單線程

衆所周知,JavaScript的一大特色就是單線程,可是咱們有沒有思考過它爲何不能是多線程的?編程

咱們假定JavaScript有兩個線程,一個線程在某個DOM節點上添加內容,另外一個線程刪除了這個節點,這時瀏覽器應該以哪一個線程爲準?因此爲了不這種複雜性,從一誕生,JavaScript就是單線程。promise

儘管HTML5提出Web Worker,容許JavaScript腳本建立多個線程,可是子線程徹底受主線程控制,且不得操做DOM。因此,並無改變JavaScript單線程的本質。瀏覽器

2、定時器

定時器主要是setTimeout()和setInterval()這兩個函數,這也是平時編程時候用到最多的。多線程

console.log(1);
setTimeout(function() {
    console.log(2);
},5000);
console.log(3);

上面代碼的執行結果是1,3,2。但若是將setTimeout()的第二個參數設爲0,就表示當前代碼執行完之後,當即執行(0毫秒延遲)指定的回調函數。setTimeout(fn,0)的含義是,它在任務隊列的尾部添加一個事件,在主線程最先獲得空閒時去執行,也就是說,儘量早得執行。併發

須要注意的是,定時器只是將事件插入了任務隊列,必須等到當前代碼(執行棧)執行完,主線程纔會去執行它指定的回調函數。若是當前代碼耗時很長,有可能要等好久,因此並無辦法保證,回調函數必定會在setTimeout()指定的時間執行。這也引伸出JavaScript的併發模型異步

3、併發模型

咱們先看一下理論上的併發模型:函數

圖片描述

棧(stack):函數調用會造成了一個堆棧幀
堆(heap):對象被分配在一個堆中,一個用以表示一個內存中大的未被組織的區域
隊列(queue):運行時包含的一個待處理的消息隊列。當棧爲空時,則從隊列中取出一個消息進行處理。這個處理過程包含了調用與這個消息相關聯的函數(以及於是建立了一個初始堆棧幀)oop

4、Event Loop

針對上面的併發模型和JavaScript的同步異步運行機制,咱們能夠看到整個流程大體是這樣的:spa

1.全部同步任務都在主線程上執行,造成一個執行棧(併發模型的stack)。
2.主線程以外,還存在一個任務隊列(併發模型的queue)。只要異步任務有了運行結果,就在任務隊列中放置一個事件。
3.一旦執行棧中的全部同步任務執行完畢,系統就會讀取任務隊列,看看裏面有哪些事件和那些對應的異步任務,因而等待結束狀態,進入執行棧,開始執行。
4.主線程不斷重複上面的第三步。線程

這個過程是循環不斷的,因此這種運行機制又稱爲Event Loop(事件循環)。放一張大神演講時的圖片來更好地理解Event Loop:

圖片描述

咱們能夠看到,主線程運行的時候,產生(heap)和(stack),中的代碼調用各類外部API,它們在任務隊列中加入各類事件(click,load,done)。只要中的代碼執行完畢,主線程就會去讀取任務隊列,依次執行那些事件所對應的回調函數。

5、Macrotask 和 Microtask

這是一個比較冷門的知識,在併發模型中隊列又能夠分爲Macrotask 和 Microtask,它們都屬於異步任務。先來看一個例子:

console.log('script start');

setTimeout(function() {
    console.log('setTimeout');
}, 0);

Promise.resolve().then(function() {
    console.log('promise1');

    setTimeout(function() {
        console.log('setTimeout in microtask');
    }, 0);
}).then(function() {
    console.log('promise2');
});

console.log('script end');

輸出:

script start
script end
promise1
promise2
setTimeout
setTimeout in microtask

Macrotask 和 Microtask有什麼區別呢?

  • Macrotasks:setTimeout, setInterval, setImmediate, I/O, UI rendering
  • Microtask:process.nextTick, Promises, Object.observe(廢棄), MutationObserver

它們的執行過程以下:

JavaScript引擎首先從macrotask queue中取出第一個任務執行完畢後,將microtask queue中的全部任務取出,按順序所有執行而後再從macrotask queue中取下一個執行完畢後,再次將microtask queue中的所有取出循環往復,直到兩個queue中的任務都取完

相關文章
相關標籤/搜索