【JavaScript】究竟什么是事件循环 任务队列的分类( 宏任务和微任务)

本文深入解析JavaScript事件循环机制,包括宏任务与微任务的区别及执行流程。探讨了浏览器内核如何处理不同类型的线程,以及这些线程如何与JS引擎交互以确保网页的流畅运行。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

线程 与 进程

JS 是单线程执行的,指的是一个进程里只有一个主线程
进程是 CPU 资源分配的最小单位;
线程是 CPU 调度的最小单位
多进程:
在同一个时间里,同一个计算机系统中如果允许两个或两个以上的进程处于运行状态。
多进程带来的好处是明显的,比如你可以听歌的同时,打开编辑器敲代码,编辑器和听歌软件的进程之间丝毫不会相互干扰。
多线程:
程序中包含多个执行流,即在一个程序中可以同时运行多个不同的线程来执行不同的任务
也就是说允许单个程序创建多个并行执行的线程来完成各自的任务。

浏览器内核

以 Chrome 浏览器中为例,当你打开一个 Tab 页时,其实就是创建了一个进程,一个进程中可以有多个线程
比如渲染线程、JS 引擎线程、HTTP 请求线程等等
当你发起一个请求时,其实就是创建了一个线程,当请求结束后,该线程可能就会被销毁

  简单来说浏览器内核是通过取得页面内容、整理信息(应用 CSS)、计算和组合最终输出可视化的图像结果,通常也被称为渲染引擎。

浏览器内核是多线程,在内核控制下各线程相互配合以保持同步,一个浏览器通常由以下常驻线程组成:

  1. GUI 渲染线程
  2. JavaScript 引擎线程
  3. 定时触发器线程
  4. 事件触发线程
  5. 异步 http 请求线程

1.GUI 渲染线程

  • 主要负责页面的渲染,解析 HTML、CSS,构建 DOM 树,布局和绘制等。

  • 当界面需要重绘或者由于某种操作引发回流时,将执行该线程。
    该线程与 JS 引擎线程互斥,当执行 JS 引擎线程时,GUI 渲染会被挂起,当任务队列空闲时,JS 引擎才会去执行 GUI 渲染。

2. JS 引擎线程(执行栈)

  • 该线程当然是主要负责处理 JavaScript 脚本,执行代码。

  • 也是主要负责执行准备好待执行的事件,即定时器计数结束,或者异步请求成功并正确返回时,将依次进入任务队列,等待 JS 引擎线程的执行。

  • 当然,该线程与 GUI 渲染线程互斥,当 JS 引擎线程执行 JavaScript 脚本时间过长,将导致页面渲染的阻塞。

3. 定时器触发线程

  • 负责执行异步定时器一类的函数的线程,如: setTimeout,setInterval。

  • 主线程依次执行代码时,遇到定时器,会将定时器交给该线程处理,当计数完毕后,事件触发线程会将计数完毕后的事件加入到任务队列的尾部,等待 JS 引擎线程执行。

4. 事件触发线程

  • 主要负责将准备好的事件交给 JS 引擎线程执行。

  • 比如 setTimeout 定时器计数结束, ajax 等异步请求成功并触发回调函数,或者用户触发点击事件时,该线程会将整装待发的事件依次加入到任务队列的队尾,等待 JS 引擎线程的执行。

5. 异步 http 请求线程

  • 负责执行异步请求一类的函数的线程,如: Promise,axios,ajax 等。

  • 主线程依次执行代码时,遇到异步请求,会将函数交给该线程处理,当监听到状态码变更,如果有回调函数,事件触发线程会将回调函数加入到任务队列的尾部,等待 JS 引擎线程执行。

事件循环 Event Loop

事件循环中的任务队列有两种:

  • macro(宏任务)队列
  • micro(微任务)队列

macro(宏任务)

  常见的 macro-task 比如:setTimeout、setInterval、 setImmediate、script(整体代码)、 I/O 操作、UI 渲染等。

micro(微任务)

  常见的 micro-task 比如: process.nextTick、new Promise().then(回调)、MutationObserver(html5 新特性) 等

宏任务队列可以有多个,微任务队列只有一个

Event Loop 过程解析

在这里插入图片描述

1.一开始执行栈空,我们可以把执行栈认为是一个存储函数调用的栈结构,遵循先进后出的原则

  • 设:micro 队列空,macro 队列里有且只有一个 script 脚本(整体代码)。

2.全局上下文(script 标签)被推入执行栈,代码执行

  • 在执行的过程中,会判断是同步任务还是异步任务,通过对一些接口的调用,可以产生新的 macro-task 与 micro-task,它们会分别被推入各自的任务队列里。
  • 同步代码执行完了,script 脚本会被移出 macro 队列,这个过程本质上是队列的 macro-task 的执行和出队的过程。

3.当某个宏任务执行完后,会查看是否有微任务队列,如果有则优先处理 micro-task

  • 当 macro-task 出队时,任务是一个一个执行的;
  • 而 micro-task 出队时,任务是一队一队执行的;
  • 即我们处理 micro 队列这一步,会逐个执行队列中的任务并把它出队,直到队列被清空。

4.执行渲染操作,更新界面

5.检查是否存在 Web worker 任务,如果有,则对其进行处理

6.上述过程循环往复,直到两个队列都清空

总结

  1. 当某个宏任务执行完后,会查看是否有微任务队列
  2. 如果有,先执行微任务队列中的所有任务(一队一队执行),如果没有,会继续读取宏任务队列中排在最前的任务,执行宏任务的过程中,遇到微任务,依次加入微任务队列
  3. 栈空后,再次读取微任务队列里的任务,依次类推。

例子

在此观看更直观的例子演示
在这里插入图片描述
输出结果是 :Promise1,setTimeout1,Promise2,setTimeout2

一开始执行栈的同步任务(这属于script为宏任务)执行完毕,会去查看是否有微任务队列,上题中存在(有且只有一个),然后执行微任务队列中的所有任务输出 Promise1,同时会生成一个宏任务 setTimeout2

然后去查看宏任务队列,宏任务 setTimeout1 在 setTimeout2 之前,先执行宏任务 setTimeout1,输出 setTimeout1

在执行宏任务 setTimeout1 时会生成微任务 Promise2 ,放入微任务队列中,接着先去清空微任务队列中的所有任务,输出 Promise2

清空完微任务队列中的所有任务后,就又会去宏任务队列取一个,这回执行的是 setTimeout2

参考

https://zhuanlan.zhihu.com/p/54882306
https://jakearchibald.com/2015/tasks-microtasks-queues-and-schedules/

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

列队猫

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值