浏览器渲染过程:高级前端面试题

1. 浏览器从 HTML 到最终显示,完整过程是什么?

核心回答

浏览器先解析 HTML 和 CSS,生成 DOM 和 CSSOM,再计算布局、分层、生成绘制指令,经过分块和光栅化,最后把各个图层合成到屏幕上。

展开

可以把现代 Chromium 的渲染过程理解成:

text 复制代码
HTML
 ↓
解析
 ↓
DOM Tree

CSS
 ↓
解析
 ↓
CSSOM
      │
      └──────┐
             ↓
        样式计算 / 布局
             ↓
          布局树
             ↓
           分层
             ↓
           绘制
             ↓
           分块
             ↓
          光栅化
             ↓
           合成
             ↓
          显示

注意,这不是简单的"一个步骤执行完才执行下一个步骤"。

HTML 解析、CSS 加载/解析、资源加载、JavaScript 执行之间存在并行和相互阻塞关系。

真正面试高级的地方,是继续回答:

什么时候能继续?什么会阻塞?什么变化会重新走哪一步?


2. HTML 解析的时候,浏览器到底做了什么?

核心回答

HTML Parser 会把 HTML 字节流逐步解析成 DOM,而不是等整个 HTML 下载完才一次性生成 DOM。

例如:

html 复制代码
<html>
  <body>
    <div>
      <span>Hello</span>
    </div>
  </body>
</html>

大致是:

text 复制代码
HTML 字节流
   ↓
词法分析
   ↓
Token
   ↓
DOM Node
   ↓
DOM Tree

所以浏览器可以边下载、边解析、边构建 DOM。


3. HTML 解析和 CSS 解析是串行还是并行?

核心回答

不是简单的串行,它们可以并行推进;HTML 解析发现 CSS 后可以发起 CSS 请求,同时继续解析 HTML。

例如:

html 复制代码
<head>
  <link rel="stylesheet" href="style.css">
</head>

<body>
  <div>Hello</div>
</body>

可以理解成:

text 复制代码
                HTML Parser
                    │
          ┌─────────┴─────────┐
          ↓                   ↓
     构建 DOM             发现 CSS
                              ↓
                         请求 CSS
                              ↓
                         CSS Parser
                              ↓
                            CSSOM

所以不要说:

HTML 解析完以后才开始 CSS 解析。

这不准确。


4. CSS 会不会阻塞 HTML 解析?

核心回答

普通外部 CSS 一般不会直接让 HTML Parser 停下来等 CSS 下载完,但 CSS 会影响后续的样式计算和渲染,而且还可能间接影响脚本执行。

所以一定要区分:

text 复制代码
CSS 阻塞 HTML 解析?
        ↓
通常不是直接阻塞

CSS 阻塞渲染?
        ↓
关键 CSS 会影响首次渲染

CSS 影响 JS 执行?
        ↓
某些情况下会

这也是面试里很容易被追问的地方。


5. 为什么 CSS 会影响首次渲染?

核心回答

因为浏览器必须知道元素最终是什么样子,才能正确计算布局和绘制,所以关键 CSS 没准备好时,通常不能完成正确的首次渲染。

例如:

css 复制代码
.box {
  display: none;
}

如果浏览器完全不管 CSS,直接把:

html 复制代码
<div class="box">Hello</div>

画出来,之后 CSS 到了再隐藏,就会产生错误的中间画面。

所以:

text 复制代码
DOM
 │
 │
CSSOM
 │
 └──────┐
        ↓
     样式计算
        ↓
      布局树
        ↓
       绘制

6. HTML 遇到 <script> 会发生什么?

核心回答

普通同步 script 会暂停 HTML 解析,浏览器先下载并执行脚本,执行结束后 HTML Parser 才继续。

例如:

html 复制代码
<div>1</div>

<script src="app.js"></script>

<div>2</div>

大致:

text 复制代码
解析 <div>1</div>
       ↓
发现 script
       ↓
暂停 HTML Parser
       ↓
下载 JS
       ↓
执行 JS
       ↓
继续 HTML Parser
       ↓
解析 <div>2</div>

这也是为什么大量同步脚本放在 HTML 前面可能拖慢页面构建。


7. defer 和 async 到底解决什么问题?

核心回答

defer 是"异步下载、HTML 解析完再按顺序执行";async 是"异步下载、谁先下载完谁先执行"。

普通 script

text 复制代码
HTML解析
 ↓
发现JS
 ↓
暂停解析
 ↓
下载 + 执行JS
 ↓
继续解析

defer

text 复制代码
HTML解析 ─────────────────→ 完成
       │
       └── JS异步下载
                              ↓
                         HTML解析完成
                              ↓
                           执行JS

多个 defer:

text 复制代码
a.js → b.js → c.js

保持文档顺序。

async

text 复制代码
HTML解析 ─────────────────→
       │
       └── JS异步下载
             ↓
          下载完成
             ↓
          立即执行

所以多个 async:

text 复制代码
a.js ──┐
b.js ──┼── 谁先下载完谁先执行
c.js ──┘

8. DOM、CSSOM 之后为什么不是直接"Render Tree"?

核心回答

现代浏览器更值得关注的是样式计算和布局树,而不是把 Render Tree 当成一个独立的核心阶段死记。

可以理解成:

text 复制代码
DOM
 +
CSSOM
 ↓
样式计算
 ↓
布局树

布局树描述的是:

最终参与布局的内容,以及它们的几何信息。

例如:

css 复制代码
display: none;

对应的节点不会正常参与布局。所以布局树和 DOM Tree 并不是一模一样。


9. 什么是布局?

核心回答

布局就是确定每个元素最终放在哪里、尺寸是多少,以及它和其他元素之间的几何关系。

例如:

html 复制代码
<div class="box"></div>
css 复制代码
.box {
  width: 200px;
  height: 100px;
  margin-left: 50px;
}

布局阶段需要确定类似:

text 复制代码
x = 50
y = ...
width = 200
height = 100

所以:

text 复制代码
样式
 ↓
布局
 ↓
确定几何信息

10. 什么是分层?为什么需要分层?

核心回答

分层就是把页面中的部分内容放到独立的图层里,后续这些内容可以独立参与绘制和合成。

可以简单理解:

text 复制代码
页面
 ├── Layer A
 ├── Layer B
 └── Layer C

例如某些具有:

css 复制代码
transform
opacity

或者其他特定条件的元素,可能被提升到独立合成层。这样做的价值是:

text 复制代码
普通情况:

修改元素
 ↓
重新布局/绘制
 ↓
重新处理

独立图层:

修改 transform
 ↓
直接更新图层属性
 ↓
合成

11. 什么是 Paint?

核心回答

Paint 不是直接把像素画到屏幕上,而是生成"怎么画"的绘制指令。

例如:

text 复制代码
Paint
 ↓
画背景
画边框
画文字
画阴影
画图片
...
 ↓
绘制指令

这是一个非常容易被忽略的细节。所以不要简单说:

Paint = 把东西画到屏幕。

更准确的是:

Paint 生成绘制指令,后续再由光栅化把这些指令转换成像素。


12. 什么是分块?

核心回答

分块就是把需要光栅化的内容拆成一个个 tile,方便浏览器按需调度和处理。

可以理解成:

text 复制代码
一个很大的页面

┌────┬────┬────┐
│tile│tile│tile│
├────┼────┼────┤
│tile│tile│tile│
├────┼────┼────┤
│tile│tile│tile│
└────┴────┴────┘

为什么要这么做?因为页面可能非常大。没必要每次都把整个页面一次性光栅化。

特别是滚动页面:

text 复制代码
当前可视区域
      ↓
优先处理附近需要显示的 tiles

13. 什么是光栅化?

核心回答

光栅化就是把 Paint 阶段产生的绘制指令真正转换成像素数据。

例如:

text 复制代码
Paint
 ↓
"这里画一个红色矩形"
"这里画一段文字"
"这里画一张图片"
 ↓
Raster
 ↓
像素

所以可以把两者区别记成:

text 复制代码
Paint
= 告诉浏览器"怎么画"

Raster
= 真正把"怎么画"变成像素

14. 什么是 Composite?

核心回答

Composite 就是把已经准备好的不同图层/图块按照位置、层级、透明度、transform 等信息合成最终画面。

例如:

text 复制代码
Layer A
   ↓
Layer B
   ↓
Layer C
   ↓
Composite
   ↓
最终画面

如果一个元素已经有独立图层:

css 复制代码
.box {
  transform: translateX(100px);
}

那么动画过程中可能主要改变的是:

text 复制代码
图层位置
   ↓
Composite

而不需要每一帧重新:

text 复制代码
Layout
 ↓
Paint
 ↓
Raster

这就是合成动画通常性能比较好的原因。


15. 修改 width 会发生什么?

核心回答

修改 width 会影响元素几何信息,所以通常需要重新布局,并可能进一步触发绘制和光栅化,最后重新合成。

典型路径:

text 复制代码
修改 width
   ↓
Layout
   ↓
Paint
   ↓
Raster
   ↓
Composite

注意:

这是典型路径,不是绝对规则。

实际浏览器会根据具体元素、渲染状态、优化策略决定哪些阶段需要重新执行。


16. 修改 color 呢?

核心回答

修改 color 通常不需要重新布局,但会影响绘制,所以通常需要重新 Paint,后续相关内容还需要重新光栅化并合成。

大致:

text 复制代码
color
 ↓
Paint
 ↓
Raster
 ↓
Composite

17. 为什么 transform 通常性能更好?

核心回答

transform 只改变合成层的变换矩阵,不触发重排和重绘,直接交给 GPU 处理。

例如:

css 复制代码
.box {
  transform: translateX(100px);
}

可能变成:

text 复制代码
原来的页面
 ↓
Layout
 ↓
Paint
 ↓
Raster
 ↓
独立图层
 ↓
transform变化
 ↓
Composite

动画过程中主要处理:

text 复制代码
Composite

而不是反复:

text 复制代码
Layout → Paint → Raster

18. transform 是不是一定不走主线程?

核心回答

不是。不能把 transform 简单理解成"完全不走主线程",准确说法是:满足条件时,它可以让动画主要在合成阶段处理。

transform 和 opacity 在满足合成条件时,可以避免每帧重新执行 Layout 和 Paint,从而降低主线程压力。


19. 一帧之内修改 10 次样式,会 Layout 10 次吗?

核心回答

不一定。浏览器通常会把连续的样式修改合并,在合适的渲染时机统一处理。

例如:

js 复制代码
box.style.width = '100px';
box.style.width = '200px';
box.style.width = '300px';

通常没必要:

text 复制代码
100px → Layout
200px → Layout
300px → Layout

而可能最终只处理:

text 复制代码
100px
 ↓
200px
 ↓
300px
 ↓
最终300px
 ↓
Layout

所以:

不要把每次 DOM/CSS 修改都等同于一次 Layout。


20. 什么情况下会强制浏览器立即 Layout?

核心回答

改完样式马上读布局值,浏览器为了给出最新结果,会先强制更新布局。

例如:

js 复制代码
box.style.width = '500px';

console.log(box.offsetWidth);

因为:

js 复制代码
offsetWidth

需要最新布局结果。浏览器可能:

text 复制代码
修改样式
 ↓
布局失效
 ↓
读取 offsetWidth
 ↓
强制 Layout
 ↓
返回结果

这就是性能问题的来源之一。


21. 什么是 Layout Thrashing?

核心回答

就是不断交替"修改布局"和"读取布局",导致浏览器反复被迫同步 Layout。

例如:

js 复制代码
for (const item of items) {
  item.style.width = '100px';

  console.log(item.offsetWidth);
}

可能形成:

text 复制代码
Write
 ↓
Read
 ↓
Layout

Write
 ↓
Read
 ↓
Layout

Write
 ↓
Read
 ↓
Layout

大量重复计算就会拖慢页面。

更合理:

js 复制代码
for (const item of items) {
  item.style.width = '100px';
}

for (const item of items) {
  console.log(item.offsetWidth);
}

尽量变成:

text 复制代码
Write
Write
Write
Write
 ↓
Read
Read
Read
Read

22. requestAnimationFrame 在这里解决什么问题?

核心回答

requestAnimationFrame 会把回调安排到浏览器下一次绘制前执行,这样动画更新就能跟着浏览器的绘制节奏走。

例如:

js 复制代码
requestAnimationFrame(() => {
  box.style.transform = `translateX(${x}px)`;
});

但不要把 requestAnimationFrame 理解成:

"只要用了它,就不会回流。"

它解决的是:

更新时机和渲染节奏的问题。


23. will-change 是不是 GPU 加速开关?

核心回答

不是。will-change 是提前告诉浏览器某个元素的哪些属性可能发生变化,浏览器可以提前做优化准备。

例如:

css 复制代码
.box {
  will-change: transform;
}

它不是:

text 复制代码
will-change
 ↓
强制GPU加速

而是:

text 复制代码
告诉浏览器:
这个元素接下来可能发生 transform 变化
 ↓
浏览器自行决定是否提前优化

所以不能滥用。因为独立图层本身也有:

text 复制代码
GPU/内存占用
纹理管理成本
合成成本

图层不是越多越好。


24. contain: layout 能解决什么问题?

核心回答

它可以把布局影响限制在特定元素内部,减少内部变化向外部扩散的范围。

例如:

css 复制代码
.card {
  contain: layout;
}

重点是:

限制布局影响范围。

大型列表、复杂组件、局部独立区域中比较有价值。


25. 首屏图片很多,白屏时间长,应该怎么排查?

核心回答

我不会看到图片多就直接压图片,而是先用 Performance 和 Network 定位白屏到底卡在网络、HTML/CSS、JS,还是主线程渲染,再针对瓶颈处理。

例如:

text 复制代码
白屏
 ↓
到底卡在哪里?
 │
 ├─ TTFB 高?
 │
 ├─ HTML 下载慢?
 │
 ├─ CSS 下载/解析慢?
 │
 ├─ JS 下载慢?
 │
 ├─ JS 执行时间长?
 │
 ├─ 首屏图片请求晚?
 │
 ├─ 主线程任务太重?
 │
 └─ Layout / Paint 太重?

这才是线上问题的正确排查思路。


26. 如果发现 CSS 文件特别大,应该怎么办?

核心回答

首屏用到的 CSS 先加载,首屏用不到的 CSS 后加载。

可以考虑:

text 复制代码
大 CSS
 ↓
拆分
 ↓
关键 CSS
 ↓
优先加载

非首屏样式根据场景延后。比如不同媒体类型的 CSS:

html 复制代码
<link
  rel="stylesheet"
  href="print.css"
  media="print"
>

核心思想就是:

减少关键渲染路径上的阻塞。


27. 如果首屏图片特别多,应该怎么优化?

核心回答

核心不是"所有图片都加载得更快",而是让首屏真正需要的图片优先加载,非首屏图片延后加载。

例如:

text 复制代码
首屏图片
 ↓
合理尺寸
 ↓
压缩
 ↓
现代图片格式
 ↓
优先请求

非首屏图片
 ↓
loading="lazy"
 ↓
需要时再加载

还可以结合:

html 复制代码
<img
  src="hero.avif"
  fetchpriority="high"
  alt=""
>

以及:

html 复制代码
<img
  src="below-fold.avif"
  loading="lazy"
  alt=""
>

28. 这道题真正考什么?

核心回答

它考的不是你能不能背出渲染流程,而是你能不能根据浏览器的渲染管线,判断一次操作会影响哪一步,以及线上性能问题到底卡在哪里。

可以把能力分成四层:

text 复制代码
第一层
知道流程
 ↓
HTML → DOM
CSS → CSSOM
 ↓
布局 → 分层 → 绘制 → 分块 → 光栅化 → 合成


第二层
知道阻塞关系
 ↓
script 是否阻塞 HTML?
CSS 是否影响渲染?
defer / async 怎么工作?


第三层
知道更新路径
 ↓
width
 → Layout → Paint → Raster → Composite

color
 → Paint → Raster → Composite

transform
 → 满足条件时主要走 Composite


第四层
能解决线上问题
 ↓
为什么白屏?
为什么滚动卡?
为什么动画掉帧?
为什么 Layout 很高?
为什么 JS 执行把渲染卡住?
到底应该优化哪一步?

最后:真正面试时怎么答

如果面试官只问:

"说一下浏览器的渲染过程。"

不要一上来讲五分钟。

核心回答

浏览器会先解析 HTML 和 CSS,生成 DOM 和 CSSOM,然后进行样式计算和布局,生成布局树;接着进行分层、绘制、分块和光栅化,最后把这些图层合成并显示到屏幕上。

如果面试官继续追问:

"CSS、JS 谁会阻塞谁?"

再展开:

普通同步 JS 会暂停 HTML 解析;CSS 通常不会直接阻塞 HTML 解析,但关键 CSS 会影响首次渲染,而且 CSS 的加载还可能影响某些脚本的执行。

再追问:

"为什么 transform 动画性能好?"

再答:

因为满足合成条件时,transform 可以直接作用在已经准备好的图层上,动画过程中主要走合成,不需要每一帧重新做布局和绘制。

再追问:

"一帧改十次样式,会 Layout 十次吗?"

再答:

不一定,浏览器通常会把连续修改合并;但如果修改后马上读取 offsetWidth 这类布局信息,就可能强制同步 Layout,连续这样做就容易产生 Layout Thrashing。

这才是这道题从**"会背流程"到"真正懂浏览器渲染"**的分水岭。

相关推荐
变与不变8069 小时前
jQuery怎么使用
前端·javascript
神膘护体小月半9 小时前
flex 布局的 order 属性
前端·css
paopaokaka_luck9 小时前
流浪动物救助与收养系统(DeepSeek AI对话、 ECharts多维分析、救助流程衔接、领养与回访衔接、 捐赠确认与公示、志愿者协作)
java·前端·javascript·人工智能·mysql·echarts·推荐算法
仰望.9 小时前
vue 表格组件 vxe-table 树表格懒加载支持拖拽排序的详细用法
前端·javascript·vue.js·vxe-table
前端 贾公子9 小时前
vite-plugin-uni-manifest 升级指南
前端
小码哥_常10 小时前
iPhone 用户传了张 HEIC,我的前端崩了
前端
小码哥_常10 小时前
你发的原图可能暴露你家地址——聊聊图片 EXIF 隐私
前端
java1234_小锋10 小时前
【技术专题】CSS3 网页技术 - CSS3 优先级
前端·css·css3
鸿蒙开发11 小时前
共享 Schema 与 SchemaFactory
前端·后端·harmonyos
屏什么录11 小时前
录屏超过两小时就卡顿崩盘?从参数调优到代码实现,一文彻底搞定长时间录屏
前端·音视频开发