前端性能优化面试题整理

211 阅读20分钟

基础性能优化方案

  • 页面加载优化
    • 压缩资源:对 HTML、CSS、JavaScript 文件进行压缩,减少文件大小,加快下载速度。
    • 懒加载:对于图片、视频等资源,可以采用懒加载技术,当用户滚动到相应位置时再加载资源,减少初始加载时间。
    • 优化图片:选择合适的图片格式(如 WebP),压缩图片大小,避免使用过大的图片。
    • 使用 CDN:利用内容分发网络,将静态资源分布在全球各地的服务器上,让用户从最近的服务器获取资源,提高加载速度。
  • 代码优化
    • 减少 DOM 操作:频繁的 DOM 操作会导致性能问题,可以尽量减少对 DOM 的查询和修改,或者使用文档片段等方式批量操作 DOM。
    • 优化 JavaScript 执行:避免使用过多的全局变量,减少不必要的函数调用,优化循环和条件判断等。
    • 代码分割:将大型的 JavaScript 代码库分割成多个小的模块,只在需要的时候加载相应的模块,减少初始加载时间。
  • 缓存优化
    • 浏览器缓存:设置合理的缓存策略,让浏览器缓存静态资源,减少重复请求。
    • 服务端缓存:在服务器端设置缓存,如使用缓存中间件等,提高响应速度。
  • 渲染优化
    • 减少重绘和重排:避免频繁地修改会触发重排和重绘的样式属性,如使用transform和opacity代替top和left等。
    • 使用虚拟列表:当处理大量数据列表时,使用虚拟列表技术,只渲染可见部分的数据,提高渲染性能。
  • 网络请求优化
    • 减少请求次数:合并 CSS 和 JavaScript 文件,减少 HTTP 请求次数。
    • 优化请求顺序:将关键资源的请求提前,确保页面尽快显示关键内容。
    • 使用 HTTP/2 或 HTTP/3:这些新的协议可以提高网络传输效率,减少请求延迟。

CDN 的原理

  • 节点分布
    • CDN 网络由大量分布在不同地理位置的服务器节点组成,这些节点靠近用户群体。例如,在全球范围内,会有节点分布在各个主要城市或地区。
    • 当用户请求访问内容时,CDN 会根据用户的地理位置,选择距离用户最近(或者网络状况最优)的节点来提供服务。
  • 内容缓存
    • 当源站(内容的原始提供者)的内容(如网页、图片、脚本等)首次被请求时,CDN 会从源站获取内容并缓存到其各个节点上。
    • 例如,一个热门网站的图片被请求,CDN 从该网站的源服务器获取图片,然后将图片副本存储在各个节点。之后如果其他用户在附近节点的覆盖范围内请求该图片,就可以直接从 CDN 节点获取,而不需要再次从源站获取,大大提高了访问速度。
  • 智能路由
    • CDN 系统具有智能的路由算法。当用户发出请求时,它会分析网络状况,如网络拥塞程度、不同链路的带宽等。
    • 然后选择最佳的路径将请求导向合适的 CDN 节点。例如,如果某条链路出现拥塞,CDN 会将请求导向其他相对畅通的链路对应的节点。
  • 负载均衡
    • CDN 通过负载均衡技术,确保各个节点的负载相对均衡。当某个节点的负载过高时,会将部分请求导向负载较低的节点。
    • 这有助于提高整个 CDN 网络的性能和可靠性,防止某个节点因负载过大而出现服务中断或响应缓慢的情况。

如何做前端性能优化

通过 performance 工具对项目页面性能进行分析,从中筛选出部分指标作为本项目的性能衡量指标。常见的指标有以下这些:

  • load(Onload Event),它代表页面中依赖的所有资源加载完的事件。
  • DCL(DOMContentLoaded),DOM解析完毕。
  • FP(First Paint),表示渲染出第一个像素点。FP一般在HTML解析完成或者解析一部分时候触发。
  • FCP(First Contentful Paint),表示渲染出第一个内容,这里的“内容”可以是文本、图片、canvas。
  • FMP(First Meaningful Paint),首次渲染有意义的内容的时间,“有意义”没有一个标准的定义,FMP的计算方法也很复杂(建议不使用,或者结合产品经理讨论使用)。
  • LCP(largest contentful Paint),最大内容渲染时间。

在前端优化方面,可以分为两个部分:开发时性能和生产时性能。

  • 开发时性能主要是提升前端开发打包效率,主要是构建打包方面的问题,其实具体的操作与Vite和Webpack等工具是强耦合的。
    • 可视化分析:对打包后的文件大小进行可视化分析,能够更好的分析哪些包比较大,或者小的进行合并。如 Vite 的rollup-plugin-visualizer、和 Webpack 的webpack-bundle-analyzer
    • 缩小加载范围:配置 include/exclude 缩小 Loader 对文件的搜索范围,好处是避免不必要的转译。不然所有 node_modules 都跑一边那不是卡死了。
    • 打包缓存:很多工具都可以开启打包的缓存,这一步能大大减少构建时间。如hardsource-webpack-plugin等实现缓存效果的工具。
    • 并行构建:释放 CPU 多核并发的优势。诸如 happyPack、thread-loader等工具都可以在不同阶段开启 CPU 多核进行并行构建,大大提升开发时效率。
  • 生产时性能可以分为两个层面。第一,加载层面。第二,渲染层面。加载层面又可以分为以下几个策略:构建、网络、缓存,渲染层面分为以下几个策略:CSS、DOM、阻塞、代码实践部分:
    • 加载层面
      • 构建策略:减小文件体积:
        • 代码分割:Split Chunk
        • Tree Shaking:其实大部分工具已经自带了
        • 按需加载:使用的时候才加载
        • 压缩资源:压缩 HTML/CSS/JS 代码,压缩字体/图像/音频/视频,好处是更有效减少打包体积。
        • 图像处理:在此单独提出图像的压缩处理,是因为大多数情况下,对图片进行优化的成效往往是巨大的,可能远程你分包,修改代码的优化程度,而且花费时间甚少。图像的选型往往也可以在不同场景下提供不同的效果。这里不再展开。
      • 网络策略:CDN,即时内容分发网络。使用CDN可降低网络拥塞,提高用户访问响应速度和命中率。其核心特征是缓存和回源,缓存是把资源复制到CDN服务器里,回源是资源过期/不存在就向上层服务器请求并复制到CDN服务器里。(此种方式虽然成本较高,但是中大型的公司一般都会有购买CDN)
      • 缓存策略:强缓存、协商缓存:这也是非常常见的浏览器缓存方案,这里不对其原理和配置进行描述。应用场景都可根据项目需求制定。
    • 渲染层面
      • CSS 策略:
        • 避免出现多层的嵌套规则
        • 避免为 ID 选择器添加多余选择器
        • 避免使用通配选择器,只对目标节点声明规则
      • DOM策略:(回流重绘)
        • 缓存 DOM 计算属性
        • 避免过多 DOM 操作
        • 使用 DOMFragment 缓存批量化 DOM 操作
        • 使用 display 控制 DOM 显隐,将 DOM 离线化
        • 在异步任务中修改 DOM 时把其包装成微任务
      • 阻塞策略:
        • 脚本与 DOM /其它脚本的依赖关系很强:对<script>设置 defer
        • 脚本与 DOM /其它脚本的依赖关系不强:对<script>设置 async
      • 代码实践策略:
        • 防抖、节流
        • 懒加载
        • 绘图时可开启 GPU 加速
        • 时间分片、Web Worker 处理大、长逻辑

如何使用 Chrome Performance 工具来识别和解决页面加载性能问题?

  1. 打开 Performance 工具:在 Chrome 浏览器中,打开目标网页,然后打开开发者工具并选择 Performance 面板。
  2. 配置模拟条件:在 Performance 面板中,可以通过 Capture Settings 设置模拟设备的 CPU 和网络条件,这有助于模拟真实用户在不同设备上访问网页时的性能表现。
  3. 开始录制:点击 Record 按钮开始录制页面的性能数据。如果需要分析页面加载过程,可以使用 Reload 按钮来自动刷新页面并开始录制。
  4. 分析性能数据:
  • Overview(概览)
    • 是一个时间轴视图,展示了页面加载和交互过程中的各个阶段。
    • 可以看到页面加载的不同阶段,如导航开始、DNS 查询、TCP 连接、资源加载、脚本执行等。
    • 通过观察时间轴,可以快速了解页面加载的整体情况,找出耗时较长的阶段。
  • Main(主线程)
    • 这是页面加载和交互过程中主线程的活动记录。
    • 可以看到 JavaScript 执行、样式计算、布局、绘制等活动。
    • 通过观察主线程的活动,可以找出哪些操作占用了较多的时间,如长时间的脚本执行、频繁的布局和绘制等。
  • Network(网络)
    • 显示页面加载过程中的网络请求情况。
    • 可以看到每个请求的时间、大小、状态等信息。
    • 通过观察网络请求,可以找出加载时间较长的资源,以及是否存在过多的请求或重复请求。
  • Frames(帧)
    • 展示页面的帧率情况。
    • 可以看到页面在不同时间点的帧率变化。
    • 如果帧率较低,可能意味着页面存在性能问题,如过多的动画、复杂的布局或长时间的脚本执行。
  1. 识别性能瓶颈:
  • 通过 Bottom-Up 视图查看各个任务的执行时间,找到占用时间最长的任务。
  • 在 Call Tree 视图中,分析调用栈来识别性能问题的具体代码位置。
  • 使用 Event Log 查看事件的顺序和时间,帮助诊断性能问题。
  1. 内存分析:如果启用了 Memory 跟踪,可以分析内存使用情况,检查是否存在内存泄漏。
  2. 优化建议:
  • 根据分析结果,识别并优化长任务、减少重排和重绘、优化 JavaScript 执行等。
  • 使用 Performance 监控器(Performance Monitor)来实时跟踪性能指标,如 CPU 使用率、DOM 节点数量等。
  1. 实施优化:根据分析结果,对代码进行优化,比如减少重绘和回流、优化 JavaScript 执行、利用 Web Workers 处理复杂计算等。
  2. 验证优化效果:优化后,重新使用 Performance 工具录制并分析页面性能,验证优化是否有效。

如何使用 Chrome Lighthouse 工具来识别和解决页面加载性能问题?

  1. 打开 Lighthouse 在 Chrome 浏览器中打开要分析的网页,打开开发者工具,切换到 Lighthouse 选项卡。
  2. 运行 Lighthouse 审计 选择要审计的类别,如性能、可访问性、最佳实践、SEO 等。通常,为了识别页面加载性能问题,至少选择性能类别。点击 Generate report 按钮开始审计。
  3. 分析报告
  • 性能得分:
    • Lighthouse 会给出一个总体的性能得分,范围从 0 到 100。得分越高,表示页面性能越好。
    • 关注得分的变化,以了解优化措施的效果。
  • 性能指标:
    • 报告中会显示各种性能指标,如首次内容绘制(FCP)、最大内容绘制(LCP)、首次输入延迟(FID)、累积布局偏移(CLS)等。
    • 了解每个指标的含义和目标值,以便确定需要优化的方面。
  • 诊断信息:
    • Lighthouse 会提供详细的诊断信息,指出可能影响性能的问题。
    • 例如,可能会提示资源加载时间过长、JavaScript 执行时间过长、未优化的图片等问题。
  • 优化建议:
    • 根据诊断信息,Lighthouse 会给出具体的优化建议。
    • 这些建议可能包括压缩资源、减少 HTTP 请求、优化图片、使用缓存等。
  1. 解决性能问题
  • 优化资源加载:
    • 压缩 JavaScript、CSS 和 HTML 文件,以减小文件大小。
    • 使用图像压缩工具优化图片,减少图片的文件大小。
    • 合并和压缩 CSS 和 JavaScript 文件,减少 HTTP 请求数量。
    • 使用懒加载技术,延迟加载非关键资源,如图片和视频。
  • 优化 JavaScript 执行:
    • 减少不必要的 JavaScript 代码,去除未使用的函数和变量。
    • 避免长时间的同步操作,如同步的 XMLHttpRequest 请求。
    • 使用异步加载和执行 JavaScript,如使用 defer 或 async 属性加载脚本。
  • 优化网络请求:
    • 使用内容分发网络(CDN)加速资源加载。
    • 设置适当的缓存策略,使浏览器可以缓存资源,减少重复请求。
    • 优化服务器响应时间,减少资源的下载时间。
  • 优化页面布局:
    • 避免复杂的布局和过多的嵌套元素,以减少布局计算的时间。
    • 确保页面在加载过程中不会出现明显的布局偏移(CLS)。
    • 使用响应式设计,确保页面在不同设备上都能快速加载和显示。
  1. 重新运行 Lighthouse 审计
  • 在实施优化措施后,重新运行 Lighthouse 审计,以检查性能是否有所改善。
  • 对比前后两次的报告,查看性能得分和各个指标的变化。
  • 根据新的诊断信息和优化建议,继续进行优化,直到达到满意的性能水平。

为什么大文件分片可以提高上传速度?

  • 并行上传:分片后,多个文件片段可以同时上传,利用多线程或多个连接并行传输数据,这样可以显著提高总体的上传速率。
  • 网络利用率:当一个大文件被分成多个小片段时,可以更充分地利用可用的网络带宽。如果网络条件允许,同时发送多个小文件比发送一个大文件更有效。
  • 减少等待时间:在传统的串行上传中,如果某个片段遇到网络问题或延迟,整个上传过程将等待该片段上传完成。而分片上传可以允许其他片段继续上传,不会因单个片段的问题而阻塞整个上传过程。
  • 快速恢复:如果某个片段在上传过程中失败,只需要重新上传那一部分,而不是整个文件。这减少了因错误重传所需的时间。
  • 适应性网络条件:不同的网络条件可能对小文件的传输更为有利。例如,在不稳定的网络环境下,小文件可能更容易一次性成功传输。
  • HTTP/2 特性:如果使用 HTTP/2 协议,它可以支持在单个 TCP 连接上并行传输多个请求和响应,进一步优化了分片上传的效率。
  • 减少重传开销:当使用基于 TCP 的协议上传大文件时,如果发生中断,可能需要重传整个文件。而分片上传只需重传未完成的片段,减少了重传的数据量。
  • 更好的错误处理:分片上传允许服务器对每个片段进行独立验证,如果发现错误,只需请求重新上传问题片段,而不是整个文件。
  • 提高用户体验:用户可以看到某些片段已经上传完成,这种进度反馈可以提升用户体验,尤其是在上传过程中遇到问题时。
  • 利用现代浏览器特性:现代浏览器支持更高效的文件上传API,如 XMLHttpRequest Level 2 的 FormData 对象,可以更轻松地实现大文件的分片上传。

前端性能优化的关键指标有哪些?

  • 加载时间
    • 页面加载时间(Page Load Time):指从用户在浏览器中输入 URL 到页面完全加载并可以交互所花费的时间。这是用户最直接感受到的性能指标,较短的页面加载时间可以提高用户满意度和留存率。
      • 测量工具:Google PageSpeed Insights、WebPageTest 等工具可以测量页面加载时间。
      • 优化方法:压缩资源文件、减少 HTTP 请求、使用缓存、优化服务器响应时间等。
    • 首屏加载时间(First Contentful Paint,FCP):指浏览器首次渲染出任何文本、图像、非空白的 Canvas 或 SVG 的时间。首屏加载时间对于用户体验至关重要,因为用户通常希望在最短的时间内看到页面的主要内容。
      • 测量工具:浏览器开发者工具中的性能面板可以测量首屏加载时间。
      • 优化方法:优先加载首屏内容、使用懒加载、优化关键资源的加载顺序等。
    • 可交互时间(Time to Interactive,TTI):指页面从开始加载到用户可以与页面进行交互(如点击链接、输入表单等)所花费的时间。可交互时间反映了页面的响应速度,较短的可交互时间可以提高用户的操作效率。
      • 测量工具:Google PageSpeed Insights、WebPageTest 等工具可以测量可交互时间。
      • 优化方法:减少 JavaScript 执行时间、优化 DOM 操作、使用异步加载等。
  • 渲染性能
    • 帧率(Frame Rate):指浏览器在一秒内绘制页面的次数。较高的帧率可以提供流畅的动画和交互效果,而低帧率会导致页面卡顿和不流畅。一般来说,帧率达到 60fps(frames per second)被认为是流畅的。
      • 测量工具:浏览器开发者工具中的性能面板可以测量帧率。
      • 优化方法:优化动画效果、减少重绘和重排、使用硬件加速等。
    • 重绘和重排(Repaint and Reflow):重绘是指当页面元素的外观发生改变但不影响布局时,浏览器重新绘制该元素的过程。重排是指当页面元素的布局发生改变时,浏览器重新计算页面布局的过程。重绘和重排会消耗大量的性能,应尽量减少它们的发生次数。
      • 测量工具:浏览器开发者工具中的性能面板可以查看重绘和重排的情况。
      • 优化方法:避免频繁修改样式、使用 CSS 类而不是直接修改样式、批量修改 DOM 等。
  • 资源加载
    • 资源大小(Resource Size):指页面加载的各种资源文件(如 HTML、CSS、JavaScript、图片等)的大小。较小的资源文件可以加快加载速度,减少网络带宽的使用。
      • 测量工具:浏览器开发者工具中的网络面板可以查看资源文件的大小。
      • 优化方法:压缩资源文件、使用图片优化技术、减少不必要的资源加载等。
    • 资源请求数量(Number of Requests):指页面加载过程中发起的 HTTP 请求数量。较多的请求数量会增加页面加载时间,应尽量减少请求数量。
      • 测量工具:浏览器开发者工具中的网络面板可以查看请求数量。
      • 优化方法:合并资源文件、使用雪碧图、使用缓存等。
  • 用户体验指标
    • 用户可察觉的加载时间(Perceptual Load Time):指用户主观感受到的页面加载时间。这与页面加载时间可能不同,因为用户的感受受到多种因素的影响,如页面的视觉效果、交互反馈等。
      • 测量方法:可以通过用户测试、问卷调查等方式来了解用户对页面加载时间的感受。
      • 优化方法:提供加载进度条、优化页面的视觉效果、及时提供交互反馈等。
    • 核心 Web 重要指标(Core Web Vitals):这是一组由 Google 提出的用户体验指标,包括最大内容绘制(Largest Contentful Paint,LCP)、首次输入延迟(First Input Delay,FID)和累积布局偏移(Cumulative Layout Shift,CLS)。这些指标反映了页面的加载性能、交互响应性和视觉稳定性。
      • 测量工具:Google PageSpeed Insights、Chrome 用户体验报告等工具可以测量 Core Web Vitals。
      • 优化方法:针对每个指标进行具体的优化,如优化图片加载、减少 JavaScript 执行时间、避免意外的布局变化等。

前端性能优化实例

问题:FCP(首屏加载时间)4-5秒

  • 首页加载的时候会先获取几张比较大的图片,导致 FCP 指标过长。
    • 通过 Webpack 插件imagemin-webpack-plugin在构建过程中自动压缩图片。这样可以确保在每次构建项目时,图片都被压缩到最佳状态。
    • 使用 JavaScript 库(如 lazysizes)来实现图片懒加载。当页面加载时,先不加载图片,而是在用户滚动到图片可见区域时再进行加载。这样可以减少初始页面加载的资源数量,提高页面加载速度。
  • 项目打包后,把所有页面打包成一个文件,当用户打开首页时,会一次性加载所有的资源,造成首页加载很慢,降低用户体验
    • 优化前:app.js初始体积:900KB 以上,app.css初始体积:200KB左右
    • 采用路由懒加载优化
      // 通过webpackChunkName设置分割后代码块的名字
      const Home = () => import(/* webpackChunkName: "home" */ "@/views/home/index.vue");
      const MetricGroup = () => import(/* webpackChunkName: "metricGroup" */ "@/views/metricGroup/index.vue");
      
      const routes = [
        {
          path: "/",
          name: "home",
          component: Home
        },
        {
          path: "/metricGroup",
          name: "metricGroup",
          component: MetricGroup
        },
      ]
      
    • 优化后app.jsapp.css拆分为多个文件,app.js体积:120KB,app.css初始体积:40KB
  • 首页有多个展示证券行情详情的弹框,一进入页面就加载了
    • 当用户打开首页页时,会一次性加载该页面所有的资源,期望的是用户触发按钮后,再加载该弹框组件的资源,采用组件懒加载进行优化
      const dialogInfo = () => import(/* webpackChunkName: "dialogInfo" */ '@/components/dialogInfo');
      export default {
        name: 'homeView',
        components: {
          dialogInfo
        }
      }
      
    • 使用组件路由懒后,首页资源进一步减少约 10% 左右
  • 首页中有需要渲染长列表的场景,当渲染条数过多时,所需要的渲染时间会很长,滚动时还会造成页面卡顿,整体体验非常不好
    • 采用虚拟滚动优化:只渲染可视区域的列表项,非可见区域的不渲染,在滚动时动态更新可视区域。原理是计算出列表总高度(totalHeight),并在触发时滚动事件时根据已滚动的距离(scrollTop)不断更新开始项(startIndex)以及结束项(endIndex),以此从列表数据中截取对应元素
    • 使用虚拟滚动插件vue-virtual-scroller,该插件主要有RecycleScroller.vueDynamicScroller.vue这两个组件,其中 RecycleScroller 需要 item 的高度为静态的,也就是列表每个 item 的高度都是一致的,而 DynamicScroller 可以兼容 item 的高度为动态的情况
      // main.js
      import VueVirtualScroller from 'vue-virtual-scroller'
      import 'vue-virtual-scroller/dist/vue-virtual-scroller.css'
      
      Vue.use(VueVirtualScroller)
      
      // 使用
      <template> 
        <RecycleScroller 
          class="scroller" 
          :items="list" 
          :item-size="32" 
          key-field="id" 
          v-slot="{ item }"
        > 
          <div class="user"> {{ item.name }} </div>
        </RecycleScroller> 
      </template>
      
    • 优化后性能提升了 60%(数据来源浏览器 Performance 中的 Summary)