从输入 URL 到页面渲染:浏览器在每一步做了什么
打开一个页面只需要几百毫秒,但浏览器内部走完了一条很长的流水线。把这条线拆开,很多性能问题就有了定位的方向:到底是网络慢、解析阻塞,还是布局抖动。
一、导航与网络阶段
浏览器先查缓存:Service Worker、内存缓存、HTTP 磁盘缓存依次检查,都没有命中才发起请求。DNS 解析、TCP 握手、TLS 协商都在这一步完成,随后服务器返回 HTML 的首字节(TTFB)。
HTML 是流式解析的:浏览器拿到前几 KB 就开始构建 DOM,不必等整个文档下载完。这也解释了为什么把 CSS 放在 <head>、把脚本放在底部是有效建议——它们改变的是阻塞时机,而不是下载顺序。
二、构建 DOM 与 CSSOM
解析 HTML 生成 DOM 树,遇到 <style> 和内联样式则构建 CSSOM。两者是独立的结构,最后合并成渲染树。渲染树只包含可见节点,display:none 的元素不会进入,而 visibility:hidden 会。
关键点:CSS 会阻塞渲染,但不阻塞解析;同步脚本既阻塞解析也阻塞渲染,因为脚本可能修改 DOM 或读取样式。给脚本加 defer 可以延后到解析完成、按顺序执行;加 async 则下载完就执行,顺序不可控。
<script src="app.js" defer></script>
<link rel="preload" href="/fonts/main.woff2" as="font" crossorigin>
三、布局、绘制与合成
渲染树确定后进入布局(Layout/Reflow):计算每个盒子的位置和尺寸。接着是绘制(Paint),生成绘制指令;最后是合成(Composite),把图层交给 GPU 拼成最终画面。
现代浏览器把页面拆成多个图层,滚动和变换可以在合成线程完成,不触发主线程重排。这也是 transform 和 opacity 动画更流畅的原因:它们通常只影响合成,而 width、top、margin 会连锁触发布局。
| 属性 | 触发阶段 | 代价 |
|---|---|---|
| transform / opacity | 合成 | 低 |
| background-color | 绘制 | 中 |
| width / height / top | 布局 | 高 |
四、把知识用在排查上
遇到「页面卡顿」时,先在 DevTools 的 Performance 面板录制一段,看主线程的长任务落在哪一列:黄色是脚本、紫色是布局、绿色是绘制。再结合上面的阶段判断原因,比凭感觉改 CSS 有效得多。
一个常见的误区是只看资源体积。事实上,几百 KB 的 JS 解析执行可能比 2 MB 的图片更耗时——图片解码在合成阶段,而 JS 会占住主线程。