likes
comments
collection
share

宏任务、微任务和同步代码的执行关系与事件循环 流程机制详解

作者站长头像
站长
· 阅读数 23

前言

概念

事件循环(Event Loop)

事件循环是浏览器或 Node.js 中的一个机制,它负责调度和执行任务队列中的任务。事件循环的工作过程如下:

  1. 执行一个宏任务(从宏任务队列中取第一个任务执行(即Script脚本))。
  2. 执行当前栈内所有的微任务(从微任务队列中取任务执行,直到微任务队列为空)。
  3. 更新渲染(如果有必要)。
  4. 重复上述过程直至代码全部执行完毕。

同步代码

同步代码是指在主线程上按顺序执行的代码。这些代码会在调用时立即执行,并且在所有同步代码执行完毕之前不会停止。

宏任务(Macro Task)

宏任务是一些调度到事件循环队列中的任务,这些任务通常包括:

  • script脚本,标签
  • setTimeout
  • setInterval
  • setImmediate
  • I/O 操作
  • UI 渲染

每次事件循环都会执行一个宏任务,然后执行微任务队列中的所有任务。常见的宏任务包括计时器和 I/O 事件。

微任务(Micro Task)

微任务是在当前执行上下文结束后立即执行的任务。微任务队列中的任务会在当前宏任务执行完毕之后,但在下一个宏任务开始之前执行。常见的微任务包括:

  • Promise 回调(Promise.then
  • MutationObserver
  • process.nextTick(Node.js)

执行机理

JavaScript的事件循环机制遵循以下步骤循环进行:

  1. 执行当前宏任务:这包括所有同步代码以及在执行过程中遇到的、应立即安排但稍后执行的宏任务(比如通过setTimeoutsetInterval、I/O等安排的任务)。
  2. 清空微任务队列:在当前宏任务执行完毕后,会检查微任务队列(包括Promise的回调、process.nextTick等),并将其中的所有任务执行完毕。这一步保证了微任务能够尽快得到响应,因为在每一个宏任务结束后都会检查并尝试清空微任务队列。
  3. 继续下一轮事件循环:一旦当前宏任务执行完毕,微任务队列也被清空,事件循环就会继续到下一个宏任务(如果有的话)。这个过程会不断重复,直到宏任务队列和微任务队列都为空,或者有特殊的停止条件(如程序退出)。

“先执行最大的宏任务Script脚本,在执行已进栈的全部微任务,再执行一个宏任务,再清空微任务队列。如果遇到异步任务就再进行注册执行,循环往复”

看下面的示例:

setTimeout(() => {
console.log('定时器'); //异步宏任务
},0)
new Promise((resolve) => {
console. log('同步代码')  //同步
resolve( '异步代码')  
}). then((res) => {
console.log(res);   //异步微任务
})
console. log('奥特曼'); //同步

注意new Promise是实例化一个构造函数这个过程是同步的,而.then方法是异步 宏任务、微任务和同步代码的执行关系与事件循环 流程机制详解

看执行流程图解: 宏任务、微任务和同步代码的执行关系与事件循环 流程机制详解

宏任务、微任务和同步代码的执行关系与事件循环 流程机制详解

执行栈每次在执行异步任务时,如果任务内部存在其他的异步任务时会进行事件注册,到时间后便会将该任务放入对应的任务队列中进行执行,,而执行顺序便是一个宏任务,然后清空当前微任务队列。

测试题

试题1

async function async1() {
	console.log('async1 start');  //立即执行
	await async2();  //无返回值,后面放入微任务
	console.log('asnyc1 end');
}

async function async2() {
	console.log('async2');  //立即执行
}

console.log('script start');  //立即执行

setTimeout(() => {
	console.log('setTimeOut'); //宏任务
}, 0);

async1();

new Promise(function (reslove) {  
	console.log('promise1');  
	reslove();
}).then(function () {
	console.log('promise2');
})
console.log('script end');

宏任务、微任务和同步代码的执行关系与事件循环 流程机制详解

有点懵没关系,这里我们来图解解释一下:首先,执行全局脚本的同步代码,打印出'script start'。 接着,调用async1()函数,打印'async1 start'async1内部,首先遇到的是同步代码console.log('async1 start'),因此立即执行。进入async1函数执行调用await async2()。执行async2函数,打印出'async2'。注意,尽管async2函数没有返回Promise,但await关键字会使async1在此暂停,等待当前事件循环中的所有微任务执行完毕后,再继续执行async1的剩余部分。在async1暂停前,遇到new Promise构造函数,立即执行构造函数内的同步代码,打印出'promise1'。构造函数执行完毕,Promise被创建并立即解决,其.then回调被加入到微任务队列。在当前事件循环的微任务阶段,首先执行async1await后的代码,打印出'asnyc1 end'。紧接着,执行new Promise.then回调,打印出'promise2'。当所有当前微任务处理完毕,事件循环检查宏任务队列。此时,setTimeout设定的回调函数(打印'setTimeOut')已经准备好执行,因为它被安排在0毫秒后执行,但实际上会在当前脚本所有微任务执行完毕后执行。

宏任务、微任务和同步代码的执行关系与事件循环 流程机制详解

试题1

async 函数在调用时会立即返回一个 Promise,这个过程是同步的,await 表达式会使 async 函数暂停执行,await 后面的代码会被放入微任务队列。

  console.log('Start');

setTimeout(() => {
  console.log('Timeout 1');  //宏任务
}, 0);

Promise.resolve().then(() => {
  console.log('Promise 1');  //微任务
});

async function asyncFunction() {
  await new Promise(resolve => setTimeout(resolve, 10));
  console.log('Async Function');  //在未返回resolve前被放入微任务
}

asyncFunction();

console.log('End');

揭晓答案:

宏任务、微任务和同步代码的执行关系与事件循环 流程机制详解

转载自:https://juejin.cn/post/7385350484411121690
评论
请登录