浏览器事件循环
-
原理:Event Loop(事件循环)中,每一次循环称为 tick, 每轮循环都是由一个宏任务+多个微任务组成
- 首先,执行第一个宏任务:全局Script脚本。
- 产生的的宏任务和微任务进入各自的队列中。
- 执行完Script后,把当前的微任务队列清空。完成一次事件循环。
- 接着再取出一个宏任务,同样把在此期间产生的回调入队。再把当前的微任务队列清空。以此往复。
-
原因:JS是单线程的,一次只能做一件事情。为了不阻塞用户交互。JavaScript程序采用了异步事件驱动编程(Event-driven programming)模型(通过交互驱动)。异步事件驱动编程实现原理就是事件循环(Event Loop)。
- 同步异步:在等待异步任务准备的同时,JS引擎去执行其他同步任务,等到异步任务准备好了,再去执行回调。
- 事件循环:异步任务的回调部分需要在合适时机交还给JS线程执行,这就需要事件通知。任务是由一个队列组成的,异步任务的回调遵循先进先出,在JS引擎空闲时会一轮一轮地被取出,所以被叫做循环。
-
任务:队列中任务的不同,分为宏任务和微任务
- 宏任务:script代码,setTimout,setInterval,setImmediate,UI 渲染、 I/O、postMessage、 MessageChannel
- setTimeout 0ms 也不是立刻执行,它有一个默认最小时间,为4ms。
- 微任务:promise,process.nextTick,MutationObserver
- 宏任务:script代码,setTimout,setInterval,setImmediate,UI 渲染、 I/O、postMessage、 MessageChannel
node端事件循环
node的事件循环由6个宏任务队列+6个微任务队列组成。
-
Timers:定时器setTimeout/setInterval回;
-
I/O回调
-
Idle,prepare
-
Poll :获取新的 I/O 事件, 例如操作读取文件等;
-
Check:setImmediate回调;
-
close回调
其执行规律是:在一个宏任务队列全部执行完毕后,去清空一次微任务队列,然后到下一个等级的宏任务队列,以此往复。一个宏任务队列搭配一个微任务队列。
除此之外,node端微任务也有优先级先后:
- process.nextTick;
- promise.then 等;
async/await的执行顺序
async,await: 是建立在 promise 的基础上的 ,一般成对出现。当一个函数前加了async,就表示这个函数里很大可能有异步函数,而在第一个await表达式出现之前,异步函数内部的代码都是按照同步方式执行的。await所在表达式是同步的。
async function fn1(){
console.log(1)
await fn2()
console.log(3)
}
async function fn2(){
console.log('fn(2)')
}
fn1()
console.log(2)
//结果:1 fn2() 2 3
实例1:如下代码输出顺序是什么?
console.log('script start');
setTimeout(function() {
console.log('setTimeout');
}, 0);
Promise.resolve()
.then(function() {
console.log('promise1');
}).then(function() {
console.log('promise2');
});
console.log('script end');
答案
解读:
宏任务1
按顺序执行:script start
promise的微任务入队
按顺序执行:script end
promise的微任务出队
按顺序执行:输出promise1
按顺序执行:输出promise2
setTimeout作为第二个宏任务
输出setTimeout
实例2:如下代码输出顺序是什么?
async function test1() {
console.log('start test1');
console.log(await test2());
console.log('end test1');
}
async function test2() {
console.log('test2');
return await 'return test2 value';
}
test1();
console.log('start async');
setTimeout(function () {
console.log('setTimeout');
}, 0)
new Promise(function (resolve) {
console.log('promise1');
resolve();
}).then(function () {
console.log('promise2');
});
console.log('end async');
//输出
/*
start test1
test2
start async
promise1
end async
promise2
return test2 value
end test1
setTimeout
*/
解读:需要注意的是promise2 => end test1。 原因是:return await是第二层的微任务了,所以是它的入队会在Promise后面。
实例3:如下代码输出顺序是什么?
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();
console.log('promise2')
}).then(function () {
console.log('promise3');
});
console.log('script end');
输出
script start
async1 start
async2
promise1
promise2
script end
async1 end
promise3
setTimeout
解读
宏任务1:
按顺序执行:script start
setTimeout宏任务入队
async1函数顺序执行:async1 start
await行的代码同步执行:async2。函数中没有异步操作,等价于直接执行
await后的微任务入队
new Promise内的代码同步执行
promise1
resolve执行
执行promise2
Promise微任务入队
同步执行后面的代码:script end
Promise微任务出队
微任务按顺序出队
await后的微任务出队:async1 end
promise出队执行:promise3
宏任务2:
setTimeout
宏任务和微任务的区分是为了做什么? 优先级?
宏任务和微任务的区分是为了执行 JavaScript 代码中的异步操作。在 JavaScript 中,异步操作可以使用回调函数、Promise、async/await 等方式实现。
宏任务和微任务的优先级是不同的,微任务的优先级高于宏任务。当 JavaScript 引擎执行完一个宏任务时,会检查是否有微任务需要执行,如果有,它会先执行微任务队列中的任务,直到微任务队列为空,然后再继续执行下一个宏任务。
这个机制的设计是为了保证 JavaScript 代码的正确性和性能。例如,当我们使用 Promise 来处理异步操作时,可以将 then 方法中的代码作为微任务,这样可以保证在 Promise 完成后立即执行 then 方法中的代码,而不需要等待下一个宏任务的执行。这样可以提高代码的响应速度和性能。
参考资料:
- 事件循环机制:juejin.cn/post/684490…
- 为什么要这样设计:juejin.cn/post/707309…
- 事件循环示例:blog.csdn.net/znhyXYG/art…