在浏览器和node中的Event Loop
文章类型:Vue
发布者:admin
发布时间:2023-02-21
Event Loop:也叫作事件循环,是一种执行机制,主要是解决单线程运行时不会阻塞
在js中,任务分为同步任务和异步任务,异步任务分为:宏任务(macro)和微任务(micro)两种
宏任务必然是在微任务之后才执行的(因为微任务实际上是宏任务的其中一个步骤)
宏任务主要是
1:I/O
2:setTimeout
3:setInterval
4:浏览器中requestAnimationFrame
5:node环境中setImmediate
微任务主要是
1:浏览器中MutationObserver、Promise.then catch finally
2:node环境中process.nextTick
一:浏览器中
有一个主线程(main thread)和调用栈(call-stack)(执行栈),所有的任务都会被放到调用栈等待主线程执行
JS调用栈采用的是后进先出的规则,
1:同步任务就是按照顺序等待执行
2:异步任务会被放入任务队列中,等主线程执行完(调用栈被清空),再推入执行栈执行
具体循环流程:
1:宏任务队列中,按照入队顺序,找到第一个执行的宏任务,放入调用栈,开始执行
2:执行完该宏任务下所有同步任务后,即调用栈清空后,该宏任务被推出宏任务队列,
3:然后微任务队列开始按照队首入队顺序,依次执行其中的微任务,如果在执行microtask的过程中,又产生了microtask,那么会加入到队列的末尾,也会在这个周期被调用执行,
直至微任务队列清空为止,一个事件循环结束
4:接着从宏任务队列中,找到下一个执行的宏任务,开始第二个事件循环,直至宏任务队列清空为止
说白了就是宏任务执行一次,微任务多次,微任务队列全部执行完,再去宏任务队列中取第一个任务执行,一直沿着这个模式执行,类似于内外循环,外循环执行一次,内循环执行所有
console.log("a"); //第一个同步任务
setTimeout(function () { //第一个宏任务
console.log("b");
}, 0);
new Promise((resolve) => { //第二个同步任务
console.log("c"); //微任务1
resolve();
})
.then(function () {
console.log("d"); //微任务2
})
.then(function () {
console.log("e"); ////微任务3
});
console.log("f"); //第三个同步任务
/*
* 输出结果:a c f d e b
*/
async function async1() {
console.log("a");
const res = await async2();
console.log("b");
}
async function async2() {
console.log("c");
return 2;
}
console.log("d"); //第一个同步任务
setTimeout(() => { // 第二个宏任务
console.log("e");
}, 0);
async1().then(res => {
console.log("f")
})
new Promise((resolve) => {
console.log("g");
resolve();
}).then(() => {
console.log("h");
});
console.log("i");
/**
* 输出结果:d a c g i b h f e
*/
二:Node环境中
setImmediate:为一次Event Loop执行完毕后调用
process.nextTick:类似于Promise和MutationObserver的微任务实现,在代码执行的过程中可以随时插入nextTick,并且会保证在下一个宏任务开始之前所执行,优先级大于其他微任务,
若同时存在 process.nextTick 和 promise,则会先执行前者
Event Loop每次都依次经过timers、I/O、idle, prepare、poll、check、close callbacks6个阶段,每个阶段都有自己callback队列,进入该阶段,读取callback,当队列为空或者达到最大数量,则进入下一个阶段
1:timers执行setTimeout() 和 setInterval()中到期的回调。并且是由 poll 阶段控制的。 同样,在 Node 中定时器指定的时间也不是准确时间,只能是尽快执行
2:I/O callbacks上一轮循环中有少数的I/Ocallback会被延迟到这一轮的这一阶段执行
3:idle, prepare仅内部使用
4:poll最为重要的阶段,轮询阶段,执行队列中的 I/O 队列,执行I/O 回调,在适当的条件下会阻塞在这个阶段
5:check 执行setImmediate的回调
6:close callbacks执行close事件的回调
setTimeout(() => {
console.log(1);
}, 0)
setImmediate(() => {
console.log(2);
})
new Promise(resolve => {
console.log(3);
resolve()
console.log(4);
})
.then(() => {
console.log(5);
})
console.log(6);
process.nextTick(() => {
console.log(7);
})
console.log(8);
/* 打印结果:
3
4
6
8
7
5
1
2
*/