简单理解 JavaScript Event Loop

先看一段代码,也是一道经典面试题:html

(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);
})()

其输出结果为:node

// 1
// 2
// 3
// 5
// 4

咱们知道,JavaScript 在同一时间片内只能执行一个任务:web

主线程会依次执行代码,当执行到函数的时候会将函数加入执行栈,当函数执行完毕后再将其出栈,直至代码执行完毕。当执行栈为空时,runtime 会从任务队列(先入先出)中取出待执行的回调函数并执行,入栈、出栈的过程同上。这个机制就叫作 Event Loop。面试

由此能够认为,Event Loop其实是一系列的回调函数集合。api

举例来讲:在浏览器中,对于网络请求等须要等待一段时间才会返回结果的操做,咱们一般采用异步回调来处理,这个回调就会放入任务队列中。此时浏览器会在其它线程中执行异步操做,操做完成后将回调函数放入主线程任务队列中。Event Loop负责在主线程执行完毕后将任务队列中的函数放入执行栈中,由主线程执行。当主线程将执行栈中的函数执行完毕后,再次读取任务队列,造成循环。因此即便主线程阻塞了,任务队列依然可以被添加函数,由于任务队列的添加是由浏览器负责的。(不一样的 runtime 实现可能不一样)promise

另外须要注意的是 Promise.then 是异步执行的,而建立 Promise 实例是同步执行的。这就解释了为何一、二、3输出在四、5以前。浏览器

但为何5 会输出在4前面呢?网络

JavaScript 中的任务又分为MacroTask 与 MicroTask 两种。app

典型的 MacroTask 包含了 :webapp

  • setTimeout
  • setInterval
  • setImmediate
  • requestAnimationFrame
  • I/O
  • UI rendering

而常见的MicroTask 包含了

  • process.nextTick
  • Promises
  • Object.observe
  • MutationObserver

Event Loop 中有一个或多个Task Queue,即MacroTask Queue,仅有一个Job Queue,即MicroTask Queue。Task Queue的执行是按照回调顺序先入先出,而在 MacroTask 的执行间隙中会清空已有的 MicroTask Queue

回到代码中,setTimeout(function() {console.log(4)}, 0); 既然延迟设置为0,为何5会在4以前输入呢?

那是由于setTimeout设置为0的时候,runtime其实并非0,在主流浏览器中会将其设置为4,而 node 则会将其设置为1。那么如今代码的执行顺序就很清晰了:

console.log(1);    // 建立 Promise 主线程执行
...
console.log(2); // 建立 Promise 主线程执行

console.log(3); // test 函数当即执行, 主线程执行
... 
console.log(5); // 主线程执行完毕,执行MicroTask Queue,即 promise.then
... 
console.log(4);    // 执行 setTimeout(4)

参考资料:

https://developer.mozilla.org...

https://html.spec.whatwg.org/...

相关文章
相关标签/搜索