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

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。

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

相关推荐
太子釢1 小时前
React 函数组件与 Hook 实践指南
前端·react.js
颜进强1 小时前
17 · 自定义 Provider 四形态:useValue / useClass / useFactory / useExisting 到底在选什么
前端·后端·ai编程
传人once1 小时前
网页文字滚动效果如何做
前端·css·html·jquery·html5
liangshanbo12151 小时前
面试题:React 中 Lane 和 Scheduler 是怎么配合的?
前端·react.js·前端框架
liangshanbo12152 小时前
请求拦截器相关面试题
java·前端·javascript
ShawnLiaoking2 小时前
ADS305 Big Data Lecuture4 Web Scraping
大数据·前端
liangshanbo12152 小时前
React面试题:Hooks 为什么不能在分支和循环里面写?
前端·javascript·react.js
开开心心就好2 小时前
视频压缩工具推荐,绿色版免安装双击即用
java·前端·人工智能·spring·智能手机·intellij-idea·excel
西红柿炖牛腩3542 小时前
GLB 转 OBJ/PLY 后体积没变?3D 模型不减面变小靠什么
前端·javascript·3d