浏览器渲染流程
浏览器渲染把文档、样式和资源转换成屏幕上的内容。拿到 HTML 只是开始:浏览器还要建立文档结构、计算样式、确定几何位置,再准备绘制与合成。页面更新时也不必重新走完所有阶段,浏览器会根据变化重新计算受影响的部分。
面试中解释这条链路,重点是说清每一步需要什么信息、什么会阻塞它,以及修改页面后需要重做哪部分工作。这样才能解释脚本加载顺序、样式导致的等待,以及读取一个宽度为什么也可能产生开销。
1. 面试核心
先记住这几条,再读正文- 输入 URL 后,先区分导航类型与缓存、连接复用情况,再解释请求和文档处理;不是每次访问都重新执行 DNS、TCP 和 TLS 握手。卡 · 综合题
- DOM 表达文档结构,样式计算决定属性值,布局确定几何信息,绘制描述内容,栅格化与合成完成后续呈现。浏览器会增量处理,不必等整页所有资源下载完才开始。卡 · 文档结构与绘制过程
- 阻塞 HTML 解析、阻塞脚本执行与阻塞渲染是不同关系。适用的外链样式可以阻塞渲染,也可能让后续脚本等待;不能直接说 CSS 总会暂停 DOM 构建。卡 · 样式等待发生在哪里
- 对解析器插入的外部经典脚本,普通脚本暂停后续解析,defer 在解析完成后按文档顺序执行,async 就绪后执行且不保证相互顺序。模块脚本需要另看模块规则。卡 · async、defer 与模块
- DOMContentLoaded 不是所有资源加载完成,也不是首屏已经呈现;
load同样不代表业务异步请求结束或页面永久稳定。卡 · 文档事件与首次呈现 - 布局重新计算尺寸与位置,重绘更新视觉内容,合成可能复用已有结果。属性名能提示影响范围,但不能脱离页面条件承诺固定成本。卡 · 变化影响哪些阶段
- 读取几何信息需要当前结果时,尚未处理的布局变化可能被同步计算。
offsetWidth不是每读一次都强制重新布局,getComputedStyle()也不能一律判为“会”或“不会”。卡 · 几何读取与强制布局 - 把可合并的读取与写入分组,可以减少反复作废和重算布局;
requestAnimationFrame不会自动修复回调内部的读写交错。卡 · 合并读取与写入 - display、visibility 和 opacity 对布局占位、命中与可访问性有不同影响。透明不等于不可操作,隐藏也不等于节点已经从 DOM 删除。卡 · 隐藏方式
- transform 改变视觉位置,不重新安排正常流中原来的占位;是否仅由合成器处理,还要看内容、动画和浏览器实现。卡 · 视觉移动与正常流
- 图片还要经过解码并参与后续呈现;提前声明合适尺寸有助于保留空间,下载完成不等于它已出现在屏幕上。卡 · 图片获取与解码
2. 导航与文档响应
导航是浏览器把某个页面位置转到目标资源的过程。回答“输入 URL 到页面出现”时,可以先以一次普通 HTTPS 文档导航为主线,再说明哪些步骤可能被复用或跳过。
| 阶段 | 浏览器需要解决的问题 | 条件与边界 |
|---|---|---|
| 处理目标 | 解析地址,确定协议、目标与导航方式 | 搜索词、同文档片段跳转、历史恢复,不应都按新建网络请求解释 |
| 找到响应 | 检查可用缓存、受控页面的 Service Worker 等路径;必要时访问网络 | HTTP 缓存命中或 Service Worker 返回响应时,不一定访问源站 |
| 建立或复用连接 | 需要时解析域名,使用已有连接或建立新的安全连接 | HTTP/1.1、HTTP/2 可基于 TCP/TLS;HTTP/3 使用 QUIC,不能套用 TCP 三次握手 |
| 处理响应 | 处理状态码、重定向、响应头与正文 | 重定向可能引发后续请求;下载、错误响应和 HTML 文档也不是同一种处理 |
| 处理文档 | 解码字节、解析 HTML,发现样式、脚本和图片等资源 | 正文可以流式到达,资源发现、网络与解析能够交叠进行 |
| 更新页面 | 样式、布局、绘制、栅格化与合成 | 首次呈现后,后续资源、脚本和用户交互仍可能使页面变化 |
域名解析的缓存和查询链路见 DNS,新建 TCP 连接的过程见 TCP 连接。HTTPS 的证书与密钥协商见 TLS,不同 HTTP 版本的连接方式见 HTTP。这些协议负责把响应可靠、安全地送到对应处理流程,浏览器渲染继续处理响应中的文档内容。
缓存路径也不能简化成“缓存都在 DNS 前检查一次”。DNS 缓存、HTTP 响应缓存、连接复用分别保存不同对象;新鲜响应与条件验证的区别见 HTTP 缓存。历史导航还可能恢复保留中的页面状态,而不是重新执行完整加载过程。
3. 文档结构与绘制过程
3.1 DOM 与样式
DOM 是供程序访问和修改的文档对象树。浏览器对 HTML 字节解码、识别标记,再按 HTML 解析规则建立节点关系;它不是把源码中的尖括号原样转成一棵树。
<table id="result"><tr><td>通过</td></tr></table>
<script>
console.log(document.querySelector('#result').firstElementChild.tagName);
</script>
以普通 HTML 文档解析这段代码,输出 TBODY。源码没有显式写 tbody,解析器按表格规则补出了这个元素。排查选择器或 DOM 操作时,应检查实际结构,不能只数源码中的层级。
CSSOM 是样式表及其规则等内容的对象模型。浏览器根据规则、继承、层叠和当前状态,为元素计算样式。CSSOM 与 DOM 不是一一对应:一个规则可能匹配多个元素,一个元素也可能匹配许多规则。属性值竞争见 CSS 层叠。
3.2 布局、绘制、栅格化与合成
布局(layout,重新计算时也称 reflow、重排)确定参与布局的盒子如何分行、占多大空间、位于哪里。它依赖样式、可用空间、文本和资源尺寸等信息。
面试中说的“渲染树”可以理解为连接样式与布局、绘制的教学概括,不要把它当成所有浏览器唯一且固定的内部数据结构。实际引擎可能维护布局对象、片段、属性树和显示列表等结构。
| 阶段 | 主要结果 | 例如一个申请按钮 |
|---|---|---|
| 样式计算 | 最终参与后续计算的样式 | 字号、背景色、内边距、显示方式 |
| 布局 | 几何尺寸与位置 | 文本换行后按钮多高,位于父容器哪里 |
| 绘制 | 描述要绘制内容及顺序的指令 | 背景、边框、文字、阴影怎样画 |
| 栅格化 | 把绘制内容转换成像素数据 | 生成相应区域的像素内容 |
| 合成与显示 | 按位置、变换、裁剪等组合可用结果 | 将按钮和其他页面内容组织成最终画面 |
绘制不等于立即把所有像素送上屏幕,合成也不意味着整个页面只有一个 GPU 操作。这里是机制上的分工,线程、进程和实际优化方式由引擎实现决定。
DOM 节点与布局盒也不一一对应。文本可能分成多行,伪元素可能产生盒子;display: none 的元素及其后代不产生相应布局盒,display: contents 则可能让后代继续参与布局而不生成父元素自己的盒子。不能把“在 DOM 里”直接等同于“有一个可见矩形”。
4. 资源加载与阻塞
4.1 HTML 解析与经典脚本
浏览器解析普通经典脚本时,需要先执行脚本,再继续处理它后面的 HTML。脚本可以读取已经存在的节点,也能修改文档,所以后续节点是否已经建立会影响结果。
<p id="early">已经解析</p>
<script>
console.log(Boolean(document.querySelector('#early')));
console.log(Boolean(document.querySelector('#late')));
</script>
<p id="late">尚未解析</p>
输出为 true、false。当脚本执行时,后面的 #late 还没有被这次解析建立。换成没有 async/defer 的外部经典脚本,解析还需要等待脚本获取并执行。
解析器暂停不表示整个浏览器停止工作。网络获取仍可能继续,浏览器的预扫描机制也可能提前发现后方资源并发起请求;提前发现资源不等于已经建立相应 DOM 或执行了相应脚本。
4.2 async、defer 与模块
下面的比较针对正常插入文档解析流程的脚本。动态创建脚本的默认顺序、内联脚本和模块依赖,不能只按属性名字类推。
| 方式 | 获取与 HTML 解析 | 执行时机与顺序 |
|---|---|---|
外部经典脚本,无 async/defer | 后续解析等待脚本 | 获取就绪并满足执行条件后执行,再继续解析 |
外部经典脚本 defer | 获取可与解析并行 | 解析完成后按文档中的相互顺序执行,在 DOMContentLoaded 之前 |
外部经典脚本 async | 获取可与解析并行 | 就绪后获得执行机会,不保证多个脚本的文档顺序;执行仍占用对应主线程 |
type="module",无 async | 获取模块及依赖可与解析并行 | 默认按模块处理流程在解析完成后启动求值;不需要再加 defer |
type="module" async | 并行获取模块图 | 模块图就绪后尽早开始求值,不保证与其他入口的文档顺序 |
例如,下列两个外部经典脚本存在先后依赖时,可以使用 defer:
<script defer src="/first.js"></script>
<script defer src="/second.js"></script>
<p id="ready">页面结构已解析</p>
// first.js
window.order = ['first'];
// second.js
window.order.push('second');
console.log(window.order.join(','), Boolean(document.querySelector('#ready')));
输出 first,second true,即使 second.js 更早下载完成也应等待前面的延迟脚本。改成 async 后不再具有这个顺序保证,second.js 甚至可能在 window.order 初始化前运行。
经典内联脚本上的 defer 不会产生上述延迟效果;同时写 async 与 defer 的外部经典脚本按 async 行为处理。动态插入的经典外部脚本通常具有异步执行行为,需要顺序时应显式设计加载方式,而不是认为 DOM 插入顺序自动决定执行顺序。
模块脚本还涉及依赖图和顶层 await。DOMContentLoaded 不会等待所有顶层 await 后面的异步业务完成,不能把“模块默认延迟”解释成“所有异步求值都结束后才触发事件”。模块边界见 ES Module 与 CommonJS。
4.3 样式等待发生在哪里
适用于当前页面的外链样式通常影响首次渲染:浏览器需要它来确定正确的样式,避免先展示不完整样式再大幅改变。等待样式不等于 HTML 解析器必然在 link 处暂停。
不过,样式可能让后续脚本等待。脚本能够读取尺寸和样式,为避免使用尚未就绪的结果,解析过程中符合条件的脚本会等待此前尚未就绪、会阻塞脚本的样式表。
<head>
<link rel="stylesheet" href="/slow.css">
<script>console.log('脚本执行');</script>
</head>
<body><p id="after">脚本之后的节点</p></body>
在这个结构里,如果适用的 slow.css 还没准备好,脚本会等待,后面的 #after 也因此尚未由主解析器建立。不能据此得出“CSS 直接暂停所有 DOM 解析”的结论:移除这个脚本后,解析可以继续建立后续节点。
这里特意把样式放在 head。Chromium 对 body 内的外链样式还有暂停后续 DOM 构建的实现行为,不能把上述移除脚本后的结论直接推广到所有位置和引擎。media="print" 之类当前不匹配的样式有不同的阻塞关系,动态样式也要看插入位置与具体规则。对页面慢的判断应检查具体资源依赖,不能把所有 <link> 都算成同一种阻塞。
5. 文档事件与首次呈现
DOMContentLoaded 表示文档已完成解析,并完成相应的解析后脚本处理。它不等待普通图片全部加载,也不等待任意 fetch()、计时器或异步业务逻辑结束。它本身不直接以“所有样式完成”为条件,但等待样式的脚本可以间接推迟它。
load 要等待会延迟文档加载事件的相关资源和脚本处理,但不能理解成“页面此后没有任何网络活动”。懒加载图片、后续请求、动态组件和持续连接都有自己的生命周期。
| 状态或事件 | 能说明什么 | 不能据此保证什么 |
|---|---|---|
readyState === 'loading' | 文档仍在解析 | 屏幕完全没有任何内容 |
interactive | 解析已完成 | 所有延迟脚本、资源与业务都已完成 |
DOMContentLoaded | 文档解析与相应解析后脚本阶段已完成 | 图片齐全、首屏已显示、异步业务已完成 |
complete / load | 文档的加载完成条件已满足 | 页面永远稳定、用户一定已经看到某一帧 |
如果代码可能在事件之后才被加载,要同时检查当前状态:
function initialize() {
document.querySelector('#status').textContent = '结构可用';
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initialize, { once: true });
} else {
initialize();
}
这里假设文档含有 #status。代码只需要文档结构,因此在解析已经结束时可以直接初始化;如果还依赖图片尺寸或其他模块的异步结果,就应等待对应的条件,不能借这个事件替代它们。
页面可能在 DOMContentLoaded 之前已经显示内容,也可能因渲染阻塞在之后才得到某些呈现结果。requestAnimationFrame 是渲染更新过程中的回调机会,也不是“刚才的 DOM 一定已经被用户看见”的凭据,事件循环与呈现的关系见 浏览器事件循环。
6. 页面更新与同步布局
6.1 变化影响哪些阶段
浏览器会记录哪些结果已经失效,在需要时重新计算。修改 JavaScript 对象不一定修改页面,修改 DOM 也不一定让所有节点重新布局;真正的范围取决于变化是否影响样式、几何信息和视觉内容。
| 变化 | 应先检查的影响 |
|---|---|
| 修改参与正常流的宽度、字号、内容 | 可能改变分行、尺寸和邻近布局,后续可见内容也需更新 |
| 只改变背景色 | 通常无需改变几何关系,但可见区域需要重新绘制 |
修改 transform 或 opacity | 可能复用已有内容并更新合成属性,但不保证所有场景都只合成 |
| 给不可见、未布局或未连接的对象赋值 | 不一定产生对应的屏幕更新;连接后或显示时仍可能发生工作 |
“重排一定引发重绘”适合提示布局变化通常还需要后续视觉更新,但不能解释成每次布局都必须重新绘制整个页面。不可见结果、复用、局部失效和合成策略都会改变实际工作。
6.2 几何读取与强制布局
强制同步布局是在脚本需要最新几何结果时,浏览器把尚未完成的必要布局计算提前执行。其原因是“读取依赖已经失效的结果”,不是某个 getter 无条件附带一次完整重排。
<div id="box" style="width: 100px; height: 20px"></div>
<script>
const box = document.querySelector('#box');
box.style.width = '180px';
console.log(box.offsetWidth);
console.log(box.offsetWidth);
</script>
两次都输出 180。第一次读取需要得到修改之后的布局宽度,浏览器可能必须同步更新必要结果;在没有其他失效的情况下,第二次可以使用已有效的结果。仅凭两次读取,不能声称发生两次重排。
getComputedStyle() 返回的也不只是从样式表复制出来的字符串。有些属性需要解析后的使用值,例如在相应条件下读取宽度需要知道布局。判断成本时应看读取的是 color 还是 width、页面当前是否已有有效样式和布局,以及元素所处的上下文,不要背一张“所有 API 永远触发布局”的表。
6.3 尺寸 API 的不同含义
读取几何信息前,还要先确定自己要的是哪一种尺寸。
<div id="box" style="box-sizing: content-box; width: 100px; height: 20px; padding: 10px; border: 2px solid; transform: scale(2); transform-origin: left top"></div>
<script>
const box = document.querySelector('#box');
console.log(box.clientWidth, box.offsetWidth, box.getBoundingClientRect().width);
</script>
在这个没有滚动条的例子中,输出 120 124 248:clientWidth 包含内边距而不含边框,offsetWidth 返回忽略变换的整数布局外框宽度,getBoundingClientRect() 则给出包含变换效果、相对视口的包围矩形。后者在旋转时也不等于元素本来的布局宽度;返回的小数不应随意按整数布局量处理。
6.4 合并读取与写入
布局抖动(layout thrashing)是脚本反复写入使布局失效,又立即读取迫使它更新的模式。重复计算会在同一次任务里放大成本。
例如,下面只展示已有 #list 和 .item 元素后的操作,列表宽度固定,不依赖项目的宽度:
const list = document.querySelector('#list');
const items = [...list.querySelectorAll('.item')];
for (const item of items) {
item.style.width = `${list.clientWidth}px`;
console.log(item.offsetWidth);
}
每项写完马上读,可能让浏览器为每一步都更新布局。如果只需要最终宽度,可以先读共同条件、集中写入,再集中读取结果:
const list = document.querySelector('#list');
const items = [...list.querySelectorAll('.item')];
const width = list.clientWidth;
for (const item of items) item.style.width = `${width}px`;
const results = items.map(item => item.offsetWidth);
console.log(results);
这不是零布局,而是给浏览器合并工作的机会。若每一步必须依据上一步的新尺寸作决策,就不能未经分析地交换顺序。把原来的交错循环整个包进 requestAnimationFrame,内部依赖仍然存在;改用 DocumentFragment 也不保证自动消除所有计算。
7. 隐藏、移动与图片
7.1 隐藏方式
隐藏的含义至少包括视觉、布局占位和交互三个方面。选择方式前,应明确希望哪些方面改变。
| 方式 | 布局空间 | 视觉与交互边界 |
|---|---|---|
display: none | 元素与后代不生成相关布局盒 | 不显示且通常不在可访问性树中;DOM 与监听器并未因此删除 |
visibility: hidden | 保留布局空间 | 隐藏元素通常不可命中、不可聚焦;后代可显式设为 visible 恢复可见 |
opacity: 0 | 保留布局空间 | 组内容透明,但仍可能接收指针、键盘焦点并出现在可访问性树中 |
pointer-events: none | 保留布局空间 | 改变指针命中,不负责隐藏,也不等价于禁用键盘访问 |
| 从 DOM 移除 | 不再按原文档位置布局 | 其他引用、监听器关联和异步任务是否解除要分别处理 |
对于普通已显示的盒子,切换 display: none 会改变其布局参与关系,可能重新安排邻居;切换 visibility 通常保留几何关系而更新绘制状态。不要据此声称隐藏的所有资源都停止下载,或节点占用的所有内存立即回收。
CSS display 的初始值是 inline,但 div 在浏览器默认样式中通常表现为块盒;“属性初始值”与“某个元素当前计算出来的默认表现”要分开。相关回退规则见 CSS 层叠。
7.2 视觉移动与正常流
如果只希望视觉上平移一个已有盒子,而不让邻居重新占位,可以使用 transform。它不应被当成“把正常流中的位置也改变了”。
<style>
.row { display: flex; }
.item { width: 80px; height: 40px; flex: none; }
.moving { background: #2563eb; transform: translateX(160px); }
.next { background: #facc15; }
</style>
<div class="row"><div class="item moving">移动</div><div class="item next">邻居</div></div>
蓝色盒子向右显示了 160px,黄色邻居仍占据原来第二项的位置。变换可能改变视觉覆盖、命中、溢出区域和某些后代的定位参照,因此它不是没有副作用的移动方式。确实要改变布局顺序或为内容重新留空间时,就应修改相应布局规则。
transform、opacity 的动画有机会复用已经准备好的内容,减少主线程布局和绘制工作,但是否只走合成要具体检查。will-change 是让浏览器为可能变化作准备的提示,提前太久或给太多元素使用可能增加资源成本;它还可能提前建立相关层叠等边界,不能当通用加速开关。层叠与合成的区别见 层叠上下文。
7.3 图片获取与解码
图片从 URL 到屏幕,至少还涉及资源选择、获取、解码和后续绘制。压缩的 JPEG、PNG 等字节不等于可直接放进页面的像素;图片尺寸也可能影响页面布局。
<img src="/cover.png" width="600" height="400" alt="课程封面" style="max-width: 100%; height: auto">
已知图片尺寸时,声明匹配的 width 与 height 可以让浏览器提前得到比例信息、为图片保留合适空间,减少图片加载后改变布局的机会。CSS 仍能限制显示尺寸,不是声明了 600 就永远在所有屏幕画成 600 CSS 像素。
load 表示相应图片的加载结果已经可用,img.decode() 提供解码完成相关的 Promise;二者都不能直接证明用户已看到这张图片。需要先解码再替换可见图片时,可以等待 decode() 并处理失败;首屏性能还要检查资源是否被及时发现、选择是否合适、解码和绘制是否占用过多时间。响应式候选选择见 响应式布局。
8. 渲染证据
定位页面卡顿时,先用浏览器 Performance 记录具体交互,检查耗时落在脚本、样式、布局还是绘制等阶段,再检查调用栈与对应元素。网络面板用于确认资源依赖和等待;只看到“请求很快”不能排除主线程计算或图片解码的成本。
对照实验要保持内容与最终效果一致。例如比较布局读写前后的布局次数、受影响范围和总耗时,同时确认没有为了快而读到过期结果。单次运行时间会受缓存、机器、页面状态影响,不能从几个毫秒差异推导所有浏览器的固定性能倍数。
也不要用“DOM 已改变”当作已经呈现的证据。自动化中可以检查结构、几何与加载事件;如果要判断视觉结果,还应查看实际截图或渲染记录。页面的合成优化与 Web 可观察语义相关,但具体内部数据结构和执行线程不能只从一个 JavaScript 返回值推出。
9 · 面试题卡片
本页题目与参考回答
9.1 输入 HTTPS 地址到页面显示,浏览器经历了哪些过程?
题型:综合题
考察重点:导航、缓存与连接复用、响应处理、文档资源及渲染的衔接。
相关内容:导航与文档响应、布局、绘制、栅格化与合成。
参考回答
先处理导航目标并确定如何取得响应,再把文档和资源转换成可呈现内容。 对需要访问网络的请求,必要时解析域名并建立连接,也可能复用缓存和连接;HTTPS 校验证书并协商保护,HTTP/3 不能套用 TCP 握手。浏览器处理响应状态与重定向,流式解码并解析 HTML,发现样式、脚本和图片,结合脚本执行建立当前文档状态。随后进行样式计算、布局、绘制、栅格化与合成。后续资源与交互仍会更新页面,所以不是所有步骤都严格串行,也不是每次导航都从零开始。
9.2 DOM、CSSOM、布局、绘制与合成分别负责什么?
考察重点:文档与样式模型、几何信息、绘制内容和呈现结果的区别。
相关内容:文档结构与绘制过程。
参考回答
DOM 表达文档结构,CSSOM 表达样式规则,样式计算和布局确定盒子的属性与几何,绘制和后续栅格化、合成把它们变为画面。 DOM 节点不一定生成可见盒子,布局也不等于已经把像素画出来。“渲染树”是常用概括,实际引擎内部会维护多种结构;更新时可以只处理失效的部分,不能理解成每次都重建整个页面。
9.3 CSS 会阻塞 DOM 解析和 JavaScript 吗?
考察重点:直接与间接阻塞、样式适用条件及脚本依赖。
相关内容:样式等待发生在哪里。
参考回答
适用的外链 CSS 可以阻塞渲染,也可能阻塞后续符合条件的脚本,但不应概括成 CSS 总会直接暂停 DOM 解析。 在本文 head 示例中,后续经典脚本等待此前样式,解析又在等待脚本,因此出现间接等待;去掉该脚本,解析可以继续。Chromium 的 body 内外链样式还会暂停后续 DOM 构建,媒体条件不匹配、动态插入等情况也有不同规则,应结合具体资源、位置和引擎分析。
9.4 async、defer 和普通脚本的加载顺序有什么区别?
考察重点:解析器插入的经典脚本、下载并行、执行顺序与模块边界。
相关内容:async、defer 与模块。
参考回答
普通外部经典脚本暂停后续解析,defer 在解析完成后按文档顺序执行,async 就绪后执行且不保证相互顺序。 下载并行不等于脚本在主线程执行时完全不影响解析或交互。内联经典脚本的 defer 不产生同样效果;模块默认有解析后执行规则,依赖图和顶层 await 需另行分析;动态插入脚本也不能直接套用普通解析器脚本的顺序。
9.5 DOMContentLoaded 和 load 能否表示页面已经完全显示?
考察重点:文档状态、资源等待、异步业务与屏幕呈现的区别。
相关内容:文档事件与首次呈现。
参考回答
不能,二者描述文档解析或加载阶段,不保证某个业务状态已经上屏。 DOMContentLoaded 不等待普通图片与任意异步请求;load 等待相关加载条件,但后续懒加载和业务请求还可以继续。首次绘制与这些事件不是固定的一一对应关系,代码晚加载时还要检查 readyState,不能只注册一个可能已经触发的事件。
9.6 重排和重绘有什么区别,哪些变化会触发它们?
考察重点:几何失效、视觉失效、局部更新与合成复用。
相关内容:变化影响哪些阶段。
参考回答
重排重新确定几何关系,重绘更新要显示的视觉内容,二者解决的问题不同。 宽度、字号、内容等变化可能影响布局,背景色变化通常只影响绘制;transform 或 opacity 在适合的条件下可能复用已有内容处理视觉效果。布局变化通常还需要后续视觉更新,但不等于全页重新绘制,也不能把任何属性永久归为固定成本而不看页面条件。
9.7 读取 offsetHeight 或 getComputedStyle 一定会触发重排吗?
考察重点:有效布局、脏状态、几何读取与属性依赖。
相关内容:几何读取与强制布局、尺寸 API 的不同含义。
参考回答
不一定;当读取所需的几何结果已失效时,浏览器可能必须同步更新布局,否则可以复用有效结果。 先写宽度再读 offsetHeight 可能提前计算,连续读而没有新的失效不必每次重算。getComputedStyle 还取决于所读属性是否需要布局结果及上下文,不能一律回答会或不会。几何值也要分清布局尺寸与含变换的视口矩形。
9.8 怎样减少反复重排,把代码放进 requestAnimationFrame 就够了吗?
考察重点:读写依赖、批量更新与回调内部的同步计算。
相关内容:合并读取与写入。
参考回答
先分析依赖,把可以合并的读取、写入和最终测量分组,避免每次写后马上读最新几何。 如果布局仍然需要更新,目标是减少重复计算而非保证零布局。requestAnimationFrame 只改变回调获得执行机会的阶段,回调内部继续读写交错仍可能反复同步布局;真正依赖中间结果的操作不能随意调换顺序。
9.9 display: none、visibility: hidden 和 opacity: 0 有什么区别?
考察重点:布局占位、视觉、指针键盘交互与 DOM 生命周期。
相关内容:隐藏方式。
参考回答
display: none 移除相关布局盒,visibility: hidden 保留空间但隐藏内容,opacity: 0 则让内容透明而仍保留布局和许多交互能力。 visibility 的后代可以设为 visible,opacity 会按组影响后代显示。透明元素仍可能接收点击和键盘焦点,也不自动从可访问性树消失。三者都不能直接等同于从 DOM 删除或停止所有资源与异步工作。
9.10 怎样平移一个盒子而不让邻居重新布局,是否保证只走合成?
考察重点:视觉变换、正常流占位、合成条件与副作用。
相关内容:视觉移动与正常流。
参考回答
只需要视觉平移时可以使用 transform,它不重新安排正常流原来的占位,但不保证所有情况都只走合成。 邻居仍按原布局排列,视觉覆盖、命中、溢出和后代定位参照可能变化。若要真正改变流中顺序或留白,应调整布局。是否跳过绘制或主要由合成器处理,需要查看实际页面记录,will-change 也不是免费加速。
9.11 一张图片从请求到显示还会经历什么,为什么要声明宽高?
考察重点:资源选择、解码、几何稳定与屏幕呈现。
相关内容:图片获取与解码。
参考回答
浏览器要选择和获取资源,将编码图片解码为可绘制内容,再结合布局、绘制与合成显示。 提前提供正确宽高能帮助浏览器获得比例并保留空间,减少加载后改变几何布局的机会,CSS 仍可以控制实际显示尺寸。load 或 decode 完成不等于图片已经被用户看到,还需要后续呈现;优化也应检查发现时间、候选尺寸和解码成本。
9.12 will-change 为什么不能给所有元素一直开启?
考察重点:预备资源、实现选择及层叠或定位副作用。
参考回答
will-change 只是让浏览器为可能发生的变化作准备,可能消耗资源并提前改变相关边界。 它不保证创建某种合成层,也不保证一定更快。应在实际证据表明有收益、且能够合理提前提示时使用,变化结束后清理不再需要的提示;大面积长期设置可能增加内存和管理成本,甚至影响弹层等行为。
10. 参考资料
- WHATWG HTML:Parsing HTML documents
- WHATWG HTML:The script element
- WHATWG HTML:Document readiness
- Chromium:RenderingNG architecture
- Chrome:Inside look at modern web browser, part 3
- web.dev:Render-blocking CSS
- WHATWG:In-body external stylesheet parsing discussion
- web.dev:Avoid large, complex layouts and layout thrashing
- CSSOM View:Element geometry
- CSSOM:Resolved values
- CSS Will Change:The will-change property
- WHATWG HTML:HTMLImageElement.decode()
来源:Coding 101 原文 · 免费阅读,引用请保留出处。内容使用说明