请说下前端性能优化有哪些方式?

79 阅读8分钟

前端性能优化可以分成两部分来讲

  1. 加载性能优化
  2. 渲染性能优化

加载性能优化

本质:
1. 减少请求次数;
2. 减少请求资源的大小;
3. 网络优化;

加载性能优化方式:

  • 减少请求次数
为什么减少请求次数?
    浏览器能够并行发出请求,但是每次并行发出请求的个数是有限制的,以chrome为例:
        同一域名下,同一GET请求的并发数是1,也就是说上一个请求结束,才会执行下一个请求,否则置入队列等待发送;
        同一域名下,不同GET/POST请求的并发数量是6。当发送的请求数量达到6个,并且都没有得到响应时,后面的请求会置入队列等待发送。
    所以资源请求数过多肯定比请求数少更耗时,直接影响网页的加载速度;
减少请求次数方式: 
1)图片资源
    + CSS雪碧图技术
        把一些常用、重复使用的小图合并成一张大图,使用的时候通过背景图片定位(background-position),定位到具体的某一张小图上;
        1、UI给图;
        2、webpack插件:webpack-spritesmith

    + 图片懒加载
        视区外图片先不加载,当进入视区或者进入视区之前的某个位置加载;
        1、css的loading属性;
        2、getBoundingClientRect方法,获取dom元素的top、left、bottom、right、height、width信息,对比视区大小,进行图片加载(需要结合节流);
        3、IntersectionOberser方法,能够监听元素是否到达当前视口的事件;

    + base64编码
        图片的base64编码就是可以将一张图片数据编码成一串字符串,使用该字符串代替图像地址url;
        (均衡css体积增大和http请求减少之间的收益)
        
2)合理利用缓存
    + 浏览器缓存(资源)
        强缓存
        协商缓存
        
    + DNS缓存(DNS查找时间)

    + 分包加载 (Bundle Spliting)
        避免一行代码修改导致整个 bundle 的缓存失效   
        
3)合并CSS和JS文件
    将CSS和JavaScript文件合并为单独的文件。合并CSS和JavaScript文件是减少HTTP请求数量和提高网站加载速度的有效方法;
  • 减少资源大小
相同网络环境下,更小体积意味着传输速度更快;
减少资源大小方式:
1)资源压缩
    + js、css、html资源压缩
        + JS代码压缩
          mode:production,使用的是terser-webpack-plugin
               module.exports = {
                   // ...
                   optimization: {
                   minimize: true,
                   minimizer: [new TerserPlugin({}),]
                   }
               }
        + CSS代码压缩
          css-minimizer-webpack-plugin
            module.exports = {
              // ...
              optimization: {
                minimize: true,
                minimizer: [
                  new CssMinimizerPlugin({})
                 ]
             }
            }

        + Html文件代码压缩
          module.exports = {
              ...
              plugin:[
                 new HtmlwebpackPlugin({
                   ...
                   minify:{
                     minifyCSS:false, // 是否压缩css
                     collapseWhitespace:false, // 是否折叠空格
                     removeComments:true // 是否移除注释
                    }
                  })
                ]
          }
          设置了minify,实际会使用另一个插件html-minifier-terser


2) 图片资源压缩
    图片压缩工具:tinyPng等
    图片格式:,webp 普遍比 jpeg/png 更小,而 avif 又比 webp 小一个级别

    如何鉴别浏览器是否支持webp?
        通过canvas来判断(这个比较常用),创建一个canvas元素,然后把它转成image/webp格式的data_url,如果这个data_url里面包含webp,则代表当前浏览器支持webp格式, 反之则不支持:
        document.createElement('canvas').toDataURL('image/webp').indexOf('data:image/webp')
        在服务端根据请求header信息判断浏览器是否支持webp:在图片请求发出的时候,Request Headers 里有 Accept,服务端可以根据Accept 里面是否有 image/webp 进行判断。
        通过加载一张 webp 图片进行判断:先加载一个WebP图片,如果能获取到图片的宽度和高度,就说明是支持WebP的;


3)Tree Shaking技术
    Tree Shaking: 无用导出将在生产环境进行删除,到达减少资源体积的效果,依赖于ES Module的静态语法分析

    Tree shaking是基于ES6模板语法(importexports),主要是借助ES6模块的静态编译思想,在编译时就能确定模块的依赖关系,以及输入和输出的变量
    Tree shaking无非就是做了两件事:
        1. 编译阶段利用ES6 Module判断哪些模块已经加载
        2. 判断那些模块和变量未被使用或者引用,进而删除对应代码

    在webpack实现Tree shaking有两种不同的方案:
    usedExports:通过标记某些函数是否被使用,之后通过Terser来进行优化的
    sideEffects:跳过整个模块/文件,直接查看该文件是否有副作用
        + usedExports
            配置方法也很简单,只需要将usedExports设为true
            module.exports = {
                ...
                optimization:{
                    usedExports
                }
            }

4)代码分离
    将代码分离到不同的bundle中,之后我们可以按需加载,或者并行加载这些文件
    默认情况下,所有的JavaScript代码(业务代码、第三方依赖、暂时没有用到的模块)在首页全部都加载,就会影响首页的加载速度
    代码分离可以分出更小的bundle,以及控制资源加载优先级,提供代码的加载性能
    这里通过splitChunksPlugin来实现,该插件webpack已经默认安装和集成,只需要配置即可

  • 网络优化
1)CDN:就近原则
2)Http2.0
    多路复用,在浏览器可并行发送 N 条请求。
    首部压缩,更小的负载体积。
    请求优先级,更快的关键请求
  • 路由懒加载
  • 第三方组件按需加载
  • ···

渲染性能优化

浏览器渲染过程:
    解析HTML生成DOM树。
    解析CSS生成CSSOM规则树。
    解析JS,操作 DOM 树和 CSSOM 规则树。
    将DOM树与CSSOM规则树合并在一起生成渲染树。
    遍历渲染树开始布局,计算每个节点的位置大小信息。
    浏览器将所有图层的数据发送给GPU,GPU将图层合成并显示在屏幕上。
    重排
        当改变 DOM 元素位置或大小时,会导致浏览器重新生成渲染树,这个过程叫重排。
    重绘
        当重新生成渲染树后,就要将渲染树每个节点绘制到屏幕,这个过程叫重绘。

        不是所有的动作都会导致重排,例如改变字体颜色,只会导致重绘。
        重排和重绘这两个操作代价非常大,因为 JavaScript 引擎线程与 GUI 渲染线程是互斥,它们同时只能一个在工作,因此重排和重绘会阻塞主线程。

渲染性能优化方式:

  • 资源加载优先级控制

    + preload/prefetch 可控制 HTTP 请求优先级,从而达到关键请求更快响应的目的;
        link 标签的 preload 属性:用于提前加载一些需要的依赖,这些资源会优先加载;
        link 标签的 prefetch 属性:利用浏览器的空闲时间,加载页面将来可能用到的资源的一种机制;通常可以用于加载其他页面(非首页)所需要的资源,以便加快后续页面的打开速度
        dns-prefetch,可对主机地址的 DNS 进行预解析。
    
    + js和css的引入位置/script类型设置
        css引入放在head便签尾部,css影响renderTree的构建,会阻塞页面的渲染
        script脚本放在body标签尾部,js会阻塞dom解析
    
    + 避免js阻塞
        js可以修改CSSOM和DOM,因此js会阻塞页面的解析和渲染,并且会等待css资源的加载。
        也就是说js会抢走渲染引擎的控制权。所以我们需要给js资源添加defer或者async,延迟js脚本的执行。
        脚本与DOM/其它脚本的依赖关系很强:对<script>设置defer
        脚本与DOM/其它脚本的依赖关系不强:对<script>设置async
    
    + 降低css选择器的复杂度
        浏览器读取选择器,遵循的原则是从选择器的右边到左边读取。
        减少嵌套:最多不要超过三层,并且后代选择器的开销较高,慎重使用
        避免使用通配符,对用到的元素进行匹配即可
        利用继承,避免重复匹配和定义
        正确使用类选择器和id选择器
    
    + 使用事件委托
    
    + 尽量不要使用JS动画
        css3动画和canvas动画都比JS动画性能好
    
  • 减少重排重绘

    减少页面DOM操作;
        对 DOM 元素执行一系列操作,可以将 DOM 元素脱离文档流,修改完成后,再将它带回文档。
            例如:隐藏元素(display:none)、文档碎片(DocumentFragement)等(虚拟dom);
        用 JavaScript 修改样式时,最好不要直接修改单个样式属性,而是替换 class 来改变样式;
    
  • 合理使用防抖和节流;

  • 利用缓存: 页面缓存(keep-alive),接口缓存(减少数据更新导致的页面刷新)

  • Web Worker: 用于那些处理纯数据,或者与浏览器 UI 无关的长时间运行脚本;