js的事件循環和任務隊列


js 異步、棧、事件循環、任務隊列

在開發中經常遇到js的異步問題,為了方便理解,記錄下來,隨時回顧。

  • 以下的所有代碼都是在瀏覽器環境下運行

在瀏覽器中js的運行是依賴瀏覽器js引擎來解析的,並且是在一定的runtime(運行時)的環境被調用,被執行。由於js引擎是單線程的,所以在執行dom渲染,script引入的時候這些操作是同步的,js引擎會通過 Event Loop 的機制,按順序把任務放入棧中執行

而在代碼中產生的異步代碼則是由 runtime 提供的,擁有和Js引擎互不干擾的線程

棧是一個后進先出的一種數據結構,執行起來效率比較高,往往堆里存放着一些對象。而棧中則存放着一些基礎類型變量以及對象的指針,在函數調用的時候,會產生函數的執行棧,也叫執行上下文,這個執行環境中存在着這個函數的私有作用域,上層作用域的指向,函數的參數,這個作用域中定義的變量以及這個作用域的this對象。 而當一系列函數被依次調用的時候,因為js是單線程的,同一時間只能執行一個函數,於是這些函數被排隊在一個單獨的地方。這個地方被稱為執行棧。

當一個腳本第一次執行的時候,js引擎會解析這段代碼,並將其中的同步代碼按照執行順序加入執行棧中,然后從頭開始執行。如果當前執行的是一個方法,那么js會向執行棧中添加這個方法的執行環境,然后進入這個執行環境繼續執行其中的代碼。當這個執行環境中的代碼 執行完畢並返回結果后,js會退出這個執行環境並把這個執行環境銷毀,回到上一個方法的執行環境。。這個過程反復進行,直到執行棧中的代碼全部執行完畢。

總的來說

  • 棧存放着一些基礎類型變量以及對象的指針
  • 當代碼執行的時候,同步代碼按照執行順序開始執行
  • 當代碼執行的時候,碰到函數,引擎會在棧里產生這個函數執行棧,也叫執行上下文。
  • 當代碼執行到函數的時候,會進入這個執行環境繼續執行其中的代碼,反復進行,全部執行完

任務隊列

Js 中,有兩類任務隊列:宏任務隊列(macro tasks)和微任務隊列(micro tasks)。宏任務隊列可以有多個,微任務隊列只有一個
  • 宏任務:script(全局任務), setTimeout, setInterval, setImmediate, I/O, UI rendering.
  • 微任務:process.nextTick (node.js中進程相關的對象), Promise, Object.observer, MutationObserver。

在瀏覽器的 Event Loop機制中,整個流程可以用張圖來表示一下:

avatar

這張圖中可以看到:

  • 微任務隊列(micro tasks)只會有一個
  • 宏任務隊列(macro tasks)可以有多個
  • click ajax 等回調方法都會進入到宏任務隊列(macro tasks)中,當然也包括上面的

而在瀏覽器的Event Loop機制運行時,宏任務隊列(macro tasks)和微任務隊列(micro tasks)的關系,關於這點詳見(關系

  • 宏任務按順序執行,且瀏覽器在每個宏任務之間渲染頁面
  • 所有微任務也按順序執行,且在以下場景會立即執行所有微任務
    • 每個回調之后且js執行棧中為空。
    • 每個宏任務結束后。

而在 NodeJs 的 Event Loop 遵循的是 libuv

這個庫是node作者自己寫的,內部實現了一整套的異步io機制(內部使用c++和js實現),使我們開發異步程序變得簡單,因為這個原因導致了一些js解析和瀏覽器的會不一樣。

NodeJs 的運行是這樣的:

  • 初始化 Event Loop
  • 執行您的主代碼。這里同樣,遇到異步處理,就會分配給對應的隊列。直到主代碼執行完畢。
  • 執行主代碼中出現的所有微任務:先執行完所有nextTick(),然后在執行其它所有微任務。
  • 開始 Event Loop

當每個階段執行完畢后,都會執行所有微任務(先 nextTick,后其它),然后再進入下一個階段。

最后我們來段代碼徹底解析下兩類任務隊列在運行時的邏輯

console.log('1');

setTimeout(function() {
    console.log('2');
    process.nextTick(function() {
        console.log('3');
    })
    new Promise(function(resolve) {
        console.log('4');
        resolve();
    }).then(function() {
        console.log('5')
    })
})
process.nextTick(function() {
    console.log('6');
})
new Promise(function(resolve) {
    console.log('7');
    resolve();
}).then(function() {
    console.log('8')
})

setTimeout(function() {
    console.log('9');
    process.nextTick(function() {
        console.log('10');
    })
    new Promise(function(resolve) {
        console.log('11');
        resolve();
    }).then(function() {
        console.log('12')
    })
})

結合上面講的邏輯 可以分析一波得出最后答案是1,7,6,8,2,4,3,5,9,11,10,12


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM