vlambda博客
学习文章列表

搞懂js中 eventloop事件循环和Promise面试题


js事件循环中的异步队列有两种:macro(宏任务)队列和 micro(微任务)队列。


常见的 macro-task 比如:setTimeout、setInterval、 setImmediate、script(整体代码)、 I/O 操作、UI 渲染等。


常见的 micro-task 比如: process.nextTick、Promise、MutationObserver 等。


循环过程

我们先看一个完整的 Event Loop 过程。


  1. 初始状态:micro 队列空,macro 队列里有且只有一个 script 脚本(整体代码为宏任务);

  2. 执行script代码,创建的宏任务推到宏任务调用栈中,创建的微任务推到微任务调用栈中;【宏任务阶段】

  3. 执行微任务,调出当前微任务栈的所有微任务,一次执行,其中如果有宏任务推到宏任务栈中【微任务阶段】

  4. 执行宏任务,调出当前宏任务栈中的第一个宏任务,其中创建的微任务推到微任务栈中【宏任务阶段】

如果代码未结束,循环执行3,4步骤,从上我们可以总结两个规律


  • 宏任务和微任务阶段是轮番交替进行的

  • 每个宏任务阶段只执行当前宏任务栈中的第一个任务,执行完就切换到微任务阶段,而微任务阶段却是要执行当前微任务栈的所有微任务,微任务阶段才结束


例题分析

如果不理解两个规律那么我们就来试着做一道promise的面试题来加深一下

console.log('sync'); setTimeout(function () { console.log('setTimeout')}, 0); var promise = new Promise(function (resolve, reject) { console.log('promise'); setTimeout(function() { promise.then(function(){ console.log('promise-setTimeout-then')  }) console.log('promise-setTimeout') }, 0); resolve();}); promise.then(function() { setTimeout(function() {  promise.then(function(){ console.log('then-setTimeout-then')  }) console.log('then-setTimeout') }, 0); console.log('then'); promise.then(function(){ console.log('then-than')  })})




我们先分析一下这段代码中有几个宏任务微任务阶段,我们假装自己是电脑执行一下代码


首先页面加载后同步代码会被推到宏任务中,故第一步是执行script的【宏任务阶段】,我们将宏任务和微任务将分别用两个数组来表示

//宏任务 macro = ["script同步代码"]//微任务 micro = []script代码开始执行


注://注释部分表示不在当前阶段执行的代码


阶段1【宏任务阶段】


执行第一个宏任务 script同步代码,执行后删除当前宏任务

console.log('sync');/**监测到settimeout,创建宏任务,并push到宏任务栈中 宏任务+1 macro = ['setTimeout'] **///setTimeout(function () {// console.log('setTimeout')//}, 0); var promise = new Promise(function (resolve, reject) { console.log('promise'); /** 宏任务+1 macro = ['setTimeout','promise-setTimeout'] **/ setTimeout(function() { // promise.then(function(){ // console.log('promise-setTimeout-then')  // }) // console.log('promise-setTimeout') }, 0); resolve();});/**监测到then,创建宏任务,并push到微任务栈中 微任务+1 micro = ['then']**/promise.then(function() { // setTimeout(function() {  // promise.then(function(){ // console.log('then-setTimeout-then')  // }) // console.log('then-setTimeout') // }, 0); // console.log('then'); // promise.then(function(){ // console.log('then-than')  // })})



当同步代码执行后我们任务栈的结果为

//宏任务 macro = ['setTimeout','promise-setTimeout']//微任务 micro = ["then"]



此时同步代码执行可以直接得到一下输出

syncpromise



此时【宏任务阶段】执行完毕,根据上面的规则我们知道,【宏任务阶段】过后就要进入【微任务阶段】了,我们继续往下分析


阶段2【微任务阶段】

// console.log('sync'); // setTimeout(function () {// console.log('setTimeout')// }, 0); // var promise = new Promise(function (resolve, reject) {// console.log('promise');// setTimeout(function() {// promise.then(function(){// console.log('promise-setTimeout-then') // })// console.log('promise-setTimeout')// }, 0);// resolve();// }); promise.then(function() { /** 宏任务+1 macro = ['setTimeout','promise-setTimeout','then-setTimeout'] **/ setTimeout(function() {  // promise.then(function(){ // console.log('then-setTimeout-then')  // }) // console.log('then-setTimeout') }, 0); console.log('then'); /** 微任务+1 micro = ['then','then-than'] **/ promise.then(function(){ console.log('then-than')  })})

根据上面的规则2我们知道,微任务阶段会把微任务栈的任务全部执行,所以此阶段新增的 then-then 也会被执行,最终微任务和宏任务栈里面的结果为


//宏任务 macro = ['setTimeout','promise-setTimeout','then-setTimeout']//微任务 micro = []

所以这个阶段会输出以下结果


thenthen-than

根据上面的规则我们知道,【微任务阶段】结束了,又该开始【宏任务阶段】了,我们要注意宏任务阶段只执行任务栈内的第一个宏任务 就是 setTimeout


阶段3【宏任务阶段】


执行宏任务中的第一个任务 setTimeout


// console.log('sync'); setTimeout(function () { console.log('setTimeout')}, 0); // var promise = new Promise(function (resolve, reject) {// console.log('promise');// setTimeout(function() {// promise.then(function(){// console.log('promise-setTimeout-then') // })// console.log('promise-setTimeout')// }, 0);// resolve();// }); // promise.then(function() {// setTimeout(function() { // promise.then(function(){// console.log('then-setTimeout-then') // })// console.log('then-setTimeout')// }, 0);// console.log('then');// promise.then(function(){// console.log('then-than') // })// })

第三阶段没有新增任务,执行结果


//宏任务 macro = ['promise-setTimeout','then-setTimeout']//微任务 micro = []

输出


setTimeout

再次执行 微任务


阶段4【微任务阶段】

//宏任务 macro = ['promise-setTimeout','then-setTimeout']//微任务 micro = []

此时发现微任务栈为空,并没有什么操作,跳过执行第五阶段


阶段5【宏任务阶段】


执行宏任务中第一个任务 promise-setTimeout



// console.log('sync'); // setTimeout(function () {// console.log('setTimeout')// }, 0); // var promise = new Promise(function (resolve, reject) {// console.log('promise'); setTimeout(function() { /** 微任务+1 micro = ['promise-setTimeout-then'] **/ promise.then(function(){ // console.log('promise-setTimeout-then') }) console.log('promise-setTimeout') }, 0);// resolve();// }); // promise.then(function() {// setTimeout(function() { // promise.then(function(){// console.log('then-setTimeout-then') // })// console.log('then-setTimeout')// }, 0);// console.log('then');// promise.then(function(){// console.log('then-than') // })// })

我们看到当前阶段宏任务中又增加了微任务,故执行结果

//宏任务 macro = ["then-setTimeout"]//微任务 micro = ["promise-setTimeout-then"]

输出


promise-setTimeout

继续执行


阶段6【微任务阶段】

执行微任务栈中所有微任务

// console.log('sync'); // setTimeout(function () {// console.log('setTimeout')// }, 0); // var promise = new Promise(function (resolve, reject) {// console.log('promise');// setTimeout(function() { promise.then(function(){ console.log('promise-setTimeout-then')  })// console.log('promise-setTimeout')// }, 0);// resolve();// }); // promise.then(function() { // setTimeout(function() {  // promise.then(function(){ // console.log('then-setTimeout-then')  // }) // console.log('then-setTimeout') // }, 0);// console.log('then');// promise.then(function(){// console.log('then-than') // })// })

执行结果


//宏任务 macro = ["then-setTimeout"]//微任务 micro = []

输出

promise-setTimeout-then

阶段7【宏任务阶段】

之后阶段,执行最后一个宏任务



// console.log('sync'); // setTimeout(function () {// console.log('setTimeout')// }, 0); // var promise = new Promise(function (resolve, reject) {// console.log('promise');// setTimeout(function() {// promise.then(function(){// console.log('promise-setTimeout-then') // })// console.log('promise-setTimeout')// }, 0);// resolve();// }); // promise.then(function() { setTimeout(function() { /** 微任务+1 micro = ['then-setTimeout-then'] **/ promise.then(function(){ // console.log('then-setTimeout-then') }) console.log('then-setTimeout') }, 0);// console.log('then');// promise.then(function(){// console.log('then-than') // })// })

任务栈此时的状态

//宏任务 macro = []//微任务 micro = ["then-setTimeout-then"]

输出


then-setTimeout

 


阶段8【微任务阶段】


执行最后一个微任务then-setTimeout-then ,代码省略


输出


then-setTimeout-then

任务栈清空,运行结束


最终版输出结果


//宏任务阶段sync promise//微任务阶段thenthen - than//宏任务阶段setTimeout//微任务阶段(没有任务,没有输出)//宏任务阶段promise - setTimeout//微任务阶段promise - setTimeout - then//宏任务阶段then - setTimeout//微任务阶段then-setTimeout-then

从结果中我们也可以证实上面两个规则。不论多复杂的题目,只要按照这两个规则解析,就可以化繁为简,从容应对