js事件循环机制 宏任务微任务执行时机

事件循环的工作步骤
  1. 先执行同步代码,所有同步代码都在主线程上执行,形成一个执行栈(又称调用栈,先进后出)。
  2. 当遇到异步任务时,会将其挂起并添加到任务队列中(先进先出),宏任务放入宏任务队列,微任务放进微任务队列。
  3. 当执行栈为空时,事件循环先从微任务队列中按顺序取出任务,加入到执行栈中执行。如果微任务队列清空,就从宏任务队列中取出任务加入执行栈中执行。
  4. 重复上述步骤,直到任务队列为空。
执行时机

微任务早于宏任务,每次从宏任务队列读取任务时,微任务队列都是已执行完毕清空的。

微任务执行过程中生成新的异步任务,将其放在对应的队列中,如果有生成新的微任务,会依次将所有的微任务执行完成后再执行宏任务。

宏任务在执行过程生成新的微任务时,那么这些微任务会被添加到微任务队列中,等待当前这个宏任务执行完成后依次执行微任务队列。当所有微任务都执行完成后,才会从宏任务队列中取出下一个任务执行。

调用栈

调用栈(call stack)也叫执行栈,是 JavaScript 运行时用于存储函数调用的数据结构(栈先进后出),它记录了当前执行的上下文(context)和函数调用链。

javascript 复制代码
function bar() {
  return 2;
}
function foo() {
  return bar();
}
function main() {
  console.log(foo());
}
main();

在执行 main() 函数时,它会调用 foo() 函数,将 foo() 函数的调用信息添加到调用栈中。在 foo() 函数中,又调用了 bar() 函数,将 bar() 函数的调用信息也添加到调用栈中。最后,在 bar() 函数中返回结果后,将 bar() 函数的调用信息从调用栈中弹出。接着执行 foo() 函数返回结果,再将 foo() 函数的调用信息从调用栈中弹出。最后,main() 函数也执行完毕,调用栈为空。在函数执行期间,调用栈会保持不断增长和收缩的状态。

任务队列

任务队列(task queue)通常分为两种类型:宏任务队列和微任务队列。只有异步代码才会进入任务队列,然后按照先进先出的顺序执行。

代码示例

javascript 复制代码
async function async1() {
  console.log("start"); // 第1轮
  await async2(); // 第1轮
  await async3(); // 第2轮 因为上一行的await,这里属于微任务,放在微任务队列
  console.log('async111 end'); // 第3轮 第2轮执行的时候,因为上一行的await,这里属于微任务,放在微任务队列,等待第3轮执行。
}
async function async2() {
  console.log('async222 end'); // 第1轮
}
async function async3() {
  console.log('async333 end'); // 第2轮
}
async1(); // 第一轮
setTimeout(() => {
  console.log('setTimeout1'); // 第4轮 放在宏任务队列,等待微任务队列清空后,执行
  new Promise(resolve=>{
  	console.log("setTimeout1->Promise") // 第4轮 同步代码
  	resolve(true)
  }.then(()=>{
  	console.log("setTimeout1->Promise->then") // 第5轮 微任务
  })
}, 0)
new Promise(resolve => {
  console.log('Promise'); // 第1轮,这里属于同步代码
  resolve()
}).then(() => {
  console.log('Promise111'); //第2轮,微任务
  setTimeout(() => 
  	console.log("setTimeout2"); // 第7轮,第2轮的时候,这个任务才被放在宏任务队列中,在setTimeout3之后执行。
  })
}).then(() => {
  console.log('Promise222'); // 第3轮 第2轮执行时,会将这里放在微任务队列中,等待第3轮执行
})
setTimeout(() => {
  console.log('setTimeout3'); // 第6轮 宏任务,本可以在第4轮宏任务一起执行,由于4生成了新的微任务,当4轮宏任务执行完毕后,要从微任务队列中执行微任务后(第5轮),再执行该宏任务
}, 0)
console.log('end'); // 第1轮
// start
// async222 end
// Promise
// end
// async333 end
// Promise111
// async111 end
// Promise222
// setTimeout1
// setTimeout1->Promise
// setTimeout1->Promise->then
// setTimeout3
// setTImeout2
// 第几轮的说法并不准确,仅作分析思路。

参考博客:
JavaScript------事件循环机制(Event Loop)浅析

相关推荐
敲敲了个代码5 小时前
从硬编码到 Schema 推断:前端表单开发的工程化转型
前端·javascript·vue.js·学习·面试·职场和发展·前端框架
dly_blog6 小时前
Vue 响应式陷阱与解决方案(第19节)
前端·javascript·vue.js
消失的旧时光-19437 小时前
401 自动刷新 Token 的完整架构设计(Dio 实战版)
开发语言·前端·javascript
console.log('npc')7 小时前
Table,vue3在父组件调用子组件columns列的方法展示弹窗文件预览效果
前端·javascript·vue.js
我命由我123457 小时前
SVG - SVG 引入(SVG 概述、SVG 基本使用、SVG 使用 CSS、SVG 使用 JavaScript、SVG 实例实操)
开发语言·前端·javascript·css·学习·ecmascript·学习方法
C_心欲无痕8 小时前
vue3 - markRaw标记为非响应式对象
前端·javascript·vue.js
qingyun9898 小时前
深度优先遍历:JavaScript递归查找树形数据结构中的节点标签
前端·javascript·数据结构
胡楚昊8 小时前
NSSCTF动调题包通关
开发语言·javascript·算法
熬夜敲代码的小N9 小时前
Vue (Official)重磅更新!Vue Language Tools 3.2功能一览!
前端·javascript·vue.js
小彭努力中9 小时前
1.在 Vue 3 中使用 Cesium 快速展示三维地球
前端·javascript·vue.js·#地图开发·#cesium·#vue3