SEO教程

SEO教程

Products

当前位置:首页 > SEO教程 >

浏览器事件循环如何从输出题看Chrome源码?

96SEO 2026-07-24 13:36 9


前言

最近重新梳理浏览器事件循环。发现很多文章最终都会落到输出题:

宏任务、微任务、requestAnimationFrame、渲染,到底谁先?老实说,

输出题它适合检查基础概念。但如果只把事件循环背成“宏任务队列 + 微任务队列”,遇到 requestAnimationFrame 和渲染时机就很容易卡住。

浏览器事件循环如何从输出题看Chrome源码?

我的思路这方面,先看哪些输出是稳定的。再看 timerrAF 为什么不一定谁先,最终结合 Chromium 源码解释背后的原因。

这篇文章参考源码版本固定为:

Chromium: .commit: d096af1c9e98c45c3596e59620622b1a049bfecb

先看一道常见输出题

async function run { console.log;await null,console.log;}console.log;setTimeout => { console.log;},),Promise.resolve.n => { console.log;}),queueMicrotask => { console.log;}),requestAnimationFrame => { console.log;}),run;console.log,

稳定能确定的输出是:

script startasync startscript endpromisequeueMicrotaskasync end

那后面的 timerrAF 呢?

这就是这道题真正有意思的地方:timer`和``rAF`不一定谁先。

说白了完整输出可能是情况一:

script startasync startscript endpromisequeueMicrotaskasync endrAFtimer

也可能是情况二:

script startasync startscript endpromisequeueMicrotaskasync endtimerrAF

前六行是稳定的,因为它们只涉及当前 script task 和 microtask checkpoint。后两行不稳定,因为它们涉及 timer task 和下一次渲染机会。而浏览器不是每轮事件循环都会渲染。

先给什么时候情况一,什么时候情况二

先把结论放前面:

output situation | common trigger condition | how to understand it |

output situation

common trigger condition

how to understand it |

rAF -> timer

current script task and microtask cleared after browser enters next visual update step;page visible so rAF callback runs during rendering step while browser grabs this frame's rendering opportunity.

Timer -> rAF Current task ends before next frame or just missed a frame;scheduler picks already due timer task first. Page background or low refresh rate or throttled rAF more likely leads browser to execute available timer task first.

microtasks cleared n browser immediately enters rendering step → rAF executed first followed by timer tasks.

microtasks cleared n still no rendering opportunity yet but timer is ready → scheduler may run timer first.

Hence correct answer is not a single deterministic output but description: First six lines stable: Last two lines unstable: rAF / timer relative order depends on wher browser entered rendering opportunity right after microtasks were processed.

请改成简洁版本:

  • A microtasks finished → Browser goes into current render update → Executed `rAF` first n any pending timers afterward.
  • A microtasks finished → Timer is already ready before render time → Scheduler may run `timer` before `rAF`. 
  • The last two lines of this question are refore non-deterministic and cannot be memorized as a single fixed output.
  • But you must not memorize that “`requestAnimationFrame` always precedes `setTimeout`” – y are different kinds of events . 这也是事件循环容易被误解的地方:

    Why 前六行稳定?

    整体 script 本身就是一个 task。这个 task 里的同步代码会一直执行到结束,所以先输出:
    text
    script start async start script end
    中间遇到几个异步 API:
    * `setTimeout` 注册一个后续 timer task。* `Promise.n` 注册一个微任务。* `queueMicrotask` 注册一个微任务。* `await null` 后面的代码会作为后续微任务执行。* `requestAnimationFrame` 注册下一次渲染前回调。等当前 script task 结束,浏览器会执行微任务检查点,于是依次输出:
    text
    promise queueMicrotask async end
    注意这方面。`timer` 和 `rAF` 谁先,不应该用一句“rAF 一定在 timer 前”来记。`timer` 是后续 timer task,`rAF` 是下一次视觉更新前回调。它们的相对顺序和帧时机、页面状态、调度策略有关。

    直观模型

    我们先不要急着说“宏任务队列”和“微任务队列”,先看浏览器主线程上会发生什么:
    • 执行 JS。
    • 处理点击、输入、滚动等事件。
    • 解析 HTML,建立 DOM。老实说,
    • 计算样式和布局。
    • 执行 `requestAnimationFrame`。
    • 绘制页面。
    • 执行 `setTimeout`、网络回调等异步任务。
    • 这些工作大多都要占用主线程。主线程一次只能做一件事,所以浏览器需要一个调度机制:当前这段工作做完后接下来做什么?可以先记住这张简化图: mermaid flowchart TD A --> B B --> C C --> D D --> E{Are microtasks queued?} E -- No --> D E -- Yes --> F{Is it time for visual update?话说回来,} F -- No --> A F -- Yes --> G G --> H H --> A 这张图有三个关键词:
      • Task一段普通异步工作。比如整体脚本、定时器回调、事件回调或解析器的一次继续解析。

      • MicroTask当前 Task 完成后的收尾,比如 Promise reaction、queueMicroTask 或 await 的后续。

      • Rendering Opportunity一次浏览器有机会更新页面的时机,并非每个 Task 后都一定发生。

      有了这张图,就能理解为什么 rAFtimer 不应该放到同一个简单队列里比较。Timer 等待的是“下一个可运行 Task”,Raf 等待的是“下一次渲染步骤”。


      Task : 不可随便打断

      很多时候页面卡住不是因为浏览器“不想渲染”,而是主线程还没空出来。例如这方面,

      javascript box.textContent = 'loading...';while { // 长时间同步计算 }

      这里即使修改了 DOM。也不会立刻看到效果,因为当前 JS Task 没结束——浏览器没有机会进入微任务检查点,也没有机会进入渲染更新。

      在 Chromium Blink 的调度文档里也能看到这个实现思路。在 Blink 的 Scheduler 文档里可以看到:

      cpp // At moment Blink Scheduler treats tasks as an atomic unit...

      可以把它理解成 Task 一旦开始,调度器不会从中间把它切开;其实,只有等当前 Task 结束后才会选下一个可运行 Task

      平时写代码时要注意长时间运行的同步操作,例如:

      javascript button.addEventListener => { heavyWork;updateDOM,});按理说,

      如果 heavyWork 很久。 则使用者输入、动画和渲染都会被拖住。解决思路通常不是换成 Promise,而是把重活拆成多段或放到 Worker。


      MicroTask : 当前 Task 的收尾

      再看 MicroTask。例如的观点是,

      javascript console.log;setTimeout => { console.log;},),Promise.resolve.n => { console.log;}),queueMicrotask => { console.log;}),说起来,console.log;

      至于输出为,

      text startendpromisemicrotasktimer

      说到原因。

      1. start,end: 同步代码。

      2. Promise.n / queueMicroTask 入微任务队列。

      3. setTimeout 入后续 Timer Queue。

      4. 当前 Task 完毕后立即清空 MicroTasks。其实,

      5. 微任务清完后才轮到下个 Timer 或者 Rendering Opportunity。

      再看易错例子的观点是,

      javascript Promise.resolve.n => { console.log;queueMicrotask => { console.log;}),});Promise.resolve.n => { console.log;}),setTimeout => { console.log;},),怎么说呢,

      text promise1promise2microtask in promisetimer

      此处说明 promise 内追加 MicroTask 会排到当期 Queue 尾部。但仍在本轮 Checkpoint 内完成。不过,若不停追加 MicroTasks 如下面例子。就会阻塞 Rendering:

      javascript box.textContent = 'loading...';function loop { queueMicrotask;} loop,按理说,

      虽然不是同步死循环。但由于 MicroTask checkpoint 永远无法清空,所以主线程永远不能进入新的 Rendering 或其它 Task。


      Await 本质是排队时机

      下面简单例子展示 await 的排队顺序:

      javascript async function foo { console.log;await,console.log;按理说,} Promise.resolve.n => {console.log;}),foo;按理说,Promise.resolve.n=>{console.log;}),

      text foo start promise1 foo end promise2

      至于关键点,

      1. Promise1 入 MicroTask Queue;

      2. 调用 foo 输出 'foo start';

      3. 当走到 await 时将 'foo end' 入 MicroTask Queue;

      4. 再加入 Promise2;

      结果顺序为 promise1 → foo end → promise2


      DOM 修改并不立即绘制

      从举例来看,

      javascript box.textContent = 'loading...';box.style.width = '200px';

      这里同步修改了 DOM/CSSOM 状态,但并不代表着屏幕已画出。其实,当下 JS Task 未结束,没有进入 MicroTasks 清理。也未达到 Visual Update 时机之前,都无法触发 Render 步骤。下面代码不会在显示 loading 后再做计算:

      javascript jsx= box.textContent = 'loading...';let i =,while {}

      若想让页面及时刷新,可以将重活推迟至下一轮。如 setTimeout 或 requestAnimationFrame:

      javascript jsx= box.textContent = 'loading...';setTimeout=>{ heavyWork;},),requestAnimationFrame=>{ box.style.transform = 'translateX';}),其实,


      setTimeout 与 requestAnimationFrame 的区别

      setTimeout 是 Timer Task

      Blink 源码入口为 dom_timer.cc 简化流程如下:

      cpp

      int DOMTimer::setTimeout { auto* action = MakeGarbageCollected<ScheduledAction>;return MakeGarbageCollected<DOMTimer>(context,action。base::Milliseconds,true)->timeoutid;} ... if { // ... ... MoveToNewTaskRunner);... } DOMTimer::Fired executes action->Execute;setTimeout 并非马上执行,而投递了一个未来 Timer Task。

      requestAnimationFrame 是 渲染前回调

      入口在 document.cc 与 scriptedanimationcontroller.cc:

      Document::RequestAnimationFrame registers callback in ScriptedAnimationController;Schedule animation if needed. PageAnimator::ServiceScriptedAnimations runs resize/scroll/media query/raf callbacks. This explains *** sometimes raf precedes timers and vice versa.


      HTML Parsing 在事件循环中的角色

      HTML Parser 在 Blink 的 htmldocumentparser.cc 中工作;主要函数 PumpTokenizerIfPossible/PumpTokenizer 按预算解析 HTML。并按需 yield 给其他 high‑priority work,如脚本或样式加载;之后通过 SchedulePumpTokenizer 投递剩余解析至新的 Task。


      渲染更新到底做了什么

      Local Frame View UpdateAllLifecyclePhases 推进 DocumentLifecycle 到 PaintClean;其内部调用 RunStyleAndLayoutLifecyclePhases / RunCompositingInputsLifecyclePhase / RunPrePaintLifecyclePhase / RunPaintLifecyclePhase。在 PageAnimator::ServiceScriptedAnimations 调用中完成 Resize/Scroll/Media Query/raf callbacks,接下来进入样式/layout/pre‑paint/paint/compositing 阶段。


      从回到最开始来看,如何记忆事件循环?

      从规则如下来看,

    • The current Task’s synchronous code will run until completion.
    • The scheduler never splits a running Task mid‑execution for Rendering.
    • A completed Task triggers Microtasks checkpoint immediately afterward.
    • The Microtasks queue will be emptied completely—never infinite loops allowed.
    • The Rendering step does not occur on every event‑loop iteration—depends on page state &timing strategy.
    • requestAnimationFrame is a pre‑render callback—not a normal timed macro‑event like setTimeout.
    • Blink’s parser also participates in scheduling on main thread—long parsing can delay scripts &renders alike.
    • So from opening question’s viewpoint:

      The stable part of output is always:


标签: 深入浅出

SEO优化服务概述

作为专业的SEO优化服务提供商,我们致力于通过科学、系统的搜索引擎优化策略,帮助企业在百度、Google等搜索引擎中获得更高的排名和流量。我们的服务涵盖网站结构优化、内容优化、技术SEO和链接建设等多个维度。

百度官方合作伙伴 白帽SEO技术 数据驱动优化 效果长期稳定

SEO优化核心服务

网站技术SEO

  • 网站结构优化 - 提升网站爬虫可访问性
  • 页面速度优化 - 缩短加载时间,提高用户体验
  • 移动端适配 - 确保移动设备友好性
  • HTTPS安全协议 - 提升网站安全性与信任度
  • 结构化数据标记 - 增强搜索结果显示效果

内容优化服务

  • 关键词研究与布局 - 精准定位目标关键词
  • 高质量内容创作 - 原创、专业、有价值的内容
  • Meta标签优化 - 提升点击率和相关性
  • 内容更新策略 - 保持网站内容新鲜度
  • 多媒体内容优化 - 图片、视频SEO优化

外链建设策略

  • 高质量外链获取 - 权威网站链接建设
  • 品牌提及监控 - 追踪品牌在线曝光
  • 行业目录提交 - 提升网站基础权威
  • 社交媒体整合 - 增强内容传播力
  • 链接质量分析 - 避免低质量链接风险

SEO服务方案对比

服务项目 基础套餐 标准套餐 高级定制
关键词优化数量 10-20个核心词 30-50个核心词+长尾词 80-150个全方位覆盖
内容优化 基础页面优化 全站内容优化+每月5篇原创 个性化内容策略+每月15篇原创
技术SEO 基本技术检查 全面技术优化+移动适配 深度技术重构+性能优化
外链建设 每月5-10条 每月20-30条高质量外链 每月50+条多渠道外链
数据报告 月度基础报告 双周详细报告+分析 每周深度报告+策略调整
效果保障 3-6个月见效 2-4个月见效 1-3个月快速见效

SEO优化实施流程

我们的SEO优化服务遵循科学严谨的流程,确保每一步都基于数据分析和行业最佳实践:

1

网站诊断分析

全面检测网站技术问题、内容质量、竞争对手情况,制定个性化优化方案。

2

关键词策略制定

基于用户搜索意图和商业目标,制定全面的关键词矩阵和布局策略。

3

技术优化实施

解决网站技术问题,优化网站结构,提升页面速度和移动端体验。

4

内容优化建设

创作高质量原创内容,优化现有页面,建立内容更新机制。

5

外链建设推广

获取高质量外部链接,建立品牌在线影响力,提升网站权威度。

6

数据监控调整

持续监控排名、流量和转化数据,根据效果调整优化策略。

SEO优化常见问题

SEO优化一般需要多长时间才能看到效果?
SEO是一个渐进的过程,通常需要3-6个月才能看到明显效果。具体时间取决于网站现状、竞争程度和优化强度。我们的标准套餐一般在2-4个月内开始显现效果,高级定制方案可能在1-3个月内就能看到初步成果。
你们使用白帽SEO技术还是黑帽技术?
我们始终坚持使用白帽SEO技术,遵循搜索引擎的官方指南。我们的优化策略注重长期效果和可持续性,绝不使用任何可能导致网站被惩罚的违规手段。作为百度官方合作伙伴,我们承诺提供安全、合规的SEO服务。
SEO优化后效果能持续多久?
通过我们的白帽SEO策略获得的排名和流量具有长期稳定性。一旦网站达到理想排名,只需适当的维护和更新,效果可以持续数年。我们提供优化后维护服务,确保您的网站长期保持竞争优势。
你们提供SEO优化效果保障吗?
我们提供基于数据的SEO效果承诺。根据服务套餐不同,我们承诺在约定时间内将核心关键词优化到指定排名位置,或实现约定的自然流量增长目标。所有承诺都会在服务合同中明确约定,并提供详细的KPI衡量标准。

SEO优化效果数据

基于我们服务的客户数据统计,平均优化效果如下:

+85%
自然搜索流量提升
+120%
关键词排名数量
+60%
网站转化率提升
3-6月
平均见效周期

行业案例 - 制造业

  • 优化前:日均自然流量120,核心词无排名
  • 优化6个月后:日均自然流量950,15个核心词首页排名
  • 效果提升:流量增长692%,询盘量增加320%

行业案例 - 电商

  • 优化前:月均自然订单50单,转化率1.2%
  • 优化4个月后:月均自然订单210单,转化率2.8%
  • 效果提升:订单增长320%,转化率提升133%

行业案例 - 教育

  • 优化前:月均咨询量35个,主要依赖付费广告
  • 优化5个月后:月均咨询量180个,自然流量占比65%
  • 效果提升:咨询量增长414%,营销成本降低57%

为什么选择我们的SEO服务

专业团队

  • 10年以上SEO经验专家带队
  • 百度、Google认证工程师
  • 内容创作、技术开发、数据分析多领域团队
  • 持续培训保持技术领先

数据驱动

  • 自主研发SEO分析工具
  • 实时排名监控系统
  • 竞争对手深度分析
  • 效果可视化报告

透明合作

  • 清晰的服务内容和价格
  • 定期进展汇报和沟通
  • 效果数据实时可查
  • 灵活的合同条款

我们的SEO服务理念

我们坚信,真正的SEO优化不仅仅是追求排名,而是通过提供优质内容、优化用户体验、建立网站权威,最终实现可持续的业务增长。我们的目标是与客户建立长期合作关系,共同成长。

提交需求或反馈

Demand feedback