前端性能优化面试题

360 阅读22分钟

性能优化.png

回流与重绘

一、回流与重绘的概念及触发条件

回流(也称重排)

概念: 回流是浏览器为了响应某些操作,需要重新计算页面元素的几何属性(如位置、大小)的过程。这个过程包括重新计算元素的布局及渲染树,可能导致父元素及其后续元素的几何属性变化。回流是相对成本较高的操作,因为它会影响文档中其他元素的位置,有时甚至导致整棵渲染树的重新构造。

触发条件:

  1. 页面初次加载或首次渲染DOM结构。
  2. 元素尺寸、位置或显示属性发生变化(如宽度、高度、边距、边框、字体大小等)。
  3. 浏览器窗口尺寸变化,导致响应式布局调整。
  4. DOM树结构变化,如添加或删除可见的DOM元素。
  5. 使用JavaScript动态改变元素的样式,影响布局。
  6. CSS动画或变换可能引起回流,特别是当它们影响到元素的尺寸或位置时。

重绘

概念: 重绘是指当页面中元素的外观属性(如颜色、背景、边框、阴影等)改变,但不影响布局的情况下,浏览器重新绘制该元素外观的过程。相对于回流,重绘的开销较低,因为它不需要重新计算布局,只涉及视觉更新。

触发条件:

  1. 改变元素的背景色、边框颜色、文字颜色等不影响布局的样式。
  2. 元素的visibility属性从visible变为hidden或反之。
  3. 使用CSS的:hover伪类改变元素外观。
  4. 动态添加CSS滤镜效果,如opacity或transform(如果该变换不影响布局)。
  5. 修改CSS的outline样式。

关系: 回流一定会触发重绘,因为一旦元素布局变化,其外观通常也需要更新。然而,重绘不一定会触发回流,除非该视觉变化影响到了元素的布局属性。优化性能时,尽量减少回流的发生,因为它是更耗时的操作。可以通过合并样式修改、使用CSS transforms而非position变化、以及利用requestAnimationFrame等策略来优化。

二、如何避免回流与重绘?

  1. 优化CSS选择器:使用高效的选择器,减少样式的计算成本,避免不必要的样式重新计算导致重绘。
  2. 使用CSS Transform和Opacity:对于动画效果,尽可能使用transform和opacity属性,因为这些属性的改变不会引发回流,而是在合成层中进行,性能更优。
  3. 避免使用强制同步布局的属性:如offsetWidth、scrollLeft等属性的读取会触发回流,考虑使用getComputedStyle代替或者缓存这些值。
  4. 批量修改样式:使用className切换或CSS类名来批量修改样式,而不是逐个修改元素样式,减少重绘次数。
  5. 创建新的渲染层:利用will-change属性或者通过设置position: fixed|absolute等属性,为元素创建独立的合成层,这样元素的变化就不会影响到其他部分的布局。
  6. 避免频繁读写DOM:减少对DOM的操作频率,使用DocumentFragment进行批量DOM操作,或者使用虚拟DOM(如React)来最小化实际DOM操作。
  7. 使用requestAnimationFrame:在进行样式更改或DOM操作时,使用requestAnimationFrame来确保在浏览器下一帧渲染前进行,有助于减少重绘和回流。
  8. 避免布局触发的属性:减少使用会导致回流的CSS属性,如width、height、margin、padding、border-width等的动态修改,尤其是在循环或频繁的事件处理中。
  9. 预计算布局信息:如果必须读取布局信息(如元素尺寸),尽可能一次性读取并缓存这些值,避免重复触发回流。
  10. 合理利用CSS的contain属性:通过设置contain: layout|paint|size|style,可以限制元素的影响范围,减少不必要的回流和重绘。

三、如何优化动画

在优化动画时,我们应该优先考虑使用CSS动画而非JavaScript动画,因为CSS动画由浏览器优化并支持硬件加速。对于动画属性,使用transformopacity可以避免回流和重绘,提升性能。同时,利用requestAnimationFrame能与浏览器的渲染帧同步,确保动画流畅。避免动画中涉及大量DOM元素以及复杂的布局变化,减少动画计算的复杂性。另外,使用translate3d()可以强制启用GPU加速,进一步优化性能。

四、documentFragment是什么?用它跟直接操作DOM的区别是什么

documentFragment是一个轻量级的虚拟DOM容器,它没有父节点,不会被渲染,也不会影响页面的回流和重绘。在直接操作DOM时,每次修改都会立刻触发页面的渲染,可能会导致性能问题,尤其是在进行批量操作时。而使用documentFragment时,所有的DOM修改都在内存中进行,只有在将其添加到DOM树中时,才会触发一次渲染,避免了多次回流和重绘,提高了性能。因此,documentFragment适用于批量操作DOM元素的场景,尤其是在生成大量元素时。

图片优化

一、如何对项目中的图片进行优化

在项目中进行图片优化时,我们可以从多个方面入手。首先,选择合适的图片格式,如使用WebP代替JPEG和PNG,可以减少文件大小。然后,进行图片压缩,既可以使用无损压缩,也可以通过有损压缩减少图片体积。调整图片的尺寸,确保上传和显示尺寸匹配,避免加载过大的图片。对于需要显示多个小图标的情况,可以使用精灵图来合并多个图片,减少HTTP请求次数。同时,使用懒加载技术,只有图片进入视口时才加载,进一步提升页面加载性能。此外,可以使用CDN来加速图片的加载,并设置合理的缓存策略,以提高用户体验。

二、常见的图片格式及使用场景

在项目中,我们通常会根据图像的类型和需求来选择合适的格式。对于照片和复杂的色彩图像,JPEG通常是首选,因为它提供了较好的压缩效果和较小的文件大小。对于需要透明背景的图像,像图标、按钮或界面元素,PNG是比较合适的格式。对于支持高压缩比且保持较高质量的现代浏览器,WebP是一个很好的选择,尤其是对于网页上的图片优化。如果需要动画效果,GIF格式适合用来做简单的动画。对于可缩放的图标或矢量图,SVG是理想的选择,它可以无损缩放,适用于响应式设计。而对于未来优化的需求,AVIF格式提供了更高的压缩比和图像质量,适合未来的图片优化。

webpack优化

一、如何减少webpack打包时间

为了减少 Webpack 的打包时间,我会从以下几方面入手优化:

  1. 确保使用 production 模式,这样可以启用压缩、Tree Shaking 等优化。
  2. 配置缓存(如 filesystem 缓存),避免每次构建时重新编译所有模块。
  3. 使用代码分割(splitChunks)和动态导入(import())来实现按需加载,减小初始包的体积。
  4. 开启 Tree Shaking 去除未使用的代码,并通过配置 sideEffects 提示 Webpack 哪些模块是没有副作用的。
  5. 使用并行加载器(如 thread-loader)来加速文件的处理。
  6. 通过外部引入 CDN 中的库,减少 Webpack 需要处理的文件量。

二、如何减少webpack打包体积

  1. Tree Shaking:

    • 确保你的代码和依赖库采用ES模块(ESM)编写,以便Webpack能够识别未使用的导出并自动排除它们。使用ES6模块语法,配合sideEffects: false配置项,可以让Tree Shaking效果更佳。
  2. 代码分割(Code Splitting):

    • 利用Webpack的动态导入(import())特性进行代码分割,将代码根据不同入口或路由拆分成多个chunk,实现按需加载。

    • 使用SplitChunksPlugin自动将公共代码抽离出来,避免重复打包。

  3. 压缩和丑化:

    • 生产环境下启用TerserPlugin进行JavaScript代码压缩,移除无用代码、注释和空格。

    • 对CSS使用MiniCssExtractPlugin和css-loader的minimize选项,减少CSS体积。

  4. 图片和资源优化:

    • 使用url-loader和file-loader对小图片进行base64编码内联,减少HTTP请求。

    • 对大图片和其他资源进行压缩,使用image-webpack-loader等工具。

    • 考虑使用现代图片格式,如WebP,利用 <picture> 标签和格式选择器适应不同浏览器。

  5. 去除开发依赖和无用代码:

    • 使用optimization.providedExports和optimization.usedExports配置项,确保只打包实际用到的代码。

    • 仔细审查package.json,移除不必要的开发依赖(devDependencies),避免它们被打包进生产环境。

  6. 外部化库:

    • 对于大型库,如React、Vue等,可通过externals配置将其作为外部依赖,通过CDN引入,避免打入包内。
  7. 缓存策略:

    • 利用长期缓存策略,如内容哈希(content hashing),确保文件名随内容变化,同时让浏览器能够缓存静态资源。
  8. 持续性能监测:

    • 使用webpack-bundle-analyzer等工具定期分析包体积,识别体积膨胀的原因并采取相应措施。

三、如何用webpack来优化前端性能

为了优化前端性能,我通常会从以下几个方面入手:

  1. 优化构建速度:启用缓存来加速构建过程,并通过并行构建和减少不必要的转译来缩短构建时间。
  2. 资源优化:使用代码分割和懒加载来按需加载资源,减少初始加载时间。同时,通过 Tree Shaking 去除无用代码,压缩静态资源(如 JavaScript、CSS 和图片)来减小文件体积。
  3. 提高缓存效率:为输出文件添加哈希值,确保浏览器能正确缓存文件。
  4. 使用生产模式的优化:通过设置 mode: 'production' 来启用 Webpack 的生产环境优化。
  5. 外部依赖和分析工具:将常见的第三方库通过 CDN 引入,并使用 webpack-bundle-analyzer 来分析并优化打包结果。

四、如何提高webpack的构建速度

  1. 升级与配置优化:

    • 保持工具链最新:确保webpack、Node.js及其相关依赖(如loader和plugins)均是最新稳定版本,新版本往往包含性能改进。

    • 生产环境配置:在生产环境中使用mode: 'production',webpack会自动开启代码压缩、Tree Shaking等功能,显著减少打包体积。

    • SplitChunks策略:合理配置SplitChunksPlugin,对公共代码、第三方库进行分离,减少重复代码,优化加载时间。

  2. 代码分割与懒加载:

    • 动态导入:利用动态导入(import()语法)按需加载代码模块,避免一次性加载所有代码。

    • 路由级别拆分:在大型应用中,根据路由进行代码分割,用户访问特定页面时才加载对应代码。

  3. 缓存策略:

    • 构建缓存:利用webpack的缓存机制,如cache-loader、HardSourceWebpackPlugin,以及Webpack 5自带的持久化缓存功能,减少重复编译。

    • 浏览器缓存:通过文件指纹(content hashing)确保更新的文件被正确缓存,同时旧文件可复用,减少用户下载量。

  4. 并发与多进程:

    • 多核利用:使用thread-loader或webpack的Worker插件,让编译任务并行处理,充分利用多核CPU。

    • Parallel Uglify Plugin:在压缩JS代码时使用,它能利用多进程加快UglifyJS的工作。

  5. 模块分析与优化:

    • 分析工具:使用webpack-bundle-analyzer等工具分析包体积,定位体积大的模块进行针对性优化。

    • 移除无用代码:借助purgecss-webpack-plugin等工具自动移除未使用的CSS,减小样式文件大小。

  6. 外部化依赖:

    • externals配置:将不常变动的第三方库通过CDN引入,不在打包范围内,减少构建时间及输出体积。
  7. 持续集成与自动化:

    • CI/CD优化:在持续集成流程中,利用缓存机制保存npm包、webpack编译结果等,减少每次构建的准备时间。

五、如何⽤webpack来优化前端性能?

webpack主要分为两个方面进行性能优化,构建优化(开发体验和效率)和线上性能优化(用户加载体验)。

构建优化(开发体验和效率)

  1. 加载器(Loaders)优化:

    • 减少加载器的数量和复杂性。

    • 使用缓存加载器,如 cache-loader,以加快构建速度。

    • 配置加载器的 parallel 或 concurrency 选项,以并行处理文件。

  2. 插件(Plugins)选择:

    • 仅在生产环境中使用那些对构建速度有负面影响的插件。

    • 选择高效的插件版本。

  3. 排除不必要的模块:

    • 使用 exclude 选项来避免对 node_modules 中的代码进行转换,除非绝对必要。

    • 利用 resolve.alias 或 resolve.modules 来优化模块解析路径。

  4. 多进程构建:

    • 使用 happypack 或 thread-loader 来并行执行加载器任务。
  5. 持久化缓存:

    • 使用 cacheDirectory 或 cacheCompression 选项来缓存加载器的中间结果。
  6. 优化 DevServer:

    • 使用 hot 和 inline 选项启用热模块替换(HMR)。

    • 限制 devServer 的 contentBase 以减少不必要的文件读取。

    • 适当调整 devServer 的 watchOptions。

  7. 减少文件系统访问:

    • 使用内存文件系统(如 memory-fs)来减少磁盘 I/O。

线上性能优化(用户加载体验)

  1. 代码分割(Code Splitting):

    • 使用动态导入 (import()) 和 splitChunks 插件来按需加载代码。

    • 分离 vendor 代码和应用代码,避免每次更新都下载整个 bundle。

  2. Tree Shaking:

    • 确保你的代码是模块化的(使用 import 和 export),以便 Webpack 可以移除未使用的代码。
  3. 资源压缩:

    • 使用 TerserWebpackPlugin 或 UglifyJsPlugin 来压缩 JavaScript。

    • 使用 css-minimizer-webpack-plugin 来压缩 CSS。

    • 使用 html-minifier-terser 来压缩 HTML。

    • 压缩和优化图像和字体文件。

  4. 文件名哈希:

    • 使用 [hash] 或 [chunkhash] 在文件名中添加哈希值,以实现缓存控制和避免浏览器缓存过期。
  5. 资源懒加载:

    • 使用 React 的 React.lazy 和 Suspense 或 Vue 的异步组件来实现资源的懒加载。
  6. CDN 和外部资源:

    • 利用 CDN 来托管静态资源,如 vendor 库。

    • 异步加载非关键资源。

  7. 分析工具:

    • 使用 webpack-bundle-analyzer 来分析 bundle 的组成,找出可以进一步优化的地方。
  8. 减少网络请求:

    • 使用 Webpack 的 externals 配置来避免将某些库打包进 bundle,而是通过 CDN 提供。
  9. 缓存策略:

    • 为静态资源设置适当的 HTTP 缓存头。
  10. 使用 DLLs:

    • 预编译第三方库,使用 DLLPlugin 和 DLLReferencePlugin,以避免每次构建时重复编译相同的代码。

六、如何对项目中的图片进行优化?

  1. 图片压缩:

    • 使用工具或在线服务(如TinyPNG, ImageOptim, 或者Squoosh)对图片进行有损或无损压缩,减少文件大小而不明显牺牲视觉质量。

    • 对于JPEG格式,调整压缩率以找到视觉质量和文件大小的最佳平衡。

    • 对于PNG格式,考虑使用PNG-8或PNG-24,根据图片内容选择最适合的色彩深度。

    • 使用SVG格式对于矢量图,保持清晰度的同时减小文件大小。

  2. 图片格式选择:

    • 利用现代图片格式,如WebP或AVIF,它们通常提供更好的压缩效率,相比JPEG和PNG能大幅减小文件大小。

    • 根据浏览器支持情况,可以使用 <picture> 元素或JavaScript库(如picturefill)进行格式兼容性处理。

  3. 懒加载(Lazy Loading):

    • 实施懒加载策略,仅在图片即将进入可视区域时才加载,减少初始页面加载时间。
  4. 图片尺寸与响应式设计:

    • 为不同屏幕尺寸准备合适尺寸的图片,避免在小屏幕上加载大图。

    • 使用<img>标签的srcset属性或CSS的 background-image 配合媒体查询来提供响应式图片。

  5. 雪碧图(CSS Sprites)或图标字体:

    • 将多个小图标合并成一张大图(雪碧图),减少HTTP请求次数。

    • 或使用图标字体,进一步减少请求和优化加载时间,虽然现在更倾向于使用SVG图标以获得更高的分辨率和灵活性。

  6. 缓存策略:

    • 设置合理的HTTP缓存策略,如利用ETag和Last-Modified头,或者设置远期的Expires和Cache-Control头,使得浏览器能够缓存图片资源,减少重复加载。
  7. CDN(内容分发网络):

    • 利用CDN托管图片资源,减少延迟,提高加载速度。
  8. 图片自动优化工具和服务:

    • 使用自动化工具(如Cloudinary, Imgix)或云服务提供商的图片处理API,在上传时自动对图片进行优化处理。

CDN

一、CDN的概念

CDN(内容分发网络)是一种通过分布在各地的服务器来加速内容传输的技术。它的工作原理是将网站的静态资源(如图片、CSS、JS文件等)缓存到离用户最近的边缘服务器上,当用户请求这些资源时,CDN会将请求路由到最近的服务器,从而减少网络延迟,提升加载速度。同时,CDN还可以减少源服务器的带宽消耗,提高网站的可用性和容错性。它广泛应用于静态资源加速、视频流服务、API加速等场景。

二、CDN的作用

CDN(内容分发网络)的主要作用是通过将静态资源缓存到全球分布的多个边缘服务器上,减少了从源服务器到用户的传输时间,进而加速了内容传输,提高了用户访问速度。CDN可以减轻源服务器的负载,避免单点瓶颈,提高网站的可扩展性和稳定性;同时,CDN还可以提高网站的可用性和容错性,支持大规模流量,并且通过优化带宽的使用降低带宽成本。此外,CDN还可以改善用户体验,特别是对于全球分布的用户,提升网站的响应速度,对SEO优化也有积极作用。

三、CDN的原理

CDN(内容分发网络)的原理是通过将静态资源分布在多个地理位置上分布式的边缘服务器中,让用户能够从距离自己最近的服务器上获取内容。用户请求内容时,DNS解析将请求指向最近的边缘服务器,边缘服务器首先检查缓存是否有该资源。如果缓存命中,则直接返回内容;如果缓存未命中,则请求源服务器并缓存资源,之后返回给用户。CDN还采用了负载均衡和智能路由技术来优化请求的分配,提高性能和可靠性。此外,CDN还会根据配置的缓存策略控制资源的更新和过期,确保用户访问的内容是最新的。

四、CDN的使用场景

CDN的使用场景非常广泛,最常见的包括静态资源的加速,如图片、CSS、JavaScript文件等,它通过将这些资源缓存到离用户更近的服务器上,显著提高页面加载速度。此外,CDN还被广泛应用于全球化内容分发,特别是面向全球用户的网站和应用,保证不同地区的用户都能获得快速的访问体验。视频流媒体和直播服务也是CDN的一个重要应用场景,它能确保在高并发情况下流畅的观看体验。CDN还可用于大文件下载、API加速、电子商务平台的流量分担和安全防护等场景,帮助提高网站的可用性、降低带宽成本、加速资源访问,甚至在SEO优化中也有帮助。

懒加载

一、懒加载的概念

懒加载(Lazy Loading)是一种资源加载优化技术,它的核心思想是只有在资源需要的时候才加载,而不是在页面加载时就一次性加载所有资源。通常用于图片、视频、组件等的加载,特别是在内容丰富且包含大量资源的页面中。懒加载的工作原理是,当某个资源进入用户的视口时才触发加载,从而减少初始加载时的资源请求,加快页面加载速度,提升用户体验。例如,图片只有在滚动到视口时才加载,而不是一开始就全部加载。懒加载的优点包括提高页面加载速度、节省带宽和提升用户体验,常用于图片、视频、第三方资源以及单页应用中的组件加载。

二、懒加载的特点

懒加载的特点是按需加载和延迟加载,意味着只有当用户需要某个资源时,才会触发资源的加载。它通过减少初始加载时需要下载的内容,显著提高页面加载速度,尤其适用于包含大量图片、视频或其他重资源的网页。懒加载不仅能节省带宽,还能提升移动端用户的体验,因为它只在用户接触到资源时才加载。这种方式通常依赖滚动事件或视口检测触发加载,适合应用于长列表、单页应用(SPA)等场景。懒加载的缺点是可能在首次访问时产生加载延迟,且需要正确配置以避免SEO问题。

三、懒加载的实现原理

懒加载的实现原理是延迟加载页面中的资源,只有在用户需要时才开始加载这些资源。实现懒加载的常见方式有两种:一种是通过IntersectionObserver API,它可以高效地监控元素是否进入视口,当元素接近可见区域时开始加载;另一种是通过监听scroll事件,手动计算元素是否进入视口并触发加载。懒加载的好处是能显著提升页面的加载速度和用户体验,尤其适用于图像、视频等资源的加载。在实现时,可以通过占位符避免加载时的空白,且要注意避免资源的重复加载。

四、懒加载与预加载的区别

懒加载和预加载的主要区别在于加载时机和目的。懒加载是在用户需要时加载资源,例如图片只有在进入视口时才加载,这有助于减少页面的初始加载时间,提升性能。预加载则是提前加载资源,通常是在页面加载时就加载用户将要使用的资源,以便后续使用时不需要等待。懒加载通常应用于页面内容较多、资源较大的场景,预加载则适用于确保用户即将访问的资源已准备好,避免等待。例如,懒加载可以用于长页面中的图片,预加载则可以用于页面中的字体或JS文件。两者都是提升用户体验的技术,但在使用时要根据具体的场景需求来选择。

节流与防抖

一、对节流与防抖的理解

节流和防抖都是为了控制频繁触发的事件函数的执行频率。节流是将函数的执行频率限制在一个固定的时间间隔内,比如每隔500ms执行一次,适用于滚动、resize等需要定时执行的事件。防抖则是将函数的执行推迟到事件停止触发后的某个时刻,通常用于输入框实时搜索、按钮点击等场景,防止事件在频繁触发时执行多次。简而言之,节流是限制频率,防抖是延迟执行。

二、实现节流函数与防抖函数

节流和防抖的主要区别在于它们控制函数执行的方式。节流是通过设置时间间隔,保证函数在固定的时间内执行一次,而防抖是通过延迟执行,只有在事件停止触发一段时间后才执行。这里是它们的实现代码:

  • 节流函数:通过记录上次执行时间并与当前时间做比较,确保在设定的时间间隔内函数只执行一次。
function throttle(func, delay) {
  let lastTime = 0;
  return function (...args) {
    const now = Date.now();
    if (now - lastTime >= delay) {
      func.apply(this, args);
      lastTime = now;
    }
  };
}

  • 防抖函数:每次触发事件时清除定时器,直到事件停止触发一段时间后才执行函数。
function debounce(func, delay) {
  let timer;
  return function (...args) {
    if (timer) {
      clearTimeout(timer);
    }
    timer = setTimeout(() => {
      func.apply(this, args);
    }, delay);
  };
}

节流常用于避免高频事件(如滚动、resize)重复执行,而防抖则用于确保某些操作只在用户停止输入或停止操作一段时间后执行(如搜索框输入、按钮点击等)。