前端性能优化浅谈

在前端,性能优化是离不开的话题。性能优化可以从两个大的方面去着手,分别是网络层面和渲染层面。网络层面主要是 HTTP 请求的优化,而渲染层面则是浏览器渲染过程中的优化。

目录
  1. 前言
  2. 网络层面
    1. Webpack
    2. 图片优化
    3. HTTP 缓存
    4. 浏览器存储
    5. CDN
    6. HTTP2
  3. 渲染层面
    1. 理解浏览器渲染机制
    2. DOM 优化
    3. 规避重绘和回流
    4. 异步更新策略
    5. 服务端渲染
  4. 一些应用
    1. 懒加载
    2. 防抖和节流
    3. 可视化性能监测

前言#

在前端,性能优化是离不开的话题。性能优化可以从两个大的方面去着手,分别是网络层面和渲染层面。网络层面主要是 HTTP 请求的优化,而渲染层面则是浏览器渲染过程中的优化。

网络层面#

网络层面优化即 HTTP 优化。我们知道 HTTP 访问的开销是非常大的,非常消耗资源。如何有效的优化 HTTP 访问,对于性能有非常大的作用。

主要分为两个方面:资源的压缩和合并,即减少 HTTP 请求次数和减少 HTTP 单次请求所花费的时间。

Webpack#

Webpack 是前端最流行的打包工具之一。在打包过程中,可以进行一下优化

  • DllPlugin:使用 DllPlugin 插件,把第三方库单独打包到一个文件中,减少打包体积。
  • Happypack:将 Loader 由单进程转为多进程,提高打包的效率。
  • Shaking:自动删除不需要的代码,减少代码体积。
  • Gzip: 压缩代码。

图片优化#

图片在静态资源中,使用非常广泛,而且体积是非常大的。优化图片资源,可以有效得减少代码的体积。

  • 适当的格式

    图片格式可以分为无压缩、无损压缩和有损压缩集中。根据不同图片格式的特点,选择正确的图片格式,可以有效的减少图片大小。

    • gif:无损压缩格式。体积较小,而且支持透明和动画,但是只存储 8 位索引,不适合色彩复杂、细节丰富的图片,对于色彩简单的 logo 和 icon 比较合适。

    • jpg:有损压缩格式。体积小,不支持透明度处理,但是人类肉眼不容易发现损失的色彩,适合色彩丰富的图片。使用场景比如大的图片,轮播图等。

    • png:无损压缩格式。体积大,png-8 最多支持 256 种颜色,png-24 最多支持 1600 万种颜色,支持透明度处理,高品质,非常适合用于源文件或需要二次编辑的图片格式的保存,比如网站大的主 logo 等。

    • webp:新的图像格式,由 Google 开发。与 png、jpg 相比,相同的视觉体验下,WebP 图像的尺寸缩小了大约 30%。另外,WebP 图像格式还支持有损压缩、无损压缩、透明和动画。理论上完全可以替代 png、jpg、gif 等图片格式,当然目前 webp 的还没有得到全面的支持。

    • svg:矢量图,文件体积更小,可压缩性更强。图片可无限放大而不失真。是文本文件,可以像写代码一样编辑,但是渲染成本比较高,还有需要学习成本。

  • 图片压缩和优化

    • 使用 base64 压缩体积小的图片,减少 HTTP 请求次数。
    • 使用适当宽度的图片,减少不必要的体积。
    • 使用 css 代替一些简单的图片效果。
    • 使用雪碧图,把多张图片合并在一起,减少图片体积和 HTTP 请求次数。

HTTP 缓存#

主要分为强缓存和协商缓存。请查看浏览器缓存浅析文章。

浏览器存储#

浏览器存储把一些不常更新的资源存储在浏览器,需要时候直接从本地获取,减少不必要的 HTTP 访问。

主要分为 LocalStorage ,SessionStorage 和 IndexDB。下面表格中展示本地存储的一些区别和特性。

特性cookieLocalStorageSessionStorageIndexDB
由谁初始化客户端或服务器,服务器可以使用Set-Cookie请求头。客户端客户端客户端
数据生命周期一般由服务器生成,可以设置过期时间除非被清理,否则一直存在 页面关闭就清理当前页面关闭时清理掉除非被清理,否则一直存在 页面关闭就清理
数据存储大小4K5M5M无限
与服务端通信每次都会携带在 header 中,对于请求性能影响不参与不参与不参与
访问权限任意窗口任意窗口当前页面窗口任意窗口

CDN#

CDN 的全称是 Content Delivery Network,即内容分发网络。CDN 是构建在网络之上的内容分发网络,依靠部署在各地的边缘服务器,通过中心平台的负载均衡、内容分发、调度等功能模块,使用户就近获取所需内容,降低网络拥塞,提高用户访问响应速度和命中率。CDN 的关键技术主要有内容存储和分发技术。

CDN 主要存储的是静态资源。比如静态的 JS、CSS 和图片等资源。还可以使用多个不同的 CDN 域名来存储,突破浏览器并发限制,节约 Cookie 带宽,节约主域名的连接数, 优化页面响应速度,防止不必要的安全问题等。

HTTP2#

HTTP2 比 HTTP1 的性能更好,但是现在还没有完全普及。

渲染层面#

渲染层面的渲染指的是浏览器渲染,这里主要指的是浏览器的渲染过程的优化。

理解浏览器渲染机制#

需要理解浏览器的渲染机制原理后,才能更好的优化渲染过程。

CSS 优化

  • CSS 选择器的读取是从右到左的,一般的话,如果使用 class 或者 id 选择器后,前面就不用画蛇添足的使用标签选择器了。

  • CSS 的放置位置。

    CSS 的样式放在 head 标签中。把<link>标签放在<head>里面是规范要求的内容。此外,这种做法可以让页面逐步呈现,提高了用户体验。将样式表放在文档底部附近,会使许多浏览器(包括 Internet Explorer)不能逐步呈现页面。一些浏览器会阻止渲染,以避免在页面样式发生变化时,重新绘制页面中的元素。这种做法可以防止呈现给用户空白的页面或没有样式的内容。

  • 使用 CDN 加速 CSS 静态文件。

JavaScript 优化

  • JavaScript 代码的放置位置。

    <script>标签恰好放在</body>之前,脚本在下载和执行期间会阻止 HTML 解析。把<script>标签放在底部,保证 HTML 首先完成解析,将页面尽早呈现给用户。例外情况是当你的脚本里包含document.write()时。但是现在,document.write()不推荐使用。同时,将<script>标签放在底部,意味着浏览器不能开始下载脚本,直到整个文档(document)被解析。也许,对此比较好的做法是,<script>使用defer属性,放在<head>中。

  • 适当使用async/defer异步加载。

    • <script> - HTML 解析中断,脚本被提取并立即执行。执行结束后,HTML 解析继续。

    • <script async> - 脚本的提取、执行的过程与 HTML 解析过程并行,脚本执行完毕可能在 HTML 解析完毕之前。当脚本与页面上其他脚本独立时,可以使用async,比如用作页面统计分析。

    • <script defer> - 脚本仅提取过程与 HTML 解析过程并行,脚本的执行将在 HTML 解析完毕后进行。如果有多个含defer的脚本,脚本的执行顺序将按照在 document 中出现的位置,从上到下顺序执行。

    注意:没有src属性的脚本,asyncdefer属性会被忽略。

    从应用的角度来说,一般当我们的脚本与 DOM 元素和其它脚本之间的依赖关系不强时,我们会选用 async。当脚本依赖于 DOM 元素和其它脚本的执行结果时,我们会选用 defer。

  • 优化 JavaScript 代码。尽量使用时间复杂度低的算法代码,提高代码性能。

  • 使用 CDN 加速 JavaScript 静态文件。

DOM 优化#

原则:减少 DOM 的操作。

减少 DOM 的操作

优先使用 JavaScript 处理,浏览器的 JavaScript 引擎和渲染引擎时独立实现的,通过桥接接口来连接。

通过 JavaScript 操作 DOM 需要昂贵的“过桥费”,尽量减少 DOM 的操作。

优化 DOM 的操作

  • 缓存 DOM 对象。
  • 在内存中操作元素。
  • 一次性 DOM 节点生成。
  • 通过类修改样式,而不是直接修改样式(多次赋值影响性能)。
  • 通过事件代理批量操作事件。

规避重绘和回流#

重绘和回流是非常消耗性能的,需要尽量减少重绘和回流的操作。以下几个方面可以注意下

CSS 方面

  • 使用 transform 替代 top

  • 使用 visibility 替换 display: none ,因为前者只会引起重绘,后者会引发回流(改变了布局

  • 避免使用 table 布局,可能很小的一个小改动会造成整个table的重新布局。

  • 尽可能在 DOM 树的最末端改变 class,回流是不可避免的,但可以减少其影响。尽可能在 DOM 树的最末端改变 class,可以限制了回流的范围,使其影响尽可能少的节点。

  • 避免设置多层内联样式,CSS 选择符从右往左匹配查找,避免节点层级过多。

<div>
  <a> <span></span> </a>
</div>
<style>
  span {
    color: red;
  }
  div > a > span {
    color: red;
  }
</style>

对于第一种设置样式的方式来说,浏览器只需要找到页面中所有的span标签然后设置颜色,但是对于第二种设置样式的方式来说,浏览器首先需要找到所有的span标签,然后找到span标签上的a标签,最后再去找到div标签,然后给符合这种条件的span标签设置颜色,这样的递归过程就很复杂。所以我们应该尽可能的避免写过于具体的 CSS 选择器,然后对于 HTML 来说也尽量少的添加无意义标签,保证层级扁平

  • 将动画效果应用到 position 属性为 absolute 或 fixed 的元素上,避免影响其他元素的布局,这样只是一个重绘,而不是回流,同时,控制动画速度可以选择requestAnimationFrame,详见探讨 requestAnimationFrame

  • 避免使用 CSS 表达式,可能会引发回流。

  • 将频繁重绘或者回流的节点设置为图层,图层能够阻止该节点的渲染行为影响别的节点,例如will-changevideoiframe等标签,浏览器会自动将该节点变为图层。

  • CSS3 硬件加速(GPU 加速),使用 css3 硬件加速,可以让transformopacityfilters这些动画不会引起回流重绘 。但是对于动画的其它属性,比如background-color这些,还是会引起回流重绘的,不过它还是可以提升这些动画的性能。

  • 对具有复杂动画的元素使用绝对定位,使它脱离文档流,否则会引起父元素及后续元素频繁回流。

JavaScript 方面

  • 避免频繁操作样式,最好一次性重写style属性,或者将样式列表定义为class并一次性更改class属性。

  • 避免频繁操作 DOM,创建一个documentFragment,在它上面应用所有DOM操作,最后再把它添加到文档中。

  • 避免频繁读取会引发回流/重绘的属性,如果确实需要多次使用,就用一个变量缓存起来。主要包括以下属性或方法:

    • offsetTopoffsetLeftoffsetWidthoffsetHeight
    • scrollTopscrollLeftscrollWidthscrollHeight
    • clientTopclientLeftclientWidthclientHeight
    • widthheight
    • getComputedStyle()
    • getBoundingClientRect()

异步更新策略#

在我们使用前端框架(Vue, React)时,修改数据(状态)时,并不是单次修改完后,立即同步改变修改的数据(状态)。而是一个异步的过程,框架会把多此修改后合并成一次统一修改,这样就可以减少数据的频繁更新操作,提高性能。

服务端渲染#

服务端渲染是服务器直接返回 HTML 字符串给浏览器,直接渲染到页面上。服务端渲染可以有效改善 SEO 问题和提高首屏加载速度。但是,服务端渲染虽然可以提高浏览器的渲染效率,但会加重服务器负担,应该谨慎使用。

一些应用#

懒加载#

一次性把所有的图片资源加载完毕,那么很可能会造成白屏、卡顿等现象。使用懒加载一步一步的展示出来,即可以减少性能压力,也不影响用户体验。

防抖和节流#

频繁触发回调导致的大量计算会引发页面的抖动甚至卡顿,使用防抖和节流来控制事件被触发的频率,提高性能。

可视化性能监测#

  • Performance 面板工具。Performance 是 Chrome 提供给我们的开发者工具,用于记录和分析我们的应用在运行时的所有活动。它呈现的数据具有实时性、多维度的特点,可以帮助我们很好地定位性能问题。
  • LightHouse 插件。生成测试报告,内容非常丰富,还可以给出了可行的建议。
  • performance API。浏览器自带的window.performanceAPI,可以获取一些较高准确度的数据,比如timing等。