【JS事件循环机制event-loop】

目录

0、总结

  • Tasks execute in order, and the browser may render between them 【宏任务按序执行,浏览器可以在它们之间进行渲染】
  • Microtasks execute in order, and are executed:【微任务按序执行,执行与如下时刻:】
    • after every callback, as long as no other JavaScript is mid-execution【每次回调后,只要没有其他 JS 处于执行过程中】
    • at the end of each task【在每次宏任务的末尾】
bash 复制代码
# 进程是 CPU 资源分配的最小单位(是能拥有资源和独立运行的最小单位);
# 线程是 CPU 调度和分派的基本单位,共享进程的内存空间和资源,每个线程有自己的栈空间和程序计数器;
# 浏览器每打开一个 tab 页,就是开了一个进程;

1、Event-Loop 概念

  • 事件循环是浏览器或 node 环境执行 js 代码的一种规则

  • 同步任务都在 JS 引擎线程(主线程)上执行,形成一个执行栈

  • 事件触发线程管理着一个任务队列(宏任务队列),异步任务有了运行结果,就在任务队列中放置一个事件

  • 什么时候用到定时器触发线程?当使用setTimeoutsetInterval时,需要定时器触发线程计时,计时完成后就会将特定的事件推入事件队列中。

    • 为什么要单独的定时器触发线程?因为 JavaScript 引擎是单线程的, 如果处于阻塞线程状态就会影响记计时的准确,因此很有必要单独开一个线程用来计时。
  • 一旦执行栈中的所有同步任务执行完毕(此时 JS 引擎空闲),系统就会读取任务队列,将可运行的异步任务添加到可执行栈中,开始执行。

    为什么有时候setTimeout推入的事件不能准时执行?因为可能在它推入到事件列表时,主线程还不空闲,正在执行其它代码,所以自然有误差。

    • 主线程运行时会产生执行栈,栈中的代码调用某些 api 时,它们会在事件队列中添加各种事件(当满足触发条件后,如 ajax 请求完毕)
    • 而栈中的代码执行完毕,就会读取事件队列中的事件,去执行那些回调
    • 如此循环

2、宏任务-微任务

  • 好文章:Tasks,microtasks,queues and schedules

  • js 代码分为两类:同步代码、异步代码

  • 异步代码又分为:宏任务(macrotask)、微任务(microtask) ,在 ECMAScript 中macrotask称为taskmicrotask称为jobs

  • 浏览器会在一个task执行结束后,在下一个task执行开始前,对页面进行重新渲染

  • microtask 微任务都是添加到微任务队列(Job Queues)中,等待当前 macrotask 执行完毕后执行,而这个队列由 JS引擎线程 维护

    任务 类别
    事件 宏任务
    setTimeout 宏任务
    setInterval 宏任务
    setImmediate(Node 独有) 宏任务
    requestAnimationFrame(浏览器独有) 宏任务
    网络请求 宏任务
    fs.readFile() 宏任务
    UI rendering(浏览器独有) 宏任务
    Promise.then() ------微任务
    async/await ------微任务
    process.nextTick(Node 独有) ------微任务
    Object.observe ------微任务
    MutationObserve ------微任务

3、事件循环执行机制

  • 进入 script 标签,就进入到第一次事件循环(可看作一个宏任务开始)

  • 遇到同步代码立即执行

  • 遇到宏任务放到宏任务队列

  • 遇到微任务放到微任务队列

  • 执行完所有同步代码

  • 执行微任务

  • 微任务执行完毕,本次主线程执行栈清空,开始检查渲染,然后 GUI 线程接管渲染

  • 渲染完毕后,JS引擎线程继续接管,寻找下一个宏任务,重复上述步骤,这种重复不断的机制,就叫做事件循环

    我们在一个微任务中使用 setTimeout() 函数添加一个宏任务,那么这个新的宏任务会在当前微任务执行完毕后立即执行,而不是等待当前宏任务执行完毕再执行。

4、调用栈

  • 调用栈 call stack 也叫执行栈,记录了当前执行的上下文 context 和函数调用链;
  • 所有函数想要执行就必须经过调用栈,因为调用栈可以理清词法环境、变量环境等;
  • 当 js 引擎执行一个函数时,它会将函数的调用信息添加到调用栈的顶部,并在执行完该函数后从调用栈中弹出该函数的信息。如果该函数调用了其他函数,那么这些函数也会依次被添加到调用栈中,并在执行完后弹出。在函数执行期间,调用栈会保持不断增长和收缩的状态。

5、示例

js 复制代码
async function async1() {
  console.log(111) //1
  await async2() //await会阻塞它下一行的代码
  await async3() //异步代码中的微任务1,先挂起
  console.log('async111 end') //7 微任务3 等async3执行完毕后进入任务队列
}
async function async2() {
  console.log('async222 end') //2
}
async function async3() {
  console.log('async333 end') //5
}
async1()
setTimeout(() => {
  //异步代码中的宏任务1,先挂起
  console.log('setTimeout') //9
}, 0)
new Promise((resolve) => {
  console.log('Promise') //3
  resolve()
})
  .then(() => {
    //异步代码中的微任务2,先挂起
    console.log('Promise111') //6
  })
  .then(() => {
    //异步代码中的微任务4,先挂起  前一个.then的回调函数执行后被进入任务队列
    console.log('Promise222') //8
  })
console.log('end') //4
相关推荐
涵信8 分钟前
第十一节:性能优化高频题-响应式数据深度监听问题
javascript·vue.js·性能优化
codingandsleeping33 分钟前
Express入门
javascript·后端·node.js
Vaclee36 分钟前
JavaScript-基础语法
开发语言·javascript·ecmascript
拉不动的猪1 小时前
前端常见数组分析
前端·javascript·面试
一个专注写代码的程序媛2 小时前
vue组件间通信
前端·javascript·vue.js
一笑code3 小时前
美团社招一面
前端·javascript·vue.js
懒懒是个程序员3 小时前
layui时间范围
前端·javascript·layui
烛阴3 小时前
面试必考!一招教你区分JavaScript静态函数和普通函数,快收藏!
前端·javascript
JiangJiang3 小时前
🚀 React 弹窗还能这样写?手撸一个高质量 Modal 玩起来!
前端·javascript·react.js
吃炸鸡的前端4 小时前
el-transfer穿梭框数据量过大的解决方案
前端·javascript