前端性能优化常见面试问题汇总
1. 网络加载与传输优化
1.1 核心性能指标 Web Vitals 包含哪些?如何优化?
问题: 请解释 Core Web Vitals 包含哪些指标,以及各自的优化方案。
标准答案:
-
LCP (Largest Contentful Paint,最大内容绘制): 测量加载性能。代表视口内可见的最大内容元素(如图片、视频、块级文本)渲染完成的时间。良好标准为 ≤2.5秒。
- 优化方案:
- 优化服务器响应时间(使用CDN、缓存、优化后端)。
- 消除渲染阻塞资源(延迟加载非关键CSS/JS)。
- 预加载关键资源(
<link rel="preload">)。 - 优化图片和字体(压缩、使用WebP/AVIF格式、设置
fetchpriority="high")。 - 使用服务端渲染(SSR)或静态站点生成(SSG)。
- 优化方案:
-
FID (First Input Delay,首次输入延迟): 测量交互性。代表从用户首次与页面交互(如点击链接、按钮)到浏览器实际能够响应该交互的时间。良好标准为 ≤100毫秒。注意: 现已被 INP (Interaction to Next Paint) 取代作为核心指标。
- 优化方案(对FID及INP均有效):
- 拆分长任务(将长时间运行的JavaScript拆分为小任务,使用
setTimeout或requestIdleCallback)。 - 优化第三方脚本(异步加载、延迟加载、使用
rel="noopener"或rel="nofollow"的<iframe>沙箱)。 - 使用Web Worker处理复杂计算。
- 减少JavaScript执行时间(代码分割、摇树优化)。
- 拆分长任务(将长时间运行的JavaScript拆分为小任务,使用
- 优化方案(对FID及INP均有效):
-
CLS (Cumulative Layout Shift,累积布局偏移): 测量视觉稳定性。代表页面生命周期内,所有意外布局偏移的累计分数。良好标准为 ≤0.1。
- 优化方案:
- 始终为图片和视频元素设置尺寸属性(
width和height)。 - 不要在现有内容上方插入动态内容(例如,使用占位符或预留空间)。
- 对广告、嵌入内容或异步加载的组件保留空间。
- 使用
transform动画代替影响布局的属性(如top,left,margin)。
- 始终为图片和视频元素设置尺寸属性(
- 优化方案:
延伸拓展:
- INP (Interaction to Next Paint): 衡量页面所有用户交互(点击、触摸、键盘输入)的响应延迟,取其中最慢的响应时间(通常排除长尾值)。优化重点是确保事件处理程序快速执行,并尽快提供下一帧的视觉反馈。
1.2 如何减少HTTP请求数?
问题: 有哪些手段可以有效减少页面加载时的HTTP请求数量?
标准答案:
- 合并文件: 合并多个小CSS/JS文件为一个,减少请求数(需权衡缓存粒度)。
- 使用CSS Sprites / 雪碧图: 将多个小图标合并为一张大图,通过
background-position定位展示。 - 使用字体图标(Icon Font)或SVG Sprites: 用单一声明文件提供大量矢量图标。
- 内联关键资源(Critical CSS/JS): 将首屏渲染所需的关键CSS直接内嵌在HTML的
<style>标签中,避免阻塞渲染的请求。 - 使用Data URLs: 将小图片或字体转换为Base64编码,直接嵌入CSS或HTML(增大会增大文件体积,需权衡)。
- 懒加载非关键资源: 图片、视频、非首屏组件等使用懒加载,初始时不请求。
- 利用HTTP/2或HTTP/3: HTTP/2的多路复用特性使得多个请求的成本降低,但合并策略需重新评估(小文件合并可能反而不利)。
1.3 请详细说明浏览器缓存策略。
问题: 请阐述强缓存和协商缓存的原理、相关HTTP头部及应用场景。
标准答案:
1. 强缓存: 浏览器直接使用本地缓存副本,不发送请求到服务器。通过以下两种响应头控制:
Cache-Control(HTTP/1.1优先级更高):常用指令max-age=31536000(缓存一年)、public、private、no-cache(跳过强缓存,直接进入协商缓存)、no-store(完全不缓存)。Expires(HTTP/1.0):指定一个过期的绝对时间。受客户端本地时间影响,优先级低于Cache-Control。
2. 协商缓存(对比缓存): 浏览器发送请求询问服务器资源是否过期,由服务器决定是否使用缓存。若未变,则返回304状态码,使用本地缓存。
- Last-Modified / If-Modified-Since: 基于文件修改时间。
- 服务器响应头:
Last-Modified: GMT。 - 浏览器后续请求头:
If-Modified-Since: GMT。 - 缺点:精确到秒级;文件可能仅修改时间变化但内容未变。
- 服务器响应头:
- ETag / If-None-Match: 基于文件内容生成的唯一标识符,更精确。
- 服务器响应头:
ETag: "W/50b1a1d4f-775c"。 - 浏览器后续请求头:
If-None-Match: "W/50b1a1d4f-775c"。 - 优先级高于
Last-Modified。
- 服务器响应头:
应用策略(最佳实践):
- 哈希指纹的文件(如
main.a1b2c3d4.js): 设置Cache-Control: max-age=31536000, immutable。内容变化文件名即变,可永久缓存。 - 非哈希的静态资源: 设置
Cache-Control: no-cache或较短max-age,配合ETag使用协商缓存。 - HTML文件: 通常设置
Cache-Control: no-cache,确保用户总能获取到最新的页面骨架。 - API接口数据: 根据数据更新频率,可酌情使用
max-age或no-cache。
延伸拓展: Service Worker 缓存是更强大的编程式缓存,可实现"离线优先"等高级策略。
2. 渲染过程与代码优化
2.1 描述浏览器关键渲染路径并给出优化建议。
问题: 从收到HTML、CSS、JS文件到渲染出像素,浏览器经历了什么?如何优化?
标准答案:
关键渲染路径步骤:
- 构建DOM树: 解析HTML,生成DOM树。
- 构建CSSOM树: 解析CSS(包括内联、外联、样式标签),生成CSSOM树。
- 合并成渲染树(Render Tree): 合并DOM和CSSOM,排除不可见节点(如
<head>,display: none)。 - 布局(Layout/Reflow): 计算渲染树每个节点的确切位置和大小(几何信息)。
- 绘制(Paint): 将布局计算的像素点填充为颜色、图片、边框等。
- 合成(Composite): 将各绘制层合并,显示到屏幕上。
阻塞关系与优化:
- CSS是渲染阻塞资源: 浏览器会阻塞渲染,直到CSSOM构建完成。需尽早加载。
- 优化: 使用
<link media>媒体查询对非首屏CSS进行异步加载;内联关键CSS。
- 优化: 使用
- JavaScript是解析器阻塞资源: 默认情况下,遇到
<script>标签会暂停HTML解析,去下载并执行JS,因为JS可能修改DOM/CSSOM。- 优化:
- 将
<script>标签放在<body>底部。 - 使用
async属性:脚本异步下载,下载完后立即执行,不保证顺序,适用于独立无依赖的脚本(如分析统计)。 - 使用
defer属性:脚本异步下载,但在HTML解析完成后、DOMContentLoaded事件前按顺序执行。 - 动态创建
<script>标签插入DOM。
- 将
- 优化:
延伸拓展: 现代浏览器有 预加载扫描器(Preload Scanner),会在主解析器阻塞时,提前扫描文档以发现并开始下载关键资源(如图片、CSS、JS)。
2.2 什么是重排和重绘?如何避免?
问题: 解释重排和重绘的区别,并列举触发场景和优化方法。
标准答案:
- 重排(Reflow/Layout): 当元素的几何属性(位置、尺寸)发生变化,影响页面布局时,浏览器需要重新计算所有受影响元素的几何属性,并更新渲染树。代价高昂 。
- 触发场景: 修改宽高、边距、定位(
top/left)、字体大小、窗口调整、读写offsetHeight/scrollTop等布局属性。
- 触发场景: 修改宽高、边距、定位(
- 重绘(Repaint/Paint): 当元素的外观属性(颜色、背景、可见性等)发生变化,但不影响布局时,浏览器只需重新绘制受影响区域。代价比重排低,但仍需优化。
优化原则:减少重排范围、次数和频率。
-
样式集中改变: 使用
class一次性修改样式,或使用cssText属性。 -
分离读写操作: 避免连续读取和修改布局属性。利用浏览器渲染队列机制,将读取操作集中。
javascript// 不好 el.style.width = el.offsetWidth + 10 + 'px'; el.style.height = el.offsetHeight + 10 + 'px'; // 好:先读后写 const width = el.offsetWidth; const height = el.offsetHeight; el.style.width = width + 10 + 'px'; el.style.height = height + 10 + 'px'; -
使用
documentFragment或display: none进行批量DOM操作。 -
复杂动画元素使用绝对定位(
position: absolute/fixed): 使其脱离文档流,重排影响范围减小。 -
优先使用CSS3动画(
transform,opacity): 这些属性由合成器(Compositor)线程处理,跳过布局和绘制,只触发合成,效率极高。 -
避免使用
table布局: 一个小改动可能导致整个表格重排。
2.3 如何进行代码分割(Code Splitting)?
问题: 代码分割的目的是什么?在前端工程中有哪些实现方式?
标准答案:
目的: 将代码拆分成多个小块(chunks),实现按需加载或并行加载,减少初始加载体积,提升首屏速度。
实现方式:
-
动态
import()语法(推荐): 现代ES模块标准。javascript// 组件级别分割 const LazyComponent = React.lazy(() => import('./LazyComponent')); // 路由级别分割(配合React Router) const About = React.lazy(() => import('./routes/About')); -
Webpack 配置:
-
入口点分割(Entry Points): 配置多入口。
-
防止重复(Split Chunks): 使用
optimization.splitChunks提取公共依赖和第三方库。javascriptoptimization: { splitChunks: { chunks: 'all', // 对所有chunk进行拆分 cacheGroups: { vendors: { test: /[\\/]node_modules[\\/]/, name: 'vendors', }, commons: { name: 'commons', minChunks: 2, // 至少被2个入口引用 } } } }
-
-
框架内置:
- React:
React.lazy()+Suspense。 - Vue Router: 定义路由时使用
component: () => import('...')。 - Next.js: 基于页面的自动代码分割。
- React:
延伸拓展: 使用 预获取(Prefetching) 进一步提高用户体验。Webpack支持魔法注释:import(/* webpackPrefetch: true */ './module')。浏览器会在空闲时间预加载该模块,当用户需要时可能已缓存。
3. 图片与资源优化
3.1 图片优化有哪些综合方案?
问题: 针对网站图片加载性能,有哪些从格式选择到加载策略的优化手段?
标准答案:
-
选择合适的格式:
- JPEG: 适用于色彩丰富、有渐变的生活照、海报。可调整压缩比。
- PNG: 适用于需要透明度的图标、图形。PNG-8(256色)和PNG-24。
- WebP: 现代格式,在同等质量下比JPEG和PNG体积小很多。支持有损/无损、透明。兼容性已很好,应作为首选。
- AVIF: 下一代格式,压缩率比WebP更高,但编解码性能和要求也更高。
- SVG: 矢量格式,适用于图标、徽标、简单图形。无限缩放且体积小。
-
压缩与优化:
- 使用工具(如Squoosh、TinyPNG、imagemin)进行无损或有损压缩。
- 在构建流程中集成自动化图片压缩插件(如
image-webpack-loader)。
-
响应式图片:
- 使用
srcset和sizes属性,让浏览器根据屏幕尺寸和分辨率选择合适图片。 - 使用
<picture>元素进行艺术指导(Art Direction)或格式适配(提供WebP和JPEG回退)。
html<img srcset="img-320w.jpg 320w, img-480w.jpg 480w, img-800w.jpg 800w" sizes="(max-width: 320px) 280px, (max-width: 480px) 440px, 800px" src="img-800w.jpg" alt="示例"> - 使用
-
懒加载:
- 使用原生属性
loading="lazy"(兼容性良好)。 - 第三方库(如
lozad.js)作为备选。
- 使用原生属性
-
CDN加速: 将图片放在CDN上,并利用其图片处理服务(如裁剪、缩放、格式转换)。
3.2 如何实现图片懒加载?
问题: 描述图片懒加载的原理和具体实现方式。
标准答案:
原理: 只加载可视区域(或即将进入可视区域)内的图片,当用户滚动时,再加载后续图片。
实现方式:
-
原生实现(最简单):
<img loading="lazy" src="image.jpg" alt="...">。浏览器自动处理。 -
基于
Intersection Observer API(现代推荐):javascriptconst images = document.querySelectorAll('img[data-src]'); const observer = new IntersectionObserver((entries) => { entries.forEach(entry => { if (entry.isIntersecting) { const img = entry.target; img.src = img.dataset.src; img.removeAttribute('data-src'); observer.unobserve(img); } }); }); images.forEach(img => observer.observe(img));HTML中:
<img data-src="real-image.jpg" src="placeholder.jpg" alt="..."> -
传统滚动监听+计算位置: 监听
scroll事件,计算图片是否在视口内。需注意防抖/节流 ,性能不如IntersectionObserver。
延伸拓展: 可结合占位符(纯色、低质量图像占位符LQIP、模糊缩略图)提升体验。对于背景图片,通常需要通过切换CSS类或内联样式来实现懒加载。
4. 存储与缓存策略进阶
4.1 Service Worker 如何进行性能优化?
问题: Service Worker 除了实现离线功能,在性能优化方面有哪些应用?
标准答案:
Service Worker 是一个独立于主线程的脚本,充当网络代理,可以拦截和处理网络请求。
性能优化应用:
- 智能缓存策略(核心):
- Cache First, Network Fallback: 优先从缓存返回,适合不常变的静态资源。
- Network First, Cache Fallback: 优先请求网络,失败再用缓存,适合需要一定实时性的数据。
- Stale-While-Revalidate: 立即从缓存返回旧数据,同时后台发起网络请求更新缓存,适用于可接受短时旧数据的场景(如用户头像、文章列表)。
- 预缓存关键资源: 在
install事件中,将App Shell(应用外壳)所需的核心静态资源提前缓存。 - 预获取和预加载: 根据用户行为预测,在空闲时预获取下一步可能需要的资源(例如,预获取文章详情页的CSS/JS)。
- 后台同步(Background Sync): 确保用户离线时的操作(如表单提交)能在网络恢复后同步到服务器,提升用户体验。
- 推送通知(Push Notifications): 提高用户参与度,间接提升应用"活性"。
延伸拓展: 使用 Workbox 库可以极大地简化Service Worker的编写,它封装了常见的缓存策略和运行时缓存。
4.2 请比较 localStorage、sessionStorage、Cookie、IndexedDB。
问题: 请从前端数据存储的角度,对比这几种存储方式的特性、容量和适用场景。
标准答案:
| 特性 | Cookie | localStorage | sessionStorage | IndexedDB |
|---|---|---|---|---|
| 生命周期 | 可设置过期时间,或浏览器会话结束 | 永久存储,除非手动删除 | 当前浏览器标签页关闭即清除 | 永久存储,除非手动删除 |
| 存储容量 | 约 4KB | 约 5-10MB(因浏览器而异) | 同localStorage | 巨大(通常≥250MB) |
| 与服务端通信 | 每次HTTP请求自动携带在请求头中 | 仅客户端 | 仅客户端 | 仅客户端 |
| API易用性 | 字符串,需自行解析 | 简单的 key-value (字符串) API |
同localStorage | 异步API,较复杂,支持事务、索引 |
| 适用场景 | 身份认证(Token)、少量服务端所需信息 | 持久化本地偏好设置、不敏感数据缓存 | 单次会话的临时数据(如表单步骤状态) | 大量结构化数据、离线应用数据、二进制数据(如图片) |
延伸拓展: 对于更复杂的客户端数据库需求,可以考虑基于IndexedDB的封装库,如 localForage (提供类似localStorage的Promise API)、Dexie.js。
5. 性能度量与监控
5.1 如何进行性能监控和数据上报?
问题: 一个完整的前端性能监控体系包含哪些方面?如何收集和上报数据?
标准答案:
监控体系包含:
- 指标收集:
- 核心Web指标 (LCP, FID/INP, CLS): 使用
web-vitals库标准化获取。 - 自定义性能指标: 首屏时间、FP/FCP、资源加载时间、AJAX请求耗时、页面错误率。
- 用户行为数据: PV/UV、页面停留时间、操作路径。
- 核心Web指标 (LCP, FID/INP, CLS): 使用
- 数据上报:
-
上报时机:
window.onload或setTimeout延时上报,避免阻塞。- 使用
sendBeaconAPI 在页面卸载时可靠上报。 - 批量上报,合并请求。
-
上报方式:
- Image Beacon: 创建一个1x1的GIF图片,将数据拼在URL参数中。简单、跨域友好、无需响应。
javascriptnew Image().src = `https://monitor.com/collect?data=${encodeURIComponent(data)}`;navigator.sendBeacon(): 专门为日志上报设计,异步、可靠,即使页面关闭也会尽力发送。
javascriptnavigator.sendBeacon(url, JSON.stringify(data));- AJAX/Fetch: 灵活性最高,但需处理跨域和页面卸载丢失问题。
-
- 数据存储与分析: 后端接收日志,存入时序数据库或大数据平台,进行聚合分析、可视化展示和告警。
延伸拓展: 使用 PerformanceObserver API 可以更精准、无侵入地监控性能条目(如长任务、布局偏移等)。同时,应建立性能基线 和版本对比机制,确保迭代不会导致性能衰退。
5.2 核心Web性能指标计算口径与实现
下表整理了三大核心指标(LCP、CLS、INP)的核心计算逻辑,以及 web-vitals 库如何对其进行封装测量:
| 指标 | 核心计算逻辑 | web-vitals 库的实现方式 |
|---|---|---|
| LCP | 浏览器持续报告新的"最大内容元素"。最终LCP值 是页面开始加载 (navigationStart) 到最后一次报告的"最大内容元素"的渲染时间。用户滚动或输入前,最大元素可能变化。 |
库使用 PerformanceObserver 监听 largest-contentful-paint 类型的性能条目,并遵循上述规则确定最终值后回调。 |
| CLS | 每次布局偏移的分数 = 影响分数 × 距离分数 。影响分数 指视口中不稳定元素 影响的总面积占比。距离分数 指不稳定元素在帧间移动的最大距离 (视口尺寸为基准)。页面生命周期内的CLS 是所有会话窗口 内单次偏移分数的累计值。 | 库监听 layout-shift 条目,按会话窗口(无用户输入≥1秒或≥5000ms)分组累加,并报告整个页面生命周期内的最大值。 |
| INP | 测量用户交互 (如点击、键盘输入)到浏览器绘制下一帧 的总延迟 。延迟包含:输入延迟 (主线程繁忙等待时间)、处理时间 (事件处理器执行时间)、呈现延迟 (浏览器计算样式、布局、绘制的时间)。页面的INP值 是所有交互中最高的延迟(通常取第98百分位以避免极端值)。 | 库监听 event 等性能条目,精确计算每次交互的完整延迟,并持续评估以报告最具代表性的高百分位数值。 |