前端一面:从输入 URL 到页面渲染,面试官到底想听啥

“从输入 URL 到页面渲染完毕,中间发生了什么?” 是国内前端一面几乎必考的大题之一。这一篇重点不是穷举所有细节,而是帮你整理一套结构化、可控的回答思路,再配几道典型追问和参考答案。

大多数一面面试官期望听到的,不是浏览器源码级别的流程,而是大致这样几层:

  1. 网络阶段:DNS 解析、TCP/HTTPS 连接、发送 HTTP 请求、服务端返回响应;
  2. 资源加载阶段:主文档下载,解析 HTML 时发现 CSS/JS/图片等子资源,发起并行请求;
  3. 渲染阶段:解析 HTML → 构建 DOM,解析 CSS → 构建 CSSOM,合成 Render Tree,布局(Layout)、绘制(Paint)、合成层(Compositing);
  4. JS 执行与阻塞:解释执行脚本,可能修改 DOM/CSSOM,从而触发布局/重绘;
  5. 后续优化点:缓存、预加载、懒加载等。

一个比较稳妥的答题方式,是按这几个阶段自上而下讲,遇到追问再往其中某一层深入。

用一到两分钟描述网络部分,通常足够:

  • 浏览器检查缓存(强缓存/协商缓存),如果命中,可能直接用本地资源;
  • 没有命中缓存时:
    • 通过 DNS 把域名解析成 IP 地址;
    • 建立 TCP 连接(如果是 HTTPS,还要完成 TLS 握手);
    • 发送 HTTP 请求(方法、路径、头部、Cookie 等);
    • 服务端处理请求,返回 HTTP 响应(状态码、头部、HTML 文本等)。

常见追问会在这里切到:

  • 缓存策略(强缓存 vs 协商缓存);
  • HTTP 与 HTTPS 的差别;
  • TCP 三次握手/四次挥手(如果面试官更偏网络)。

拿到 HTML 响应后,浏览器开始:

  • 自上而下解析 HTML,构建 DOM 树;
  • 遇到 <link rel="stylesheet"> 标签时,发起 CSS 请求;
  • 遇到 <script> 标签时,根据属性(如 defer/async)决定是否阻塞解析;
  • 遇到图片/字体/其他资源标签时,发起相应的网络请求。

这里可以顺带提一下:

  • 为什么建议把关键 CSS 放在 <head>,而 JS 尽量放在底部或使用 defer
  • 外链 CSS 资源会阻塞首次渲染,外链 JS(非 defer/async)会阻塞 HTML 解析。

这部分如果讲得清楚,面试官通常会顺势问“你会如何优化首屏加载?”。

当 HTML/CSS 被解析后,浏览器大致会做这些事:

  • DOM 树:根据 HTML 结构构建节点树;
  • CSSOM 树:解析所有 CSS,计算出每个选择器对应的样式规则;
  • Render Tree:把 DOM 和 CSSOM 合并,得到真正要绘制的“渲染树”(不包含 display:none 等不可见元素);
  • Layout(重排):为每个渲染对象计算几何信息(位置、尺寸);
  • Paint(重绘):根据样式和布局结果绘制像素;
  • Compositing:将不同的图层合成为最终屏幕上的图像。

一面里不需要死记所有内部细节,但可以强调两个高频概念:

  • 重排(Reflow):布局发生变化时,需要重新计算位置和尺寸;
  • 重绘(Repaint):样式(如颜色)改变但布局不变时,重新绘制像素。

这样为后续“如何减少重排/重绘”埋个伏笔。

JS 引擎会在解析到 <script> 标签时执行脚本,这会对渲染流程产生影响:

  • 没有 defer/async 的脚本会阻塞 HTML 解析,直到脚本下载并执行完成;
  • 脚本中如果直接操作 DOM/CSS,会引发布局/重绘;
  • 事件监听、定时器等会在后续事件循环中触发,继续修改页面。

你可以简要描述:

  • 为什么不推荐在关键路径上大量使用同步 JS 操作 DOM;
  • 如何使用 requestAnimationFrame 优化动画和频繁更新;
  • 如何用防抖/节流减少高频事件(scroll/input)对渲染的影响。

参考回答框架(口述版):

我一般会按四个阶段来讲:
1)网络阶段:浏览器先检查缓存,如果没有命中,会走 DNS 解析、TCP/TLS 握手,发起 HTTP 请求,服务器返回 HTML 响应;
2)解析阶段:浏览器自上而下解析 HTML,构建 DOM 树,同时遇到 <link> 会去加载 CSS,遇到 <script> 会按属性决定是否阻塞解析;
3)渲染阶段:CSS 解析成 CSSOM,和 DOM 合成 Render Tree,然后做 Layout 和 Paint,把页面绘制出来;
4)执行脚本和后续交互:脚本可以继续修改 DOM/CSS,触发布局和重绘,用户交互通过事件循环不断更新界面。

如果有时间,可以补一句:

这里面有很多可以优化的点,比如缓存策略、压缩/分割资源、把关键 CSS 放前面、脚本用 defer/async 等。

参考答案思路:

  • CSS 会阻塞渲染,是因为浏览器需要知道元素的最终样式才能正确构建 Render Tree 和进行 Layout,如果先渲染再应用样式,页面会频繁闪烁;
  • JS 会阻塞解析,是因为脚本可能会修改 DOM 结构甚至插入新的 <script>/<link>,浏览器必须等脚本执行完才能继续安全地解析后续 HTML。

可以顺带提一下:

  • defer 脚本会在 HTML 解析完成后、DOMContentLoaded 前执行;
  • async 脚本下载完成后立即执行,可能打断解析顺序,更适合不依赖 DOM 和其它脚本的独立逻辑。

参考答案要点:

  • 重排(Reflow):布局信息发生变化,需要重新计算元素的几何属性(宽高、位置),开销较大;
  • 重绘(Repaint):布局不变,只是一些视觉属性(颜色、背景等)变化,开销相对较小;
  • 如何减少:
    • 合并多次 DOM 操作,例如使用文档片段或一次性修改 class;
    • 避免在循环中频繁访问触发重排的属性(如 offsetTop / clientWidth),必要时先读再写;
    • 对需要频繁动画的元素使用 transform/opacity 等更容易被优化到合成层的属性,减少布局参与元素。

这些问题往往是在“从 URL 到渲染”大题之后的自然追问,提前准备好一套结构化的答案,会让整个回答看起来完整很多。