前端面试题:如何减少页面重绘跟重排

📌 面试官视角

在前端面试中,"如何减少页面重绘跟重排"是一道高频且经典的面试题。这道题不仅考察你对浏览器渲染机制的理解,还能看出你的性能优化意识和实际项目经验。掌握这个知识点,能让你在面试中脱颖而出。

一、面试题分析

❓ 面试题:如何减少页面重绘跟重排?

这道面试题看似简单,实则考察了多个层面的知识:

  • 基础概念:什么是重绘?什么是重排?它们有什么区别?
  • 触发条件:哪些操作会触发重绘?哪些操作会触发重排?
  • 优化策略:如何减少重绘和重排?有哪些具体的优化方法?
  • 实践经验:在实际项目中如何应用这些优化策略?

💡 面试加分点

能够从浏览器渲染原理出发,结合具体代码示例,说明优化策略的实际应用,并能分享项目中的实践经验。

二、重绘和重排的概念

1. 什么是重排(Reflow)?

重排,也叫回流,是指浏览器需要重新计算页面元素的几何属性(位置、大小、边距等)的过程。

复制代码
DOM改变 → 重新计算布局 → 生成渲染树 → 重排

2. 什么是重绘(Repaint)?

重绘是指浏览器需要重新绘制页面元素的外观(颜色、背景、边框等)的过程。

复制代码
样式改变 → 重新绘制 → 重绘

3. 重排和重绘的关系

重要结论:重排一定会导致重绘,但重绘不一定导致重排。

特性 重排(Reflow) 重绘(Repaint)
触发条件 元素几何属性改变 元素外观属性改变
性能消耗 大(需要重新计算布局) 小(只需重新绘制)
是否触发对方 会触发重绘 不一定触发重排
常见操作 改变宽高、位置、边距等 改变颜色、背景、边框等

三、触发重排和重绘的操作

1. 触发重排的操作

📝 触发重排的常见操作

javascript 复制代码
// 1. 改变元素的几何属性
element.style.width = '200px';
element.style.height = '200px';
element.style.margin = '10px';
element.style.padding = '10px';
element.style.border = '1px solid #000';

// 2. 改变元素的位置
element.style.top = '100px';
element.style.left = '100px';
element.style.position = 'absolute';

// 3. 操作DOM树
document.body.appendChild(element);
element.parentNode.removeChild(element);

// 4. 改变字体大小
element.style.fontSize = '20px';

// 5. 获取布局信息(强制同步布局)
const width = element.offsetWidth;
const height = element.offsetHeight;
const top = element.offsetTop;

2. 触发重绘的操作

📝 触发重绘的常见操作

javascript 复制代码
// 1. 改变颜色
element.style.color = 'red';
element.style.backgroundColor = 'blue';
element.style.borderColor = 'green';

// 2. 改变背景
element.style.background = 'url(image.jpg)';
element.style.backgroundImage = 'url(image.jpg)';

// 3. 改变透明度
element.style.opacity = '0.5';

// 4. 改变可见性
element.style.visibility = 'hidden';

// 5. 改变文本样式
element.style.textDecoration = 'underline';
element.style.textShadow = '2px 2px 2px #000';

⚠️ 强制同步布局

在JavaScript中,某些操作会强制浏览器立即执行布局计算,这被称为"强制同步布局"(Forced Synchronous Layout)。这种操作会导致性能问题,应该尽量避免。

四、减少重排和重绘的策略

1. 使用 transform 和 opacity 代替 top/left 和 visibility

❓ 面试题:为什么 transform 和 opacity 性能更好?

transform 和 opacity 不会触发重排,只会触发重绘,而且可以利用 GPU 加速。

📝 性能对比

javascript 复制代码
// ❌ 不推荐:会触发重排
element.style.left = '100px';
element.style.top = '100px';
element.style.visibility = 'hidden';

// ✅ 推荐:只触发重绘,GPU加速
element.style.transform = 'translate(100px, 100px)';
element.style.opacity = '0';

💡 原理说明

transform 和 opacity 不会改变元素的布局,只是改变元素的绘制方式。浏览器可以将这些操作交给 GPU 处理,从而提高性能。

2. 批量操作 DOM

❓ 面试题:如何批量操作 DOM 以减少重排?

将多次 DOM 操作合并为一次,可以大大减少重排次数。

📝 批量操作 DOM

javascript 复制代码
// ❌ 不推荐:每次操作都会触发重排
for (let i = 0; i < 100; i++) {
    const li = document.createElement('li');
    li.textContent = 'Item ' + i;
    document.getElementById('list').appendChild(li);
}

// ✅ 推荐:使用文档片段批量操作
const fragment = document.createDocumentFragment();
for (let i = 0; i < 100; i++) {
    const li = document.createElement('li');
    li.textContent = 'Item ' + i;
    fragment.appendChild(li);
}
document.getElementById('list').appendChild(fragment);

3. 避免强制同步布局

❓ 面试题:什么是强制同步布局?如何避免?

强制同步布局是指在 JavaScript 中读取布局信息后立即修改布局,这会强制浏览器立即执行布局计算。

📝 避免强制同步布局

javascript 复制代码
// ❌ 不推荐:强制同步布局
function resizeAll() {
    const elements = document.querySelectorAll('.item');
    elements.forEach(element => {
        const width = element.offsetWidth; // 读取布局
        element.style.width = width + 10 + 'px'; // 立即修改布局
    });
}

// ✅ 推荐:分离读取和修改操作
function resizeAll() {
    const elements = document.querySelectorAll('.item');
    const widths = [];
    elements.forEach(element => {
        widths.push(element.offsetWidth); // 先读取所有布局
    });
    elements.forEach((element, index) => {
        element.style.width = widths[index] + 10 + 'px'; // 再修改所有布局
    });
}

4. 使用虚拟 DOM

❓ 面试题:虚拟 DOM 如何减少重排和重绘?

虚拟 DOM 通过 Diff 算法计算出最小的变更,然后批量更新真实 DOM,从而减少重排和重绘的次数。

📝 虚拟 DOM 的工作原理

javascript 复制代码
// 虚拟 DOM 的核心思想
// 1. 创建虚拟 DOM 树
const virtualDOM = {
    tag: 'div',
    props: { className: 'container' },
    children: [
        { tag: 'h1', props: {}, children: ['Hello'] },
        { tag: 'p', props: {}, children: ['World'] }
    ]
};

// 2. Diff 算法比较新旧虚拟 DOM
// 3. 计算出最小的变更
// 4. 批量更新真实 DOM

💡 原理说明

虚拟 DOM 的核心思想是:在内存中维护一个虚拟的 DOM 树,当数据变化时,先比较新旧虚拟 DOM 树的差异,然后只将差异部分应用到真实 DOM 上,从而减少重排和重绘的次数。

5. 使用 CSS 动画

❓ 面试题:CSS 动画如何减少重排和重绘?

CSS 动画可以利用 GPU 加速,不会触发重排,只会触发重绘。

📝 CSS 动画示例

css 复制代码
/* ❌ 不推荐:使用 JavaScript 动画,会触发重排 */
.element {
    position: absolute;
    left: 0;
    top: 0;
}

/* ✅ 推荐:使用 CSS 动画,GPU加速 */
.element {
    transform: translateX(0);
    transition: transform 0.3s ease;
}

.element:hover {
    transform: translateX(100px);
}

/* ✅ 推荐:使用 CSS 动画,GPU加速 */
@keyframes slideIn {
    from {
        transform: translateX(-100%);
    }
    to {
        transform: translateX(0);
    }
}

.element {
    animation: slideIn 0.3s ease;
}

💡 原理说明

CSS 动画(特别是使用 transform 和 opacity 的动画)可以利用 GPU 加速,不会触发重排,只会触发重绘,从而提高性能。

6. 使用 will-change 属性

❓ 面试题:will-change 属性如何优化性能?

will-change 属性可以提前告知浏览器哪些属性会发生变化,让浏览器提前做好优化准备。

📝 will-change 属性示例

css 复制代码
/* ❌ 不推荐:滥用 will-change */
* {
    will-change: transform, opacity;
}

/* ✅ 推荐:只在需要时使用 */
.element {
    will-change: transform;
}

.element:hover {
    transform: translateX(100px);
}

/* ✅ 推荐:动画结束后移除 will-change */
.element {
    will-change: transform;
    animation: slideIn 0.3s ease;
}

.element.animation-end {
    will-change: auto;
}

💡 原理说明

will-change 属性可以提前告知浏览器哪些属性会发生变化,让浏览器提前做好优化准备(如创建新的图层)。但是,滥用 will-change 会导致性能问题,应该只在需要时使用。

7. 使用 requestAnimationFrame

❓ 面试题:requestAnimationFrame 如何优化性能?

requestAnimationFrame 可以根据浏览器的刷新率来执行动画,避免不必要的重绘。

📝 requestAnimationFrame 示例

javascript 复制代码
// ❌ 不推荐:使用 setInterval,可能导致不必要的重绘
function animate() {
    element.style.left = parseInt(element.style.left) + 1 + 'px';
    setInterval(animate, 16);
}

// ✅ 推荐:使用 requestAnimationFrame,根据浏览器刷新率执行
function animate() {
    element.style.transform = `translateX(${parseInt(element.style.transform.match(/\d+/)[0]) + 1}px)`;
    requestAnimationFrame(animate);
}
requestAnimationFrame(animate);

💡 原理说明

requestAnimationFrame 可以根据浏览器的刷新率(通常是 60Hz)来执行动画,避免不必要的重绘。当页面不可见时,requestAnimationFrame 会自动暂停,从而节省性能。

8. 使用离屏渲染

❓ 面试题:什么是离屏渲染?如何使用?

离屏渲染是指在内存中进行复杂的绘制操作,然后将结果绘制到页面上,从而减少重排和重绘的次数。

📝 离屏渲染示例

javascript 复制代码
// ❌ 不推荐:直接在页面上绘制,会触发多次重绘
function drawComplexShape() {
    const canvas = document.getElementById('canvas');
    const ctx = canvas.getContext('2d');
    ctx.fillStyle = 'red';
    ctx.fillRect(0, 0, 100, 100);
    ctx.fillStyle = 'blue';
    ctx.fillRect(100, 0, 100, 100);
    ctx.fillStyle = 'green';
    ctx.fillRect(0, 100, 100, 100);
}

// ✅ 推荐:在内存中绘制,然后一次性绘制到页面上
function drawComplexShape() {
    const canvas = document.getElementById('canvas');
    const ctx = canvas.getContext('2d');
    const offscreenCanvas = document.createElement('canvas');
    const offscreenCtx = offscreenCanvas.getContext('2d');
    offscreenCtx.fillStyle = 'red';
    offscreenCtx.fillRect(0, 0, 100, 100);
    offscreenCtx.fillStyle = 'blue';
    offscreenCtx.fillRect(100, 0, 100, 100);
    offscreenCtx.fillStyle = 'green';
    offscreenCtx.fillRect(0, 100, 100, 100);
    ctx.drawImage(offscreenCanvas, 0, 0);
}

💡 原理说明

离屏渲染是指在内存中进行复杂的绘制操作,然后将结果绘制到页面上,从而减少重排和重绘的次数。这种方法特别适合复杂的动画和图形绘制。

五、实践经验总结

1. 给面试者的建议

🎯 给面试者的建议

  1. 理解原理:深入理解浏览器渲染机制,知道重排和重绘的区别
  2. 掌握策略:掌握减少重排和重绘的各种策略
  3. 实践应用:在实际项目中应用这些优化策略
  4. 性能测试:使用 Chrome DevTools 进行性能测试和优化
  5. 持续优化:持续关注性能优化,不断改进代码
  6. 分享经验:和团队分享性能优化的经验和技巧
  7. 关注新特性:关注浏览器的新特性和优化方案

2. 面试中的回答策略

🎯 面试中的回答策略

  1. 先说概念:先解释什么是重排和重绘,以及它们的区别
  2. 再说触发条件:说明哪些操作会触发重排和重绘
  3. 然后说优化策略:详细说明减少重排和重绘的各种策略
  4. 最后说实践经验:分享在实际项目中如何应用这些优化策略
  5. 结合代码示例:用具体的代码示例说明优化策略的应用

3. 避免的误区

⚠️ 避免的误区

  1. 过度优化:不要过度优化,要根据实际情况选择合适的优化策略
  2. 滥用 will-change:不要滥用 will-change 属性,会导致性能问题
  3. 忽视用户体验:不要为了性能而牺牲用户体验
  4. 不测试性能:不要凭感觉优化,要用工具测试性能
  5. 不关注兼容性:不要忽视浏览器兼容性问题

4. 项目中的实际应用

📝 项目中的实际应用

  1. 列表渲染优化:使用虚拟 DOM 或文档片段批量操作 DOM
  2. 动画优化:使用 CSS 动画或 requestAnimationFrame
  3. 图片懒加载:使用 IntersectionObserver API 实现图片懒加载
  4. 虚拟滚动:对于长列表,使用虚拟滚动技术
  5. 代码分割:使用 Webpack 的代码分割功能,按需加载

💡 总结

减少页面重绘和重排是前端性能优化的重要手段。通过深入理解浏览器渲染机制,掌握各种优化策略,并在实际项目中应用这些策略,可以大大提高页面的性能。在面试中,能够从原理出发,结合具体代码示例,说明优化策略的实际应用,并能分享项目中的实践经验,会让你在面试中脱颖而出。

will-change 跟 conian 可以参考我之前的文章:
优化重排跟重绘,怎么少的了 will-change
使用CSS Contain 优化你的页面(重排和重绘)

感谢阅读!如果您有任何问题或建议,欢迎在评论区留言讨论。
如果你觉得本文对你有帮助,欢迎点赞、收藏、分享,也欢迎关注我,获取更多前端技术干货!

相关推荐
想学后端的前端工程师2 小时前
【前端安全防护实战指南:从XSS到CSRF全面防御】
前端·安全·xss
czlczl200209252 小时前
基于 Spring Boot 权限管理 RBAC 模型
前端·javascript·spring boot
未来之窗软件服务2 小时前
幽冥大陆(六十七) PHP5.x SSL 文字加密—东方仙盟古法结界
服务器·前端·ssl·仙盟创梦ide·东方仙盟
小北方城市网2 小时前
第 10 课:Node.js 后端企业级进阶 —— 任务管理系统后端优化与功能增强(续)
大数据·前端·vue.js·ai·性能优化·node.js
华仔啊2 小时前
JavaScript 有哪些数据类型?它们在内存里是怎么存的?
前端·javascript
我有一棵树2 小时前
淘宝 npm 镜像与 CDN 加速链路解析:不只是 Registry,更是分层静态加速架构
前端·架构·npm
东东的脑洞2 小时前
【面试突击】Kafka 核心面试知识点
面试·职场和发展·kafka
zhousenshan2 小时前
vue3基础知识100问
前端·vue.js
异界蜉蝣2 小时前
Proxy vs Object.defineProperty:Vue3响应式原理的深度革命
开发语言·前端·javascript