先看一段代码,也是一道经典面试题: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
而常见的MicroTask 包含了
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)
参考资料: