在浏览器和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
*/