手撕React18源码系列 - Event-Loop模型

前言

本系列从头手把手带你学习react源码。但是在深入React架构前,你必须先了解一个关键概念,Event Loop(事件循环)模型

为什么要关心 Event Loop?因为 React 的 Fiber 架构正是基于它的"分片执行"能力构建的。

  • 把庞大的更新任务拆成小单元(Fiber 节点)。
  • 在每个宏任务间隙(比如一次渲染后),通过 requestIdleCallback 或 scheduler 插入高优先级微任务或宏任务。
  • 如果用户有交互(比如点击),浏览器会优先处理 UI 宏任务,React 主动"让出主线程",实现可中断、可恢复、高响应的并发更新。

换句话说:没有 Event Loop,就没有 Concurrent Mode(并发模式) 。这里说的并发其实是 "可中断的渲染(Interruptible Rendering) " + "时间切片(Time Slicing)" 。它是一种 逻辑上的并发,之前有个面试官和我犟过这个事情,所以特地的说明一下。

如果你已经非常熟悉事件循环机制,可以直接跳过本章------因为这里不涉及任何 React 源码。 但如果你只是听说过这个概念,或者脑子里还是一堆碎片化的理解,那这篇文章就是为你准备的。 不管是为了面试,还是为了真正搞懂 React 的底层原理,学完这篇,你都能把这些碎片拼成一张完整的拼图。对了,你还需要了解一个前置概念:Promise。 别担心,只要你用 fetch 或 axios 调用过接口,就一定接触过 Promise。 我们只需要知道它的基本机制就够了。本文主要用它来演示微任务队列的行为,因为没有比 Promise 更直观的微任务示例了。

开始

一开始我听到这个东西的时候,我马上百度了一下啥是事件循环

以上就是我百度到的结果,"麻了",一句人话都没有!一气之下,我直接关掉了浏览器。 但我冷静了一会儿,认真地思考了一下:不对!不能关浏览器啊。关了它,我还怎么学事件循环? 说到这里,你可能会问:事件循环和浏览器到底有什么关系?它不就是一个抽象的概念吗? 其实不然,事件循环并不是抽象的,而是一个实实在在由浏览器提供的机制。

众所周知,Javascript代码通常 是执行在浏览器端的(我说的是通常),我们只讨论浏览器的执行环境。言归正传,你需要在脑海里有3个概念,主线程宏任务队列(Macro Task Queue)微任务队列(Microtask Queue)

主线程

比方:登机口的检票员,就是拿着那个扫码枪扫码的那个人,只有一个检票员的情况

主线程,就是浏览器里唯一执行 JavaScript 代码的线程。JavaScript 的执行是自上而下的。当遇到异步操作时,引擎不会阻塞等待,而是将对应的回调函数放入宏任务队列或微任务队列中,继续执行后续的同步代码。等到当前主线程的任务执行完毕,就会在事件循环里面取出对应的任务,然后在主线程中继续执行。

宏队列(Macro Task Queue)

比方:经济舱检票口的队列

主线程用来存放异步回调函数第一个待办清单,主线程干完手头的事后,就从这个队列拿最前面的一个任务出来执行,执行完再拿下一个,一次只拿一个,按顺序来。是的,就这么简单。

常见的宏任务:setTimeoutsetIntervalsetImmediate(Node)、I/O、UI 渲染等

我看到很多文章中武断地认为:"现在没有宏队列这个概念了。" 在我看来,这是前端圈的一个"误解"。他们看到最新的 WHATWG HTML 标准 中没有 "macro task" 这一术语,就以为这个概念被废弃了。 但事实是:规范中从来就没有正式定义过 "macro task"。"宏任务" 是社区为了与"微任务"(microtask)对比而约定俗成的说法。 只不过,现代规范对异步任务的分类和调度机制有了更精细的描述。 在广义的 JavaScript 异步模型中,"宏任务"这一说法依然广泛使用; 而在 HTML 标准中,浏览器通过多个特定类型的任务队列(如延时队列、用户交互队列等)来管理这些任务,从而更精细地控制优先级,保障用户交互的流畅性。

微队列(Microtask Queue)

比方:头等舱检票口的队列,坐过飞机的都知道,只要头等舱队伍有一个人在排队,那么检票员就会把当前经济舱正在检的票检完。然后立刻来头等舱队伍检票,然后直到把这个队伍清空,才会回到经济舱队列。

主线程用来存放异步回调函数第二个待办清单 ,只不过,这个队列是个VIP队列,有些特权。当主线执行完宏队列里面当前的宏任务的时候,就程立刻、马上、一口气把微队列里所有的任务全执行完毕。说白了就是能插队。

常见的微任务:Promise.then/catch/finallyqueueMicrotaskMutationObserver(浏览器)、process.nextTick(Node,优先级更高)

开始执行JS脚本

这个概念我看了很多的文章,没有一个能解释的通俗易懂,各种术语,概念。在我看来,我们写的JS代码,比如:

js 复制代码
console.log("hello word")
console.log("hello word1")
console.log("hello word2")

这个代码就是第一个脚本代码,就是我们实实在在写在JS文件里面的代码。开程序开始执行的时候,就把这3行代码,放在了宏队列中,然后这三行代码就是第一个宏任务,所以总结一下三点!

  • 程序启动 = 执行第一个宏任务
  • 微任务只能是"执行过程中产生的副产品"
  • 所以,永远不可能一上来就跑微任务

可视化这两个队列

通过代码的方式直观的感受 -> MicroTask | MacroTask

1.执行脚本中只写了宏任务注册

js 复制代码
console.log(主线开始)

// 宏任务 1 (积压在后面)
setTimeout(() => console.log('宏1执行'), 0);

// 宏任务 2 (积压在后面)
setTimeout(() => console('宏2执行'), 0);

console.log(主线结束)

2.执行脚本中只写了微任务注册

此时只有微队列有任务Micro=[微1,微2]

js 复制代码
console.log(主线开始)

// 微任务 1 (积压在后面)
Promise.resolve().then(() => {console.log('微1执行')})

// 微任务 2 (积压在后面)
Promise.resolve().then(() => {console.log('微2执行')})

console.log(主线结束)

3.执行脚本中写了宏任务注册同时写写了微任务注册

此时两个队列现在都有了任务Micro=[微1] / Macro=[宏1]

js 复制代码
console.log(主线开始)
// 宏任务 1
setTimeout(() => console.log('宏1'), 0);
// 微任务 1
Promise.resolve().then(() => {console.log('微1执行')})

console.log(主线结束)

4.执行脚本中写了宏任务但其中包含了微任务

此时只有一个宏队列有了任务Macro=[宏1]

js 复制代码
console.log(主线开始)
// 宏任务 1
setTimeout(() => {
    // 微任务 1
    Promise.resolve().then(() => console.log('微1执行'))
}, 0);

console.log(主线结束)

5.执行脚本中写了微任务但其中包含了宏任务

此时只有一个微队列有了任务Micro=[微1]

js 复制代码
console.log(主线开始)
// 宏任务 1
const A = new Promise(r => {
    setTimeout(() => {
        console.log('宏任务(2s)'); 
        r('success1')
    }, 2000)
})

console.log(主线结束)

总结

到目前为止,我们已经把浏览器中的 事件循环(Event Loop)模型 拆解成了三个核心角色:

  • 主线程:唯一的 JavaScript 执行通道,像登机口那个孤独但高效的检票员;
  • 宏任务队列(Macro Task Queue):普通乘客排队区,任务按顺序一个个执行;
  • 微任务队列(Microtask Queue):头等舱 VIP 通道,一旦有任务,主线程必须立刻清空整个队列才能干别的。

现在,当你再看到满屏的 setTimeoutPromise.then,是不是脑子里自动浮现出两个队伍在登机口排队的画面了?

相关推荐
空镜3 小时前
通用组件使用文档
前端·javascript
前端小张同学3 小时前
餐饮小程序需要你们
java·前端·后端
码农胖大海3 小时前
微前端架构(一):基础入门
前端
同聘云4 小时前
阿里云国际站服务器gpu服务器与cpu服务器的区别,gpu服务器如何使用?
服务器·前端·阿里云·云计算
lionliu05194 小时前
执行上下文 (Execution Context)
开发语言·前端·javascript
几何心凉4 小时前
openFuyao多样化算力使能
前端
方安乐4 小时前
vue3 el-select懒加载以及自定义指令
javascript·vue.js·elementui
文心快码BaiduComate4 小时前
给 AI 装上“员工手册”:如何用Rules 给文心快码 (Comate) 赋能提效?
前端·程序员·前端框架