补救年轻时的坑:从 Event Loop 到 V8

42 阅读7分钟

补坑:系统化串联自己以前不懂的,或者流程不清晰得页面渲染资源(html,css,js)加载规则,以及js是解释型语言的一些了解


你别再坑我了.gif

一、前言:从一行 console.log 开始的思考

写代码久了,我们往往更关注「功能是否实现」,而忽略了一个更本质的问题:

浏览器到底是 如何执行我们的 JavaScript 的?

这篇文章,是我在实际项目与调试过程中的一些理解与思考。
从最基础的 Event Loop,到浏览器的 渲染机制,再到 性能优化与 V8 引擎的内部原理

老生常谈的问题了,之前其实也看过相关的内容,enent loop我自认为是之前理解的内容了,做一个个人的总结这样子。


二、事件循环(Event Loop):同步、微任务、宏任务的节奏

先看一段经典的示例👇

console.log("1. 同步任务开始");

setTimeout(() => {
  console.log("4. 来自 setTimeout(宏任务)");
}, 0);

setTimeout(() => {
  console.log("5. 来自 setTimeout(宏任务1)");
  Promise.resolve().then(() => {
    console.log("6. 来自 Promise.then(微任务)");
  });
}, 1);

Promise.resolve().then(() => {
  console.log("3. 来自 Promise.then(微任务)");
});

console.log("2. 同步任务结束");

输出结果依次是:

1. 同步任务开始
2. 同步任务结束
3. 来自 Promise.then(微任务)
4. 来自 setTimeout(宏任务)
5. 来自 setTimeout(宏任务1)
6. 来自 Promise.then(微任务)

这体现了浏览器事件循环的核心节奏:

同步任务 → 微任务 → 宏任务 → 再微任务 → 再宏任务 … 循环往复。

阶段类型示例
同步任务主线程立即执行console.log()、函数调用
微任务(microtask)在本轮事件循环尾执行Promise.thenMutationObserver
宏任务(macrotask)下一轮事件循环执行setTimeoutsetInterval、I/O

理解意义:
这不仅决定了你的异步逻辑执行顺序,也解释了为什么有时 UI 会“卡顿”或“未及时更新”——因为主线程在执行微任务时,渲染会被延迟。

然后其实我之前好像看过一些文章哈,我自己目前是这么理解的,js代码是从上往下执行的,(需要关注一下变量提升函数提升,但是现在我相信大家的风格都是先定义再使用这样子符合我们从先到后,从有再到使用的逻辑),然后每一个函数(js代码)依次执行,执行过程中,分为同步任务异步任务同步任务理解执行,异步任务会进行挂起,异步任务分为宏任务微任务,遇到这两个会进行挂起,放到对应的宏任务序列和微任务序列中,同步任务执行完成之后,优先执行微任务序列的微任务列表(常见promise.then()),所有的同层微任务执行完之后然后执行宏任务,一个循环的过程,我理解为类似套娃这样子,比如宏任务序列和微任务代码中,又是一个新的js执行栈,先同步,再异步这样子,希望没有说的很绕。


三、浏览器渲染机制:从 DOM 到屏幕的旅程

浏览器渲染的完整流程如下👇

HTML → 解析成 DOM
CSS  → 解析成 CSSOM
DOM + CSSOM → Render Tree(渲染树)
Layout(布局) → Paint(绘制) → Composite(合成)

当浏览器在解析 HTML 的过程中遇到 <script> 标签时:

  1. 解析会暂停
  2. 浏览器必须下载并执行 JS
  3. JS 可能会修改 DOM/CSSOM
  4. 执行完毕后,解析才会继续

这就是著名的 **渲染阻塞(Render-blocking)**🫥 。

解决方案:

<!-- 下载与解析并行,执行在 DOM 构建后 -->
<script src="main.js" defer></script>

<!-- 第三方脚本独立执行,不保证顺序 -->
<script src="analytics.js" async></script>
属性下载时机执行时机顺序保证
默认阻塞 HTML 解析立即执行
defer异步下载DOM 解析完成后执行
async异步下载下载完成立即执行

重排重绘问题也很经典,这里就不赘述了,减少重排,多使用transformopacity🤣

transform(旋转平移) 只会触发 合成层(composite) 的变化,不会引发 重排(reflow)重绘(repaint)

opacity透明度 也是合成层变化

tips:😁合成层为什么快,因为不需要计算任何东西,不涉及重绘重排,资源加载,直接拼图展示,消耗低。


四、模拟动画掉帧:当你的 JS 拖慢了世界

<div id="box"></div>
<script>
  const box = document.getElementById("box");
  let pos = 0;

  function move() {
    pos += 2;
    box.style.left = pos + "px";

    // 模拟耗时计算
    for (let i = 0; i < 10000000; i++) Math.sqrt(i);

    if (pos < 600) requestAnimationFrame(move);
  }

  move();
</script>

打开 Chrome DevTools → Performance 面板 → Record
你会看到:

  • FPS(帧率)低于 60fps
  • Main Thread 出现长条橙色块
  • 展开后发现 Math.sqrt() 占用了大量 CPU

浏览器常见目标是每秒 60 帧(60fps),单帧预算约 16.6ms(1000ms / 60)。如果主线程某一帧耗时超过 16ms,就会掉帧,用户会感觉卡顿。这个问题之前被面试官问到过,浏览器性能会有瓶颈,怎么去优化动画,让用户觉得流畅不卡顿。

💡 结论:
浏览器主线程负责 JS、布局、绘制——如果 JS 执行太久,UI 就无法更新。

img_v3_02ru_37b2295f-9830-4ea6-8673-064bc8063fhu.jpg

img_v3_02ru_2443c187-3688-48d7-a690-e49bf25fbehu.jpg


五、Web Worker:把耗时任务移出主线程

以图片灰度化为例👇

main.js

const worker = new Worker("gray-worker.js");
worker.postMessage(imageData);
worker.onmessage = (e) => {
  ctx.putImageData(e.data, 0, 0);
};

gray-worker.js

onmessage = (e) => {
  const data = e.data;
  for (let i = 0; i < data.length; i += 4) {
    const avg = (data[i] + data[i + 1] + data[i + 2]) / 3;
    data[i] = data[i + 1] = data[i + 2] = avg;
  }
  postMessage(data);
};

Worker 可以让主线程保持流畅,避免 UI 卡顿。复杂js计算内可以移到Worker 进行优化。


六、V8 引擎:从源码到机器码的修炼之路

V8 是 Chrome 与 Node.js 使用的 JavaScript 引擎。
执行流程如下👇

源代码
 ↓
Parser(解析器)→ AST
 ↓
Ignition(解释器)→ 字节码
 ↓
TurboFan(优化编译器)→ 机器码
 ↓
执行与垃圾回收(GC)
阶段处理方式含义
解析解释把源码转成 AST
初次执行解释执行Ignition 生成字节码
热代码优化编译TurboFan 编译为机器码
类型变化去优化回退解释执行
内存管理GC 回收新生代、老生代、并发回收

优化建议:

  • 保持对象属性顺序一致,避免隐藏类退化
  • 减少临时对象创建
  • 拆分长任务、利用 Worker
  • 模块懒加载,使用 defer/async

感觉我们开发者还是有点必要了解一下现在这些知识,const handleClick = useCallback(() => doSomething(id), [id]); 一些hooks的函数也是基于引擎而做的一些优化处理了,我们的框架代码规范里面,或多或少都与优化相关。


七、性能优化实战清单

问题原因解决方案
脚本阻塞渲染<script> 默认阻塞使用 deferasync
长任务阻塞主线程JS 执行 > 50ms拆分任务 / 使用 Web Worker
布局颠簸频繁读写 layout 属性读写分离、批量更新
重绘重排过多修改影响 layout 属性使用 transform / opacity
DOM 元素过多渲染节点太多虚拟列表滚动/无限加载 / 分页加载
图片资源未优化文件大 / 无懒加载使用 WebP / 懒加载/cdn加速

📈 推荐工具:

  • Lighthouse:检测长任务与 CLS
  • Performance 面板:分析掉帧
  • Coverage 面板:找出未使用代码

image.png

性能得分跟后端接口快慢也有关系哈,就是我看到一些公司(大公司)的接口真的超级快,而且安全性高,我第一家,我还是一个青涩实习生的公司的网址,www.yupao.com/, 之前我记得不让检查来着,现在可以检查,但是我看接口,不怎么看得到有用信息,真的牛比,然后就是http1,http2接口协议的规范,我不是后端,不知道怎么处理的,我初略理解就是配置类的,不变的恒定的东西使用http2,http2真的好快,现在不是有http3吗,不知道是什么样子的


八、结语

同步与异步、渲染与阻塞、执行与优化——
它们看似复杂,其实:

我们做优化的底层其实都跟浏览器渲染,以及js底层执行有关系,有不对的,或者更好的理解,欢迎大伙指正以及指导😘😘😘