部分的内容出处:面试题:说说事件循环机制(满分答案来了)
浏览器中的事件循环
JavaScript代码的执行过程中,除了依靠函数调用栈来搞定函数的执行顺序外,还依靠任务队列(task queue)来搞定另外一些代码的执行。整个执行过程,我们称为事件循环过程。一个线程中,事件循环是唯一的,但是任务队列可以拥有多个。任务队列又分为macro-task(宏任务)与micro-task(微任务),在最新标准中,它们被分别称为task与jobs。
macro-task大概包括:
- script(整体代码)
- setTimeout
- setInterval
- setImmediate
- I/O
- UI render
micro-task大概包括:
- process.nextTick
- Promise
- Async/Await(实际就是promise)
- MutationObserver(html5新特性)
例子1:
setTimeout(_ => console.log(1))
new Promise(resolve => {
resolve()
console.log(2)
}).then(_ => {
console.log(3)
})
console.log(4)
执行结果:
2
4
3
1
运行过程:
1、将宏任务(setTimeout的回调事件)推进宏任务队列( event queue )
2、立即执行new Promise 打印:2 并将.then内的回调事件推进微任务队列
3、console.log(4)由于处于主线程, 因此直接打印:4
4、检查微任务队列中是否还有微任务未执行,发现有1个.then的回调事件未执行,因此执行打印:3
5、执行宏任务的回调事件, 打印: 1
列子2:
console.log('sync statement 1'); // 1、处于主线程, 直接执行
Promise.resolve().then(function() { // 2、将.then微任务推进微任务队列
console.log('micro task 1'); // 5、执行微任务, 并将之后的.then推进微队列
setTimeout(function() { // 6、将回调推进宏任务队列
console.log('macro task 1'); // 10、执行宏队列
}, 0);
}).then(function() {
console.log('micro task 2'); // 7、执行微队列
});
setTimeout(function() { // 3、将宏任务推进宏任务队列
console.log('macro task 2') // 7、执行宏队列
Promise.resolve().then(function(){ // 8、将微任务推进微队列
console.log('micro task 3'); // 9、由于当前宏任务下的微任务只有.then的回调事件,因此执行
})
}, 0)
console.log('sync statement 2'); // 4、处于主线程,直接执行
执行结果:
'sync statement 1'
'sync statement 2'
'micro task 1'
'micro task 2'
'macro task 2'
'micro task 3'
'macro task 1'
运行过程示意图:
例子3:
//主线程直接执行
console.log('1');
//丢到宏事件队列中
setTimeout(function() {
console.log('2');
process.nextTick(function() {
console.log('3');
})
new Promise(function(resolve) {
console.log('4');
resolve();
}).then(function() {
console.log('5')
})
})
//微事件1
process.nextTick(function() {
console.log('6');
})
//主线程直接执行
new Promise(function(resolve) {
console.log('7');
resolve();
}).then(function() {
//微事件2
console.log('8')
})
//丢到宏事件队列中
setTimeout(function() {
console.log('9');
process.nextTick(function() {
console.log('10');
})
new Promise(function(resolve) {
console.log('11');
resolve();
}).then(function() {
console.log('12')
})
})
运行结果:
执行结果:
1 7 6 8 2 4 3 5 9 11 10 12
执行过程:
1、由于console.log('1')处于主线程, 因此直接执行 打印: 1
2、将宏任务setTimeout的回调事件推入宏任务队列, 待微任务队列执行完成后再取出
3、将微任务process.nextTick的回调事件推入微任务队列
4、直接执行new Promise, 打印: 7, 并将微任务.then的回调事件推进微任务队列等待后续取出执行
5、执行setTimeout, 并将宏任务推入宏任务队列
6、检查微任务队列中是否还有微任务未执行, 由以上几点可知微任务队列有process.nextTick、.then对应的回调事件, 根据事件的先进先出原则, 取出并执行, 分别打印出: 6、8
7、此时微任务队列里的任务已经执行完毕, 开始取出宏任务队列中的宏任务
8、根据事件的先进先出原则, 先执行第一个setTimeout的回调事件, 打印: 2, 将微任务process.nextTick的回调事件推进微任务队列, 再执行new Promise, 打印4, 并将微任务.then也推进微任务队列。
9、然后检查微任务队列中是否还有未执行的微任务, 由上可知有两个,分别是process.nextTick、.then对应的回调事件, 取出并分别先后执行, 打印: 3、4
10、当前的宏任务产生的微任务已经全部执行完毕, 因此继续从宏任务队列中取出未执行的宏任务,由上我们可知还有一个宏任务, 也就是第二个setTimeout
11、取出第二个setTimeout的回调事件,执行过程同步骤8、9和10相同, 先后打印出: 9、11、10、12
运行过程的示意图:
例子4:
console.log('start')
setTimeout(() => {
console.log('定时器...')
}, 0);
new Promise((resolve)=>{
resolve();
console.log('new Promise');
}).then(()=>{
console.log('...then')
})
function sync(){
console.log('同步代码')
}
async function async2(){
new Promise(resolve=>{
resolve()
}).then(()=>{
console.log('async2...then')
})
}
async function async1(){
await async2();
console.log('继续执行了吗')
}
sync();
async1();
console.log('end')
运行结果:
打印结果:
'start'
'new Promise'
'同步代码'
'end'
'...then'
'async2...then'
'继续执行了吗'
'定时器...'
执行过程示意图:
例子5:
async function async1() {
console.log('async1 start');
await async2();
//更改如下:
setTimeout(function() {
console.log('setTimeout1')
},0)
}
async function async2() {
//更改如下:
setTimeout(function() {
console.log('setTimeout2')
},0)
}
console.log('script start');
setTimeout(function() {
console.log('setTimeout3');
}, 0)
async1();
new Promise(function(resolve) {
console.log('promise1');
resolve();
}).then(function() {
console.log('promise2');
});
console.log('script end');
运行结果:
'script start'
'async1 start'
'promise1'
'script end'
'promise2'
'setTimeout3'
'setTimeout2'
'setTimeout1'
运行过程示意图:
例子6:
new Promise(resolve => {
resolve(1);
Promise.resolve().then(() => {
console.log(2)
});
console.log(4)
}).then(t => {
console.log(t)
});
console.log(3);
运行结果:
运行结果:
4
3
2
1
运行过程:
运行过程:
1、执行new Promise(), 执行resolve(1)抛出返回值1, 继续执行微任务Promise.resolve().then(), 将.then的回调事件推入微任务队列, 继续执行console.log(4), 打印: 4, 执行到new Promise的.then, 将.then的微任务回调事件推入到微任务队列中
2、继续执行处于主线程的console.log(3), 打印: 3
3、检查微任务队列是否还有未执行的微任务, 由上可知有两个: 分别是: console.log(2)、console.log(t), 其中t的值由resolve(1)传入, 分别先后取出执行, 先后打印2、1
运行过程示意图:
例子7:
async function async1() {
console.log('async1 start');
await async2();
console.log('async1 end');
}
async function async2() {
console.log('async2');
}
console.log('script start');
setTimeout(function() {
console.log('setTimeout');
}, 0)
async1();
new Promise(function(resolve) {
console.log('promise1');
resolve();
}).then(function() {
console.log('promise2');
});
console.log('script end');
运行结果:
运行结果:
'script start'
'async1 start'
'async2'
'promise1'
'script end'
'async1 end'
'promise2'
'setTimeout'
运行过程:
运行过程:
1、处于主线程的console.log('script start')直接运行, 打印出: 'script start'
2、将setTimeout对应的回调事件推入宏任务队列
3、执行async1(), 打印: 'async1 start', 然后执行await async2(), 打印: async2, 这里要注意: 执行await后的语句async2()后会立即跳出async1, 不会继续执行接下来的console.log('async1 end'); 这是因为执行await后会抛出一个fulfilled(成功)状态的promise对象, 后续的执行代码console.log('async1 end')就等同于.then里的回调事件, 因此console.log('async1 end')会被推入到微任务队列等待执行
4、跳出async1后继续执行new Promise(), 打印: 'promise1', 并将.then的回调事件推入到微任务队列中等待执行
5、继续执行console.log('script end'); 打印: 'script end',
6、检查微任务队列中是否还有未执行的微任务, 由上可知微任务队列中还有: async1
中的console.log('async1 end')、new Promise中.then的console.log('promise2')这两个事件未被执行, 根据先进先出的执行原则, 分别执行打印的结果是: 'async1 end'、'promise2'
7、此时微任务队列中任务已经执行完毕, 开始检查宏任务队列中是否还有宏任务未执行, 由上可知还有一个setTimeout产生的宏任务事件, 取出并执行, 打印: 'setTimeout'
以上执行过程的描述是我个人对事件循环机制的理解做出的,如有不足或错误地方,希望多多指出。