秋招面试---性能优化(良子大胃袋)

在秋招面试中经常会遇到面试官问:"你的项目中有哪些性能优化、你知道的性能优化有哪些?" 本篇文章小编将给大家讲讲性能优化这个话题(如有没涉及到的欢迎在评论区补充)


一、渲染性能基石:理解重排(Reflow)与重绘(Repaint)

浏览器将 HTML、CSS 和 JavaScript 转化为用户可见的页面,需要经过 解析 -> 样式计算 -> 布局(重排) -> 绘制(重绘) -> 合成 的流程。其中,重排重绘是性能消耗的重灾区。

1. 核心概念辨析

概念 触发条件 性能开销 关系
重排 (Reflow/ Layout) 元素的几何属性 改变(width, height, margin, padding, display: none, offsetTop 读取等)。 极高 重排必然导致后续的重绘。
重绘 (Repaint) 元素的外观 改变,但不影响布局(color, background-color, visibility)。 中等 重绘不会导致重排。

关键认知:避免不必要的重排是性能优化的首要任务。

2. 优化策略与实战

策略 1:批量修改 DOM 属性

javascript 复制代码
// ❌ 反模式:连续修改可能触发多次重排
const el = document.getElementById('box');
el.style.width = '200px';   // 可能触发重排
el.style.height = '150px';  // 可能再次触发重排
el.style.margin = '20px';   // 可能再次触发重排

// ✅ 推荐 1:使用 `cssText` 批量设置
el.style.cssText = 'width: 200px; height: 150px; margin: 20px;';

// ✅ 推荐 2:通过切换 CSS 类名(最佳)
el.className = 'new-style'; // 样式定义在 CSS 文件中

原理:CSS 类名变更由浏览器在样式计算阶段统一处理,通常只会触发一次重排。

策略 2:使用文档片段(DocumentFragment)

javascript 复制代码
// ✅ 批量添加子元素
const fragment = document.createDocumentFragment();
for (let i = 0; i < 1000; i++) {
  const child = document.createElement('div');
  child.textContent = `Item ${i}`;
  fragment.appendChild(child); // 在内存中操作,无重排
}
// 将片段一次性插入 DOM,触发一次重排
document.getElementById('container').appendChild(fragment);

策略 3:脱离文档流操作("下线"优化)

javascript 复制代码
const el = document.getElementById('complex-el');

// 1. 脱离文档流,避免操作时触发重排
el.style.display = 'none'; // 或 position: absolute

// 2. 在"下线"状态下进行大量复杂操作
for (let i = 0; i < 100; i++) {
  el.style.left = i * 10 + 'px';
  el.style.top = i * 5 + 'px';
  // ... 其他操作
}

// 3. 操作完成,重新"上线"
el.style.display = 'block';
// 此时触发一次重排和重绘

策略 4:缓存布局信息

javascript 复制代码
// ❌ 危险:每次读取 offsetTop 都可能强制触发重排
for (let i = 0; i < 100; i++) {
  el.style.top = el.offsetTop + 1 + 'px'; // 每次都重排!
}

// ✅ 安全:缓存初始值
const initialTop = el.offsetTop; // 读取一次,触发一次重排
for (let i = 0; i < 100; i++) {
  el.style.top = initialTop + i + 'px'; // 仅修改,不读取布局
}

策略 5:使用 transform 代替位置属性

javascript 复制代码
// ❌ 触发重排(改变几何属性)
el.style.left = '100px';

// ✅ 仅触发重绘(或合成),性能极佳
el.style.transform = 'translateX(100px)';

原理transform 属于合成(Compositing)层操作,通常由 GPU 加速,不触发主线程的重排和重绘。


📌 面试环节:重排与重绘

Q1: 请解释什么是重排(Reflow)和重绘(Repaint)?它们有什么区别和联系?

A: 重排是当 DOM 元素的几何属性(如宽高、位置)发生变化时,浏览器需要重新计算元素的布局和位置,这个过程开销很大。重绘是当元素的外观(如颜色、背景)改变但不影响布局时,浏览器需要重新绘制该元素。重排一定会导致重绘,因为布局改变后外观也需要更新;但重绘不一定导致重排。

Q2: 如何避免或减少重排?请举例说明。

A: 主要策略有:

  1. 批量操作 :使用 cssText 或切换 className 一次性修改样式。
  2. 文档片段 :使用 DocumentFragment 批量添加节点。
  3. 脱离文档流 :操作前将元素 display: noneposition: absolute,操作完再恢复。
  4. 缓存布局信息 :避免在循环中读取 offsetTop 等布局属性。
  5. 使用 transform :用 transform 代替 left/top 等位置属性进行动画。

Q3: 为什么 transformleft/top 性能更好?

A : left/top 会修改元素的几何属性,触发重排和重绘。而 transform 属于合成层操作,通常由 GPU 处理,不触发主线程的重排和重绘,只可能触发合成(Compositing),性能开销小得多。


二、资源加载优化:减少等待时间

资源加载是影响首屏时间(FCP, LCP)的关键。

1. 图片优化

  • 懒加载 (Lazy Loading)loading="lazy" 或 Intersection Observer API,延迟加载视口外的图片。
  • 格式优化 :优先使用 WebPAVIF 格式,相比 JPEG/PNG 可减少 25%-50% 体积。
  • 图标字体/ SVG Sprite:合并小图标,减少 HTTP 请求数。

2. 代码分割 (Code Splitting)

  • 路由级懒加载

    javascript 复制代码
    // React + React Router
    const Home = React.lazy(() => import('./Home'));
    <Suspense fallback={<Loading />}>
      <Home />
    </Suspense>
  • 组件级分割:将非首屏组件拆分。

  • 第三方库分割 :将 lodash, moment 等大库单独打包,利用长期缓存。

3. 资源预加载与预解析

html 复制代码
<!-- 预加载关键资源(高优先级) -->
<link rel="preload" href="critical.css" as="style">
<link rel="preload" href="hero-image.webp" as="image">

<!-- 预解析 DNS -->
<link rel="dns-prefetch" href="//api.example.com">

<!-- 预取未来可能用到的资源(低优先级) -->
<link rel="prefetch" href="/next-page.js">

4. 脚本加载策略

属性 行为 适用场景
async 下载不阻塞解析,下载完立即执行,执行时阻塞解析。 独立脚本,如统计代码。
defer 下载不阻塞解析,DOM 解析完成后DOMContentLoaded 前执行。 依赖 DOM 的脚本,如主应用逻辑。
默认 阻塞 HTML 解析,直到脚本下载并执行完。 极少数关键内联脚本。

📌 面试环节:资源加载

Q1: asyncdefer 的区别是什么?

A : async 脚本下载完成后会立即执行 ,执行时会阻塞 HTML 解析,且执行顺序不确定。defer 脚本下载不阻塞解析,会在 DOM 解析完成后DOMContentLoaded 事件触发前按顺序执行defer 适合需要操作 DOM 的脚本。

Q2: 如何优化图片加载?

A:

  1. 使用 WebP/AVIF 格式。
  2. 实施懒加载
  3. 对于小图标,使用图标字体SVG Sprite
  4. 使用 srcsetsizes 实现响应式图片
  5. 合理设置图片尺寸,避免过大。

Q3: 什么是代码分割(Code Splitting)?它有什么好处?

A: 代码分割是将应用代码拆分成多个小块的技术。好处包括:

  1. 减少首屏加载时间:只加载当前页面需要的代码。
  2. 利用浏览器缓存:公共库(如 React)可以长期缓存,业务代码更新时无需重新下载。
  3. 并行加载:多个小文件可以并行下载,提升速度。

三、JavaScript 执行优化:解放主线程

JS 执行在主线程,会阻塞渲染。

1. 防抖 (Debounce) 与 节流 (Throttle)

  • 防抖 :事件触发后,等待 n 秒无新事件,才执行一次。适用于搜索框输入。
  • 节流 :事件触发后,在 n 秒内最多执行一次。适用于 scrollresize 事件。

2. Web Workers

将复杂计算(如大数据处理、图像编码)移出主线程,在后台线程执行,避免阻塞 UI。

javascript 复制代码
const worker = new Worker('heavy-calc.js');
worker.postMessage(data);
worker.onmessage = (e) => { /* 处理结果 */ };

3. requestAnimationFrame (rAF)

用于动画,确保在浏览器下一次重绘前执行,实现 60fps 流畅动画。

javascript 复制代码
function animate() {
  // 更新动画状态
  requestAnimationFrame(animate); // 下一帧继续
}
requestAnimationFrame(animate);

4. requestIdleCallback

在浏览器空闲时期执行低优先级任务(如预加载、日志上报),避免影响关键渲染。

javascript 复制代码
requestIdleCallback(() => {
  // 执行非关键任务
});

📌 面试环节:JS 执行

Q1: 请手写一个防抖函数和一个节流函数。

A:

javascript 复制代码
// 防抖
function debounce(func, delay) {
  let timer = null;
  return function (...args) {
    clearTimeout(timer);
    timer = setTimeout(() => func.apply(this, args), delay);
  };
}

// 节流
function throttle(func, delay) {
  let lastTime = 0;
  return function (...args) {
    const now = Date.now();
    if (now - lastTime > delay) {
      func.apply(this, args);
      lastTime = now;
    }
  };
}

Q2: Web Workers 是什么?它解决了什么问题?

A : Web Workers 允许在后台线程中运行 JavaScript 代码。它解决了长时间运行的 JS 任务阻塞主线程的问题,从而避免了 UI 冻结,提升了应用的响应性和流畅度。注意:Worker 线程不能直接操作 DOM。

Q3: requestAnimationFrame 有什么优势?

A : rAF 由浏览器统一调度,能确保回调函数在下一次重绘前执行,从而与屏幕刷新率(通常 60Hz)同步,避免画面撕裂和卡顿,实现流畅动画。它还具备节流特性,并在标签页不可见时自动暂停,节省资源。


四、缓存策略:减少重复请求

1. HTTP 缓存

机制 请求 响应头 验证方式
强缓存 无请求 Cache-Control: max-age=3600 直接使用本地缓存。
协商缓存 发起请求 ETag / Last-Modified 服务端比对 If-None-Match / If-Modified-Since,返回 304 Not Modified

2. 浏览器存储

  • localStorage:持久化存储,容量大(~5-10MB),适合存储用户偏好、离线数据。
  • sessionStorage:会话级存储,关闭标签页后清除。
  • Cookie:自动随请求发送,用于身份认证(HttpOnly 安全),但体积小(~4KB)。

📌 面试环节:缓存

Q1: 强缓存和协商缓存的区别?

A : 强缓存(Cache-Control, Expires)在有效期内直接使用本地缓存,不向服务器发请求 。协商缓存(ETag/If-None-Match, Last-Modified/If-Modified-Since)会向服务器发起请求,服务器根据资源是否变化决定返回 304(使用缓存)还是 200(返回新资源)。

Q2: ETagLast-Modified 有什么优劣?

A : Last-Modified 基于时间戳,精度为秒,如果文件在 1 秒内被修改多次,可能检测不到变化。ETag 是文件内容的哈希值(如 MD5),能精确检测任何内容变化。ETag 更精确,但生成开销稍大。

Q3: localStorageCookie 的主要区别?

A:

  • 大小localStorage (~5-10MB) 远大于 Cookie (~4KB)。
  • 发送localStorage 不会自动随 HTTP 请求发送;Cookie 会自动发送,用于身份认证。
  • 作用域localStorage 严格按同源策略;Cookie 可通过 DomainPath 设置作用域。
  • 安全性Cookie 可设置 HttpOnly(防 XSS)和 Secure(仅 HTTPS)。

五、网络与传输优化

1. CDN (Content Delivery Network)

  • 将静态资源(JS, CSS, 图片, 字体)分发到全球边缘节点。
  • 用户就近访问,降低延迟,减轻源服务器压力。

2. 压缩

  • Gzip/Brotli:对文本资源(HTML, CSS, JS)进行压缩,通常可减少 60%-80% 体积。

3. HTTP/2

  • 多路复用:单个连接上并行传输多个请求/响应,解决 HTTP/1.1 的队头阻塞。
  • 头部压缩:减少请求头体积。
  • 服务器推送:服务器可主动推送客户端可能需要的资源(但需谨慎使用)。

4. DNS 预解析

提前解析未来可能用到的域名的 IP 地址,减少 DNS 查询延迟。


📌 面试环节:网络

Q1: CDN 的工作原理是什么?

A: CDN 通过在全球部署边缘节点服务器,将源站的静态资源缓存到离用户最近的节点。用户请求时,DNS 解析到最近的 CDN 节点,节点直接返回缓存资源,大幅降低延迟和源站压力。

Q2: HTTP/2 相比 HTTP/1.1 有哪些改进?

A: 主要改进:

  1. 二进制分帧:数据以二进制帧传输,更高效。
  2. 多路复用:一个连接上可并行处理多个请求/响应,彻底解决队头阻塞。
  3. 头部压缩:使用 HPACK 算法压缩头部,减少开销。
  4. 服务器推送:服务器可主动推送资源。

六、首屏性能优化:提升用户体验

1. 服务端渲染 (SSR)

  • 原理:在服务器端生成完整的 HTML 字符串,直接发送给浏览器。
  • 优势:首屏内容立即可见(FCP/LCP 极佳),SEO 友好。
  • 框架:Next.js (React), Nuxt.js (Vue)。

2. 骨架屏 (Skeleton Screen)

  • 在数据加载期间,显示一个"骨架"占位符(灰色块),让用户感知到内容正在加载,减少"白屏"焦虑。

3. 关键渲染路径优化

  • 最小化关键 CSS/JS:只加载首屏必需的代码。
  • 内联关键 CSS:避免关键 CSS 的网络请求。

📌 面试环节:首屏优化

Q1: SSR 和 CSR(客户端渲染)的区别?SSR 的优势是什么?

A : CSR 在浏览器下载 JS 后才开始渲染页面,首屏时间长,SEO 差。SSR 在服务器端就完成了 HTML 渲染,浏览器直接显示内容,首屏速度快,利于 SEO。但 SSR 会增加服务器压力。

Q2: 什么是核心 Web 指标(Core Web Vitals)?

A: Google 定义的关键用户体验指标:

  • LCP (Largest Contentful Paint):最大内容绘制时间(< 2.5s 为好)。
  • FID (First Input Delay):首次输入延迟(< 100ms 为好)。
  • CLS (Cumulative Layout Shift):累积布局偏移(< 0.1 为好)。

七、性能监控与测试

1. Chrome DevTools

  • Performance 面板:录制页面加载过程,分析 FPS、CPU、内存、网络、重排重绘等。
  • Lighthouse :自动化审计工具,对性能、可访问性、最佳实践、SEO、PWA 进行评分,并提供详细优化建议(如图片优化、消除渲染屏蔽资源)。

📌 面试环节:监控

Q1: 如何监控前端性能?

A:

  1. 开发阶段:Chrome DevTools Performance 面板、Lighthouse。
  2. 生产阶段:集成 RUM(Real User Monitoring)工具,如 Sentry、Datadog、阿里云 ARMS,监控真实用户的 FCP、LCP、FID、CLS 等指标。

八、总结:构建高性能应用的思维

前端性能优化是一个系统工程 ,需要从渲染、资源、执行、缓存、网络、体验等多个维度协同考虑。没有银弹,只有持续的监控、分析和迭代。核心原则是:

  1. 减少:减少资源体积、请求数量、关键路径长度。
  2. 延迟:延迟非关键资源的加载和执行。
  3. 缓存:充分利用各级缓存。
  4. 并行:利用多路复用、并发加载。
  5. 测量:用数据驱动优化,关注真实用户的核心体验指标。
相关推荐
Serene_Dream12 分钟前
JVM 并发 GC - 三色标记
jvm·面试
xjt_090113 分钟前
基于 Vue 3 构建企业级 Web Components 组件库
前端·javascript·vue.js
我是伪码农25 分钟前
Vue 2.3
前端·javascript·vue.js
夜郎king1 小时前
HTML5 SVG 实现日出日落动画与实时天气可视化
前端·html5·svg 日出日落
辰风沐阳1 小时前
JavaScript 的宏任务和微任务
javascript
夏幻灵2 小时前
HTML5里最常用的十大标签
前端·html·html5
冰暮流星2 小时前
javascript之二重循环练习
开发语言·javascript·数据库
Mr Xu_2 小时前
Vue 3 中 watch 的使用详解:监听响应式数据变化的利器
前端·javascript·vue.js
未来龙皇小蓝2 小时前
RBAC前端架构-01:项目初始化
前端·架构
程序员agions2 小时前
2026年,微前端终于“死“了
前端·状态模式