前端性能优化常见面试问题汇总

前端性能优化常见面试问题汇总

1. 网络加载与传输优化

1.1 核心性能指标 Web Vitals 包含哪些?如何优化?

问题: 请解释 Core Web Vitals 包含哪些指标,以及各自的优化方案。
标准答案:

  • LCP (Largest Contentful Paint,最大内容绘制): 测量加载性能。代表视口内可见的最大内容元素(如图片、视频、块级文本)渲染完成的时间。良好标准为 ≤2.5秒。

    • 优化方案:
      1. 优化服务器响应时间(使用CDN、缓存、优化后端)。
      2. 消除渲染阻塞资源(延迟加载非关键CSS/JS)。
      3. 预加载关键资源(<link rel="preload">)。
      4. 优化图片和字体(压缩、使用WebP/AVIF格式、设置fetchpriority="high")。
      5. 使用服务端渲染(SSR)或静态站点生成(SSG)。
  • FID (First Input Delay,首次输入延迟): 测量交互性。代表从用户首次与页面交互(如点击链接、按钮)到浏览器实际能够响应该交互的时间。良好标准为 ≤100毫秒。注意: 现已被 INP (Interaction to Next Paint) 取代作为核心指标。

    • 优化方案(对FID及INP均有效):
      1. 拆分长任务(将长时间运行的JavaScript拆分为小任务,使用 setTimeoutrequestIdleCallback)。
      2. 优化第三方脚本(异步加载、延迟加载、使用 rel="noopener"rel="nofollow"<iframe>沙箱)。
      3. 使用Web Worker处理复杂计算。
      4. 减少JavaScript执行时间(代码分割、摇树优化)。
  • CLS (Cumulative Layout Shift,累积布局偏移): 测量视觉稳定性。代表页面生命周期内,所有意外布局偏移的累计分数。良好标准为 ≤0.1。

    • 优化方案:
      1. 始终为图片和视频元素设置尺寸属性(widthheight)。
      2. 不要在现有内容上方插入动态内容(例如,使用占位符或预留空间)。
      3. 对广告、嵌入内容或异步加载的组件保留空间。
      4. 使用 transform 动画代替影响布局的属性(如 top, left, margin)。

延伸拓展:

  • INP (Interaction to Next Paint): 衡量页面所有用户交互(点击、触摸、键盘输入)的响应延迟,取其中最慢的响应时间(通常排除长尾值)。优化重点是确保事件处理程序快速执行,并尽快提供下一帧的视觉反馈。

1.2 如何减少HTTP请求数?

问题: 有哪些手段可以有效减少页面加载时的HTTP请求数量?
标准答案:

  1. 合并文件: 合并多个小CSS/JS文件为一个,减少请求数(需权衡缓存粒度)。
  2. 使用CSS Sprites / 雪碧图: 将多个小图标合并为一张大图,通过background-position定位展示。
  3. 使用字体图标(Icon Font)或SVG Sprites: 用单一声明文件提供大量矢量图标。
  4. 内联关键资源(Critical CSS/JS): 将首屏渲染所需的关键CSS直接内嵌在HTML的<style>标签中,避免阻塞渲染的请求。
  5. 使用Data URLs: 将小图片或字体转换为Base64编码,直接嵌入CSS或HTML(增大会增大文件体积,需权衡)。
  6. 懒加载非关键资源: 图片、视频、非首屏组件等使用懒加载,初始时不请求。
  7. 利用HTTP/2或HTTP/3: HTTP/2的多路复用特性使得多个请求的成本降低,但合并策略需重新评估(小文件合并可能反而不利)。

1.3 请详细说明浏览器缓存策略。

问题: 请阐述强缓存和协商缓存的原理、相关HTTP头部及应用场景。
标准答案:
1. 强缓存: 浏览器直接使用本地缓存副本,不发送请求到服务器。通过以下两种响应头控制:

  • Cache-Control (HTTP/1.1优先级更高):常用指令 max-age=31536000(缓存一年)、publicprivateno-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-ageno-cache

延伸拓展: Service Worker 缓存是更强大的编程式缓存,可实现"离线优先"等高级策略。

2. 渲染过程与代码优化

2.1 描述浏览器关键渲染路径并给出优化建议。

问题: 从收到HTML、CSS、JS文件到渲染出像素,浏览器经历了什么?如何优化?
标准答案:
关键渲染路径步骤:

  1. 构建DOM树: 解析HTML,生成DOM树。
  2. 构建CSSOM树: 解析CSS(包括内联、外联、样式标签),生成CSSOM树。
  3. 合并成渲染树(Render Tree): 合并DOM和CSSOM,排除不可见节点(如<head>, display: none)。
  4. 布局(Layout/Reflow): 计算渲染树每个节点的确切位置和大小(几何信息)。
  5. 绘制(Paint): 将布局计算的像素点填充为颜色、图片、边框等。
  6. 合成(Composite): 将各绘制层合并,显示到屏幕上。

阻塞关系与优化:

  • CSS是渲染阻塞资源: 浏览器会阻塞渲染,直到CSSOM构建完成。需尽早加载。
    • 优化: 使用<link media>媒体查询对非首屏CSS进行异步加载;内联关键CSS。
  • JavaScript是解析器阻塞资源: 默认情况下,遇到<script>标签会暂停HTML解析,去下载并执行JS,因为JS可能修改DOM/CSSOM。
    • 优化:
      1. <script>标签放在<body>底部。
      2. 使用 async 属性:脚本异步下载,下载完后立即执行,不保证顺序,适用于独立无依赖的脚本(如分析统计)。
      3. 使用 defer 属性:脚本异步下载,但在HTML解析完成后、DOMContentLoaded事件前按顺序执行。
      4. 动态创建<script>标签插入DOM。

延伸拓展: 现代浏览器有 预加载扫描器(Preload Scanner),会在主解析器阻塞时,提前扫描文档以发现并开始下载关键资源(如图片、CSS、JS)。

2.2 什么是重排和重绘?如何避免?

问题: 解释重排和重绘的区别,并列举触发场景和优化方法。
标准答案:

  • 重排(Reflow/Layout): 当元素的几何属性(位置、尺寸)发生变化,影响页面布局时,浏览器需要重新计算所有受影响元素的几何属性,并更新渲染树。代价高昂
    • 触发场景: 修改宽高、边距、定位(top/left)、字体大小、窗口调整、读写offsetHeight/scrollTop等布局属性。
  • 重绘(Repaint/Paint): 当元素的外观属性(颜色、背景、可见性等)发生变化,但不影响布局时,浏览器只需重新绘制受影响区域。代价比重排低,但仍需优化

优化原则:减少重排范围、次数和频率。

  1. 样式集中改变: 使用class一次性修改样式,或使用cssText属性。

  2. 分离读写操作: 避免连续读取和修改布局属性。利用浏览器渲染队列机制,将读取操作集中。

    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';
  3. 使用documentFragmentdisplay: none进行批量DOM操作。

  4. 复杂动画元素使用绝对定位(position: absolute/fixed): 使其脱离文档流,重排影响范围减小。

  5. 优先使用CSS3动画(transform, opacity): 这些属性由合成器(Compositor)线程处理,跳过布局和绘制,只触发合成,效率极高。

  6. 避免使用table布局: 一个小改动可能导致整个表格重排。

2.3 如何进行代码分割(Code Splitting)?

问题: 代码分割的目的是什么?在前端工程中有哪些实现方式?
标准答案:
目的: 将代码拆分成多个小块(chunks),实现按需加载或并行加载,减少初始加载体积,提升首屏速度。
实现方式:

  1. 动态 import() 语法(推荐): 现代ES模块标准。

    javascript 复制代码
    // 组件级别分割
    const LazyComponent = React.lazy(() => import('./LazyComponent'));
    // 路由级别分割(配合React Router)
    const About = React.lazy(() => import('./routes/About'));
  2. Webpack 配置:

    • 入口点分割(Entry Points): 配置多入口。

    • 防止重复(Split Chunks): 使用 optimization.splitChunks 提取公共依赖和第三方库。

      javascript 复制代码
      optimization: {
        splitChunks: {
          chunks: 'all', // 对所有chunk进行拆分
          cacheGroups: {
            vendors: {
              test: /[\\/]node_modules[\\/]/,
              name: 'vendors',
            },
            commons: {
              name: 'commons',
              minChunks: 2, // 至少被2个入口引用
            }
          }
        }
      }
  3. 框架内置:

    • React: React.lazy() + Suspense
    • Vue Router: 定义路由时使用 component: () => import('...')
    • Next.js: 基于页面的自动代码分割。

延伸拓展: 使用 预获取(Prefetching) 进一步提高用户体验。Webpack支持魔法注释:import(/* webpackPrefetch: true */ './module')。浏览器会在空闲时间预加载该模块,当用户需要时可能已缓存。

3. 图片与资源优化

3.1 图片优化有哪些综合方案?

问题: 针对网站图片加载性能,有哪些从格式选择到加载策略的优化手段?
标准答案:

  1. 选择合适的格式:

    • JPEG: 适用于色彩丰富、有渐变的生活照、海报。可调整压缩比。
    • PNG: 适用于需要透明度的图标、图形。PNG-8(256色)和PNG-24。
    • WebP: 现代格式,在同等质量下比JPEG和PNG体积小很多。支持有损/无损、透明。兼容性已很好,应作为首选
    • AVIF: 下一代格式,压缩率比WebP更高,但编解码性能和要求也更高。
    • SVG: 矢量格式,适用于图标、徽标、简单图形。无限缩放且体积小。
  2. 压缩与优化:

    • 使用工具(如Squoosh、TinyPNG、imagemin)进行无损或有损压缩。
    • 在构建流程中集成自动化图片压缩插件(如 image-webpack-loader)。
  3. 响应式图片:

    • 使用 srcsetsizes 属性,让浏览器根据屏幕尺寸和分辨率选择合适图片。
    • 使用 <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="示例">
  4. 懒加载:

    • 使用原生属性 loading="lazy"(兼容性良好)。
    • 第三方库(如 lozad.js)作为备选。
  5. CDN加速: 将图片放在CDN上,并利用其图片处理服务(如裁剪、缩放、格式转换)。

3.2 如何实现图片懒加载?

问题: 描述图片懒加载的原理和具体实现方式。
标准答案:
原理: 只加载可视区域(或即将进入可视区域)内的图片,当用户滚动时,再加载后续图片。
实现方式:

  1. 原生实现(最简单): <img loading="lazy" src="image.jpg" alt="...">。浏览器自动处理。

  2. 基于 Intersection Observer API(现代推荐):

    javascript 复制代码
    const 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="...">

  3. 传统滚动监听+计算位置: 监听 scroll 事件,计算图片是否在视口内。需注意防抖/节流 ,性能不如 IntersectionObserver

延伸拓展: 可结合占位符(纯色、低质量图像占位符LQIP、模糊缩略图)提升体验。对于背景图片,通常需要通过切换CSS类或内联样式来实现懒加载。

4. 存储与缓存策略进阶

4.1 Service Worker 如何进行性能优化?

问题: Service Worker 除了实现离线功能,在性能优化方面有哪些应用?
标准答案:

Service Worker 是一个独立于主线程的脚本,充当网络代理,可以拦截和处理网络请求。
性能优化应用:

  1. 智能缓存策略(核心):
    • Cache First, Network Fallback: 优先从缓存返回,适合不常变的静态资源。
    • Network First, Cache Fallback: 优先请求网络,失败再用缓存,适合需要一定实时性的数据。
    • Stale-While-Revalidate: 立即从缓存返回旧数据,同时后台发起网络请求更新缓存,适用于可接受短时旧数据的场景(如用户头像、文章列表)。
  2. 预缓存关键资源:install 事件中,将App Shell(应用外壳)所需的核心静态资源提前缓存。
  3. 预获取和预加载: 根据用户行为预测,在空闲时预获取下一步可能需要的资源(例如,预获取文章详情页的CSS/JS)。
  4. 后台同步(Background Sync): 确保用户离线时的操作(如表单提交)能在网络恢复后同步到服务器,提升用户体验。
  5. 推送通知(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 如何进行性能监控和数据上报?

问题: 一个完整的前端性能监控体系包含哪些方面?如何收集和上报数据?
标准答案:
监控体系包含:

  1. 指标收集:
    • 核心Web指标 (LCP, FID/INP, CLS): 使用 web-vitals 库标准化获取。
    • 自定义性能指标: 首屏时间、FP/FCP、资源加载时间、AJAX请求耗时、页面错误率。
    • 用户行为数据: PV/UV、页面停留时间、操作路径。
  2. 数据上报:
    • 上报时机:

      • window.onloadsetTimeout 延时上报,避免阻塞。
      • 使用 sendBeacon API 在页面卸载时可靠上报。
      • 批量上报,合并请求。
    • 上报方式:

      1. Image Beacon: 创建一个1x1的GIF图片,将数据拼在URL参数中。简单、跨域友好、无需响应。
      javascript 复制代码
      new Image().src = `https://monitor.com/collect?data=${encodeURIComponent(data)}`;
      1. navigator.sendBeacon() 专门为日志上报设计,异步、可靠,即使页面关闭也会尽力发送。
      javascript 复制代码
      navigator.sendBeacon(url, JSON.stringify(data));
      1. AJAX/Fetch: 灵活性最高,但需处理跨域和页面卸载丢失问题。
  3. 数据存储与分析: 后端接收日志,存入时序数据库或大数据平台,进行聚合分析、可视化展示和告警。

延伸拓展: 使用 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 等性能条目,精确计算每次交互的完整延迟,并持续评估以报告最具代表性的高百分位数值。
相关推荐
Hao_Harrision2 小时前
50天50个小项目 (React19 + Tailwindcss V4) ✨ | StickyNavbar(粘性导航栏)
前端·typescript·react·tailwindcss·vite7
柒.梧.2 小时前
Java核心面试题终极总结:从基础到进阶,覆盖高频考
java·开发语言·面试
不爱学习的老登2 小时前
基于CodeServer打造一个属于自己的 LaTeX Web 编辑器
前端·编辑器
Jinuss2 小时前
飞冰ice.js中Model数据初始化原理
前端·javascript·react.js
IT_陈寒2 小时前
Python性能优化实战:7个让代码提速300%的冷门技巧(附基准测试)
前端·人工智能·后端
karshey2 小时前
【前端】Defer:存储Promise状态,多个异步事件都结束后处理一些逻辑
java·前端·javascript
be or not to be2 小时前
CSS 样式基础与视觉设计:从单位到字体
前端·css
0思必得02 小时前
[Web自动化] CSS布局与定位
前端·css·自动化·html·web自动化
朱 欢 庆2 小时前
在docker容器里 使用Jenkins部署前端项目
前端·经验分享·docker·jenkins