1、什么是 DOM、CSSOM?为什么它们合成后的 Render Tree 不包含 display: none 的元素?
答:DOM是由HTML解析生成的树,CSSOM是由CSS解析生成的规则树,浏览器会将两者结合生成Render Tree,用来进行布局和绘制。Render Tree只包含需要渲染的节点,因此像display: none的元素不会进入Render Tree(不参与布局和绘制),而visibility: hidden的元素仍然会在Render Tree中,只是最后不绘制。
DOM (Document Object Model):浏览器把HTML 文档解析成的树形结构。每个标签、文本、注释都会成为DOM节点。CSSOM (CSS Object Model):浏览器把CSS 样式表解析成的树状结构。CSSOM会描述选择器和规则。Render Tree (渲染树):浏览器会把DOM + CSSOM合并,得到一个只包含“可见节点”的树。这个树才是真正用来计算布局(Layout)和绘制(Paint)的。
为什么Render Tree不包含display: none的元素?
display: none的语义:该元素完全从视觉渲染树中移除。不占据空间,不渲染,不触发布局计算。- 浏览器性能考虑:如果把
display: none的元素也算进去会额外进行布局和绘制 → 浪费性能,所以浏览器在构建Render Tree时会直接跳过。 - 和
visibility: hidden的区别:visibility: hidden的元素仍然在Render Tree中,会占据布局空间,只是不绘制(透明处理),display: none的元素直接不进入Render Tree,连布局都不参与。
2、什么是回流(Reflow)和重绘(Repaint)?分别在什么时候发生?
答:回流(Reflow) 是浏览器重新计算元素的几何属性(大小、位置),会引发布局变化;重绘(Repaint)是元素外观样式变化但不影响布局时发生。回流的代价更大。常见触发回流的操作有:修改宽高、margin、display、读取offset系列属性等;常见触发重绘的操作有:修改颜色、背景、visibility等。优化上我们可以通过减少 DOM 操作、批量修改样式、使用transform/opacity替代位置变化来降低回流和重绘的成本。
- 回流(Reflow / Layout)
- 浏览器重新计算元素的几何属性(位置、大小、宽高、位置关系)。
- 会导致
Render Tree重新计算 + 页面布局重新生成。 - 代价比重绘大。
- 重绘(Repaint)
- 元素的几何属性没有变,只是样式(颜色、背景、阴影等)改变。
- 浏览器只会在已有的几何位置上重新填充像素。
- 比回流轻量。
- 触发回流的操作
- 页面首次渲染(必然发生)。
- 添加/删除
DOM节点。 - 改变元素的尺寸(
width/height/padding/margin/border)。 - 改变元素的显示状态(
display: none→ 显示)。 - 获取某些属性时(浏览器需要强制刷新计算):
offsetTop / offsetLeft / offsetHeight / offsetWidthscrollTop / scrollHeightgetComputedStyle()
- 改变窗口大小 / 改变字体大小。
- 触发重绘的操作
- 改变
color、background-color、visibility。 - 改变
box-shadow、outline等。 - 不涉及几何位置的纯样式变化。
- 改变
优化点:
- 避免频繁操作
DOM- 批量修改样式 → 使用
className一次性替换。 - 多次
DOM操作 → 先用documentFragment或cloneNode,最后一次性挂载。
- 批量修改样式 → 使用
- 减少回流触发
- 避免逐次访问
offsetXXX等属性,先缓存值。 - 避免逐条修改样式,最好合并修改。
- 避免逐次访问
- 使用
transform/opacity替代位置调整- 例如用
translate移动元素,不会触发回流,只触发合成层变换。 CSS动画性能优化时很常用。
- 例如用
- 使用
will-change或单独的合成层- 让浏览器提前优化某些属性的变化
2、什么是合成层(Compositing Layer)?它与 GPU 加速有啥关系?
答:合成层(Compositing Layer) 是浏览器渲染中被GPU独立管理的图层。当元素被提升为合成层时,它的动画(如transform、opacity)可以由GPU直接合成,不需要CPU重新布局和绘制,从而提升性能,实现硬件加速。常见触发方式有will-change、transform: translateZ(0)等。但层数过多会增加显存和合成开销,所以要合理使用。
- 合成层(Compositing Layer) 浏览器渲染页面时,会把
DOM + CSS转换成渲染树,然后经历以下步骤:- 布局(Layout):计算元素的大小、位置。
- 分层(Layering):根据
CSS特性(如transform, opacity等)决定哪些元素需要单独的图层(Layer)。 - 绘制(Paint):把每个图层的内容绘制到位图。
- 合成(Compositing):
GPU将这些图层合成在一起,形成最终的屏幕画面。
其中,合成层(Compositing Layer) 就是被单独提升出来,由GPU管理和渲染的图层。
- 为什么要有合成层?
- 如果所有元素都在同一个层里,每次元素变化(例如动画、滚动)都会导致 重绘/回流,性能很差。
- 把某些元素单独提到合成层,这些元素的变化(如
transform: translate())可以直接由GPU在独立层里处理,不用重新走布局和绘制。 - 结果就是:动画更流畅、性能更高。
- 如何触发
合成层(Layer Promotion),常见能让元素成为合成层的CSS/属性有:transform: translateZ(0) / translate3d(0,0,0)will-change: transform / opacity- 使用
CSS动画的transform、opacity <video>, <canvas>, <iframe>通常也会独立成层
注:滥用这些属性会导致层过多,增加内存和合成开销,反而掉帧。
- 它和
GPU加速的关系- 普通渲染流程:
CPU完成布局 + 绘制 → 把位图交给GPU合成。 - 合成层存在时:某些元素交由
GPU单独处理,移动/透明度变化时GPU直接做合成,而不依赖CPU重绘。这就是我们常说的 “硬件加速”。 - 合成层 ≠
GPU 加速,但合成层是GPU加速生效的前提。 - 提升到合成层后,能避免频繁的
CPU绘制,充分利用GPU的能力。
- 普通渲染流程:
2、浏览器中如何做到 JS 执行与页面渲染之间的协调?会不会互相阻塞?
答:JS执行和页面渲染都在浏览器主线程上运行,不能并行。如果JS执行时间过长,会阻塞渲染,导致掉帧或页面卡顿。浏览器通过事件循环协调JS和渲染:在每一帧(约 16.6ms)结束后,先清空JS微任务,再进行样式计算、布局和绘制。为了避免阻塞,可以使用requestAnimationFrame、Web Worker 或GPU 合成层优化。
- 浏览器主线程职责:
JS执行(JS 引擎,比如V8)- 样式计算(Recalculate Style)
- 布局(Layout)
- 绘制(Paint)
- 合成(Composite Layers)
也就是说,JS和渲染管线(排版、绘制)其实是在同一条主线程上运行的。
JS与渲染的协调机制。浏览器使用了事件循环(Event Loop)+ 渲染帧机制来协调:- 事件循环:
JS任务(宏任务/微任务)按队列依次执行。 - 渲染时机:浏览器通常会以
每秒 60 帧(16.6ms 一帧)为目标,在一次循环结束后:先清空微任务队列,再进行样式计算 → 布局 → 绘制 → 合成。 JS 和渲染不会并行(主线程只有一个),而是交替运行。
- 事件循环:
所以,如果JS长时间执行(比如死循环、大计算任务),主线程被占满,浏览器就没法插入渲染任务 → 页面卡死、掉帧。或者大量DOM操作、复杂计算 → 页面卡顿
- 浏览器的优化手段
- 任务切片:把大任务拆成小块,用
setTimeout/requestIdleCallback分批执行。 requestAnimationFrame:保证 JS 在浏览器下一帧渲染前运行,避免无效计算。Web Worker:把耗时计算放到后台线程,不阻塞UI渲染。GPU 合成层:把transform、opacity动画交给GPU合成,绕过主线程的布局和绘制
- 任务切片:把大任务拆成小块,用
3、Web Worker 和主线程通信原理是怎样的?适合做什么?
答:Web Worker是浏览器提供的多线程机制,运行在主线程之外,主要通过postMessage / onmessage进行通信,底层基于消息队列和结构化克隆。它不能直接操作DOM,适合处理计算密集型或长时间运行的任务,比如大数据计算、图像处理等,从而避免阻塞主线程,保证页面的流畅交互。
Web Worker是浏览器提供的一种多线程方案,允许JS在后台开辟一个独立线程运行。- 特点:
- 运行在主线程之外,不会阻塞UI渲染和事件响应。
- 不能操作
DOM、BOM(没有document、window)。 - 只能通过消息传递(postMessage + onmessage) 与主线程通信。
Web Worker和主线程之间的通信依赖事件机制:- 主线程 →
Worker
const worker = new Worker('worker.js'); worker.postMessage({ type: 'START', payload: 100 });Worker→ 主线程:
self.onmessage = function (e) { const { type, payload } = e.data; self.postMessage(`收到消息:${type}, ${payload}`); }- 主线程监听返回:
worker.onmessage = function (e) { console.log('来自 worker 的消息:', e.data); }- 主线程 →
- 使用场景:由于
Worker不能操作DOM,它主要用于 计算密集型任务- 大数据计算(如加密、压缩、复杂数学计算)。
- 图片/音视频处理(转码、滤镜)。
- AI/机器学习推理(
TensorFlow.js等在Worker中跑)。 - 长轮询/大文件分片上传等网络任务。
- 避免
JS长任务阻塞主线程,提升UI流畅度。
- 缺点:
- 不能
直接操作 DOM / BOM(document、alert、window不可用)。 - 不能访问
localStorage / sessionStorage(可用 IndexedDB 代替)。 - 受同源策略限制,加载的脚本必须同源。
- 不能
4、requestAnimationFrame 和 setTimeout 区别在哪?哪个更适合动画?
答:setTimeout基于事件循环,执行时间不准,不能保证和屏幕刷新同步。requestAnimationFrame是专为动画设计的 API,和显示器刷新率对齐,浏览器会在每一帧渲染前调用回调,保证动画流畅,并且在页面不可见时自动暂停,节能省资源。
setTimeout- 原理:把回调函数放入 宏任务队列,由事件循环调度执行。
- 缺点:
- 定时器不准:受事件循环和主线程阻塞影响,实际执行时间 ≥ 设定时间。
- 与屏幕刷新不同步:浏览器一般
60Hz(16.6ms 一帧),但setTimeout(fn, 16)不能保证和屏幕渲染对齐,可能掉帧、卡顿。 - 即使页面不可见(切到后台
Tab),它依然在运行(高耗能)。
requestAnimationFrame(rAF)- 原理:告诉浏览器“我需要执行一个动画”,浏览器会在下一次重绘前调用回调。
- 特点:
- 自动跟随显示器刷新率(通常
60Hz ≈ 16.6ms一帧),更流畅。 - 节能:后台标签页或隐藏元素不会执行(浏览器自动优化)。
- 时间戳参数:回调函数会收到一个高精度时间戳,可用于计算动画进度。
- 自动跟随显示器刷新率(通常
5、为什么说CSS动画(如 transform/opacity)性能比JS更优?
答:CSS动画性能优于JS动画的核心原因是:transform和opacity的变化不会触发回流/重绘,只触发合成(Compositing),可由GPU加速。CSS动画可以交给浏览器渲染线程独立执行,不依赖JS主线程,更流畅。JS动画虽然灵活,但会占用主线程,容易掉帧。
CSS动画可能由浏览器优化,甚至跳过JS主线程CSS动画(transform / opacity)可以交给浏览器 **渲染引擎或合成线程(Compositor Thread)**直接运行。- 即使
JS主线程卡住,CSS动画也能流畅运行。
CSS动画更容易触发GPU加速- 常见的
transform(位移、缩放、旋转)和 opacity(透明度变化),只会触发合成层更新。 - 不会引起回流(Reflow)或重绘(Repaint)。
- 浏览器可以把这些动画交给
GPU做硬件加速,性能更高、掉帧更少。
- 常见的
- 浏览器能进行内部优化(比如帧率控制、合成优化)
CSS动画运行时,浏览器可以自动调度动画帧,保证60fps。JS动画即使用requestAnimationFrame,也要走JS主线程,容易受垃圾回收、长任务影响。
- 为什么只提到
transform / opacity?- 因为 只有这两个属性的动画不会引起回流/重绘。
- 高性能属性:transform、opacity
- 慢的属性:
top/left、width/height、margin/padding等,会引起回流(Reflow),性能差。
- JS 动画的优势
- 更强的控制力(暂停、反转、动态计算值)。
- 可以与 业务逻辑、用户交互 强绑定。
- 适合复杂场景(物理模拟、游戏、拖拽)。