JavaScript運行機制:event-loop

JavaScript運行機制:event-loop

 

 

我們從javascript的單線程->任務隊列->事件和回調函數->事件環,一步一步講解javascript的執行機制。

 

一、JavaScript是單線程

JavaScript語言的特點是單線程,同一時間只能做一件事,JavaScript之所以是單線程,跟他的用途有關,作爲瀏覽器腳本語言,JavaScript的主要用途是操作DOM,假如同時有兩個js線程同時操作一個DOM,一個添加一個刪除,瀏覽器就懵了吧。

二、任務隊列

單線程就意味着,所有任務需要排隊,前一個任務結束,纔會執行後一個任務。如果前一個任務耗時很長,後面的任務就必須等待。

IO讀寫操作一般是IO設備比較慢,這些任務耗時將很久,主線程將長時間處於等待結果返回的狀態。

JavaScript語言的設計者意識到主線程完全可以不管處於等待隊列中的任務,先去運行後面的任務,當處於等待中的任務返回結果,再繼續執行此任務。

所有的任務可以分兩種:同步任務、異步任務。同步任務是在主線程上排隊的任務,一個任務執行完畢,纔會執行下一個任務;異步任務是指,不進入主線程,而進入任務隊列,只有任務隊列通知主線程,某個異步任務可以執行了,主線程纔會來執行此異步任務的回調。

異步執行的運行機制如下:

  • 所有同步任務都在主線程上執行,形成一個執行棧;

  • 主線程之外,還存在一個任務隊列,只要異步任務有了運行結果,就在任務隊列中放置一個事件;

  • 一旦執行棧中所有同步任務執行完畢,系統就會讀取任務隊列,哪個任務結束了等待狀態,就進入執行棧,開始執行。

  • 主線程不斷重複上一步。

 

 

 

三、事件和回調函數

"任務隊列"是一個事件的隊列(也可以理解成消息的隊列),IO設備完成一項任務,就在"任務隊列"中添加一個事件,表示相關的異步任務可以進入"執行棧"了。主線程讀取"任務隊列",就是讀取裏面有哪些事件。

"任務隊列"中的事件,除了IO設備的事件以外,還包括一些用戶產生的事件(比如鼠標點擊、頁面滾動等等)。只要指定過回調函數,這些事件發生時就會進入"任務隊列",等待主線程讀取。

所謂"回調函數"(callback),就是那些會被主線程掛起來的代碼。異步任務必須指定回調函數,當主線程開始執行異步任務,就是執行對應的回調函數。

"任務隊列"是一個先進先出的數據結構,排在前面的事件,優先被主線程讀取。主線程的讀取過程基本上是自動的,只要執行棧一清空,"任務隊列"上第一位的事件就自動進入主線程。但是,由於存在後文提到的"定時器"功能,主線程首先要檢查一下執行時間,某些事件只有到了規定的時間,才能返回主線程。

四、Event Loop

主線程從任務隊列中讀取事件,這個過程是循環不斷的,這個運行機制被稱爲Event Loop(事件環)

五、先來看一下瀏覽器的事件環

 

 

 

主線程運行的時候,產生堆和棧,heap就是堆,堆裏面是存的是各種對象和函數,stack是棧,var a=1就存儲在棧內;dom事件,ajax請求,定時器等異步操作的回調會被放到任務隊列callback queue中,這個隊列時先進先出的順序,主線程執行完畢之後會依次執行callback queue中的任務,當這些任務是等待結束狀態,就進入主線程被執行。

1、瀏覽器的宏任務和微任務

異步任務中分“宏任務(macro-task)”和“微任務(micro-task)”機制 macrotask 和 microtask 兩種分類。在掛起任務時,JS 引擎會將所有任務按照類別分到這兩個隊列中,首先在 macrotask 的隊列(這個隊列也被叫做 task queue)中取出第一個任務,執行完畢後取出 microtask 隊列中的所有任務順序執行;之後再取 macrotask 任務,周而復始,直至兩個隊列的任務都取完,這就是瀏覽器中Event Loop對宏任務和微任務的執行機制。

兩個類別的具體分類如下:

macro-task: script(整體代碼), setTimeout, setInterval, setImmediate, I/O, UI rendering,mesageChannel micro-task: Promises(這裏指瀏覽器實現的原生 Promise),Object.observe, MutationObserver 我們用下面一段代碼來檢驗一下是否理解瀏覽器事件環:

setTimeout(function(){
    console.log('setTimeout1')
    Promise.resolve().then(()=>{
        console.log('then1');

    })
},0)

Promise.resolve().then(()=>{
    console.log('then2');
    Promise.resolve().then(()=>{
        console.log('then3');
    })
    setTimeout(function(){
        console.log('setTimeout2')
    },0)
})
複製代碼

執行結果是then2 then3 setTimeout1 then1 setTimeout2 首先代碼裏面的setTimeout和Promise都是異步任務,js從上到下執行代碼,分別將這兩個異步任務放到了宏任務隊列和微任務隊列,主線程先到微任務隊列中,所以先輸出了then2,然後在微任務隊列中有添加一個then3的promise任務,在宏任務中添加了一個setTimeout2的定時器任務,現在接着去執行微任務隊列,所以輸出了then3,開始執行第一個宏任務,輸出setTimeout1,並且在微任務隊列又天機then1的promise任務,所以轉去執行微任務,輸出then1,再去執行一個宏任務,就是之前放進去的setTimeout2.

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章