js事件循環機制

從一個例子提及

var start = new Date()

setTimeout(function () {
    var end = new Date
    console.log('Time elapsed:', end - start, 'ms')
}, 500)

while (new Date() - start < 1000) {
}

有其餘語言能完成預期的功能嗎?Java, 在Java.util.Timer中,對於定時任務的解決方案是經過多線程手段實現的,任務對象存儲在任務隊列,由專門的調度線程,在新的子線程中完成任務的執行html

js是單線程的

JavaScript的主要用途是與用戶互動,以及操做DOM。這決定了它只能是單線程,不然會帶來很複雜的同步問題。segmentfault

爲了利用多核CPU的計算能力,HTML5提出Web Worker標準,容許JavaScript腳本建立多個線程,可是子線程徹底受主線程控制,且不得操做DOM。因此,這個新標準並無改變JavaScript單線程的本質。promise

函數調用棧和任務隊列

image

調用棧

JS執行時會造成調用棧,調用一個函數時,返回地址、參數、本地變量都會被推入棧中,若是當前正在運行的函數中調用另一個函數,則該函數相關內容也會被推入棧頂.該函數執行完畢,則會被彈出調用棧.變量也隨之彈出,因爲複雜類型值存放於堆中,所以彈出的只是指針,他們的值依然在堆中,由GC決定回收.瀏覽器

事件循環(event loop) & 任務隊列(task queue)

JavaScript 主線程擁有一個執行棧以及一個任務隊列多線程

遇到異步操做(例如:setTimeout, AJAX)時,異步操做會由瀏覽器(OS)執行,瀏覽器會在這些任務完成後,將事先定義的回調函數推入主線程的任務隊列(task queue)中,當主線程的執行棧清空以後會讀取task queue中的回調函數,當task queue被讀取完畢以後,主線程接着執行,從而進入一個無限的循環,這就是事件循環.異步

主線程執行棧 & 任務隊列 循環執行,構成事件循環函數

結論

setTimeout()只是將事件插入了"任務隊列",必須等到當前代碼(執行棧)執行完,主線程纔會去執行它指定的回調函數。要是當前代碼耗時很長,有可能要等好久,因此並無辦法保證,回調函數必定會在setTimeout()指定的時間執行。oop

另外一個例子

(function test() {
    setTimeout(function() {console.log(4)}, 0);
    new Promise(function executor(resolve) {
        console.log(1);
        for( var i=0 ; i<10000 ; i++ ) {
            i == 9999 && resolve();
        }
        console.log(2);
    }).then(function() {
        console.log(5);
    });
    console.log(3);
})()

Macrotask & Microtask

macrotask 和 microtask 是異步任務的兩種分類。在掛起任務時,JS 引擎會將全部任務按照類別分到這兩個隊列中,首先在 macrotask 的隊列(這個隊列也被叫作 task queue)中取出第一個任務,執行完畢後取出 microtask 隊列中的全部任務順序執行;以後再取 macrotask 任務,周而復始,直至兩個隊列的任務都取完。學習

  • macro-task: script(總體代碼), setTimeout, setInterval, setImmediate, I/O, UI rendering
  • micro-task: process.nextTick, Promises(這裏指瀏覽器實現的原生 Promise), Object.observe, MutationObserver

image

結論

所有代碼(script) macrotask -> microtask queue (含有promise.then) -> macrotask(setTimeout) -> 下一個microtaskspa

Node.js的事件循環

process.nextTick & setImmediate

  • process.nextTick指定的任務老是發生在全部異步任務以前
  • setImmediate指定的任務老是在下一次Event Loop時執行
process.nextTick(function A() {
  console.log(1);
  process.nextTick(function B(){console.log(2);});
});

setTimeout(function timeout() {
  console.log('TIMEOUT FIRED');
}, 0)
new Promise(function(resolve) {
    console.log('glob1_promise');
    resolve();
}).then(function() {
    console.log('glob1_then')
})
process.nextTick(function() {
    console.log('glob1_nextTick');
})

總結

經過學習函數調用棧,任務隊列,MacroTask, MicroTask等概念,對js中的事件循環機制有更深的理解,在之後面對setTimeout, setInterval等異步操做時,更清晰的理解其運行機制,避免寫出不可控的代碼。

參考連接:

https://zhuanlan.zhihu.com/p/...

https://zhuanlan.zhihu.com/p/...

http://www.ruanyifeng.com/blo...

相關文章
相關標籤/搜索