搞懂js中 eventloop事件循环和Promise面试题
js事件循环中的异步队列有两种:macro(宏任务)队列和 micro(微任务)队列。
常见的 macro-task 比如:setTimeout、setInterval、 setImmediate、script(整体代码)、 I/O 操作、UI 渲染等。
常见的 micro-task 比如: process.nextTick、Promise、MutationObserver 等。
循环过程
我们先看一个完整的 Event Loop 过程。
初始状态:micro 队列空,macro 队列里有且只有一个 script 脚本(整体代码为宏任务);
执行script代码,创建的宏任务推到宏任务调用栈中,创建的微任务推到微任务调用栈中;【宏任务阶段】
执行微任务,调出当前微任务栈的所有微任务,一次执行,其中如果有宏任务推到宏任务栈中【微任务阶段】
执行宏任务,调出当前宏任务栈中的第一个宏任务,其中创建的微任务推到微任务栈中【宏任务阶段】
如果代码未结束,循环执行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"]
此时同步代码执行可以直接得到一下输出
sync
promise
此时【宏任务阶段】执行完毕,根据上面的规则我们知道,【宏任务阶段】过后就要进入【微任务阶段】了,我们继续往下分析
阶段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 = []
所以这个阶段会输出以下结果
then
then-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
//微任务阶段
then
then - than
//宏任务阶段
setTimeout
//微任务阶段(没有任务,没有输出)
//宏任务阶段
promise - setTimeout
//微任务阶段
promise - setTimeout - then
//宏任务阶段
then - setTimeout
//微任务阶段
then-setTimeout-then
从结果中我们也可以证实上面两个规则。不论多复杂的题目,只要按照这两个规则解析,就可以化繁为简,从容应对