前端性能优化的一些方向

135 阅读11分钟

前端性能优化大体方向

体系

从输入 URL 到页面加载完成,发生了什么?

  • DNS 解析
  • TCP 连接
  • HTTP 请求抛出
  • 服务端处理请求,HTTP 响应返回
  • 浏览器拿到响应数据,解析响应内容,把解析的结果展示给用户

网络层面(HTTP请求/响应)

减少请求次数

webpack 的优化瓶颈,主要是两个方面:
  • 构建过程太花时间
  • 打包的结果体积太大
webpack 优化:
  • 构建过程策略

    • ①不要让 loader 做太多事情
    • ②不要放过第三方库
    • ③使用Happypack
  • 构建结构体积压缩

    • ④文件结构可视化,找出导致体积过大的原因
    • ⑤拆分资源
    • ⑥删除冗余代码
    • ⑦按需加载
    • ⑧Gzip 压缩

①用 include 或 exclude 来帮我们避免不必要的转译

  • 如庞大的 node_modules 文件夹
  • loader 增加相应的参数设定缓存

②使用DllPlugin(dll)插件版本库打包业务代码

③Happypack将 loader 由单进程转为多进程

④使用webpack-bundle-analyzer插件分析打包较大文件

⑤同②

⑥Tree-Shaking处理模块级别的冗余代码

  • uglifyjs-webpack-plugin已集成webpack4当中
  • 配置 optimization.minimize 与 optimization.minimizer 来自定义压缩操作

⑦路由按需加载import()进行异步动态加载

⑧ request headers加上accept-encoding:gzip

  • 服务端压缩:消耗服务器性能添加服务器压力
  • webpack客户端压缩:compression-webpack-plugin
  • vite客户端压缩:vite-plugin-compression
  • 客户端压缩也需要nginx启动gzip模块
图片文件优化
jpeg/jpg:有损压缩、体积小、加载快、不支持透明
  • 大的背景图、轮播图或 Banner 图
PNG-8 与 PNG-24
  • 无损压缩、质量高、体积大、支持透明
  • 呈现小的 Logo、颜色简单且对比强烈的图片或背景
SVG:文本文件、体积小、不失真、兼容性好
  • 图标等矢量图
Base64:文本文件、依赖编码、小图标解决方案
  • 作为雪碧图的一个补充
  • 浏览器可以理解这个字符串解码而不需去发送 HTTP 请求
  • 图片的实际尺寸很小,无法以雪碧图的形式与其它小图结合或更新频率非常低
  • webpack 的 url-loader可结合文件大小有必要地自动准换Base64
webp:
  • Google 专为 Web 开发的一种旨在加快图片加载速度的图片格式,它支持有损压缩和无损压缩,使用需处理兼容性问题

减少单次请求所花费的时间

缓存:
  • Memory Cache
  • Service Worker Cache
  • HTTP Cache
  • Push Cache
HTTP 缓存机制
强缓存:
  • 利用 http 头中的 Expires 和 Cache-Control 两个字段来控制
  • expires,在Response Headers 中将过期时间写入 expires 字段
  • 是一个时间戳,试图再次向服务器请求资源,浏览器就会先对比本地时间和 expires 的时间戳,未过期则去缓存取(服务器与客户端可能存在时差)
  • Cache-Control 中的max-age 字段也允许我们通过设定相对的时间长度来达到同样的目的
  • public 与 private 是针对资源是否能够被代理服务缓存,private(默认)表示该资源只能被浏览器缓存
  • no-cache 绕开了浏览器(协商缓存),no-store 不使用任何缓存策略
  • Cache-Control 的 max-age 配置项相对于 expires 的优先级更高
协商缓存
  • 服务器缓存资源未改动(Not Modified),资源会被重定向到浏览器缓存,这种情况下网络请求对应的状态码是 304
  • Last-Modified 是一个时间戳会被 Response Headers 返回,每次请求时,则带上一个叫 If-Modified-Since 的时间戳字段,它的值正是上一次 response 返回给它的 last-modified 值
  • Etag 是由服务器为每个资源生成的唯一的标识字符串,Etag 在感知文件变化上比 Last-Modified 更加准确,优先级也更高。当 Etag 和 Last-Modified 同时存在时,以 Etag 为准
本地存储
  • Cookie:

    • 说白了就是一个存储在浏览器里的一个小小的文本文件,它附着在 HTTP 请求上,在浏览器和服务器之间“飞来飞去”,只能有 4KB,过量的 Cookie 会带来巨大的性能浪费,同一个域名下的所有请求,都会携带 Cookie
  • Web Storage:根据浏览器的不同,存储容量可以达到 5-10M 之间

  • Local Storage  永远不会过期

  • Session Storage  临时性的本地存储,它是会话级别的存储

  • IndexedDB:运行在浏览器上的非关系型数据库

CDN的缓存
  • CDN 指的是一组分布在各个地区的服务器。
  • 这些服务器存储着数据的副本,因此服务器可以根据哪些服务器与用户距离最近,来满足数据的请求
  • CDN 提供快速服务,较少受高流量影响
CDN 的核心点有两个,一个是缓存,一个是回源。
  • “缓存”就是说我们把资源 copy 一份到 CDN 服务器上这个过程
  • “回源”就是说 CDN 发现自己没有这个资源(一般是缓存的数据过期了),转头向根服务器(或者它的上层服务器)去要这个资源的过程

CDN 往往被用来存放静态资源,不需要业务服务器进行计算即得的资源

渲染层面

客户端渲染

  • 服务端把静态文件发送给客户端,客户端加载文件之后在浏览器里跑一遍 JS,根据运行结果生成 DOM
  • 页面上呈现的内容,你在 html 源文件里里找不到

服务端渲染SSR

  • 当用户第一次请求页面时,由服务器把需要的组件或页面渲染成 HTML 字符串,然后把它返回给客户端
  • 页面上呈现的内容,我们在 html 源文件里也能找到
  • 解决SEO问题,加快首屏加载速度
服务端渲染本质上是本该浏览器做的事情,分担给服务器去做
  • 服务器稀少而宝贵,需要消耗大量的服务器性能

浏览器内核:渲染引擎和JS引擎

  • HTML 解释器、CSS 解释器、图层布局计算模块、视图绘制模块与JavaScript 引擎

    • HTML 解释器:将 HTML 文档经过词法分析输出 DOM 树。
    • CSS 解释器:解析 CSS 文档, 生成样式规则。
    • 图层布局计算模块:布局计算每个对象的精确位置和大小。
    • 视图绘制模块:进行具体节点的图像绘制,将像素渲染到屏幕上。
    • JavaScript 引擎:编译执行 Javascript 代码
  • 流程:

    • 解析 HTML->计算样式->计算图层布局->绘制图层->整合图层,得到页面
    • 基于 HTML 构建一个 DOM 树,这棵 DOM 树与 CSS 解释器解析出的 CSSOM 相结合成布局渲染树。最后浏览器以布局渲染树为蓝本,去计算布局并绘制图像,之后每当一个新元素加入到这个 DOM 树当中,浏览器便会通过 CSS 引擎查遍 CSS 样式表,找到符合该元素的样式规则应用到这个元素上,然后再重新去绘制它
  • CSS 选择符是从右到左进行匹配的

    • 避免使用通配符,只对需要用到的元素进行选择
    • 通过继承实现的属性,避免重复匹配重复定义
    • 少用标签选择器,用类选择器替代
阻塞:HTML、CSS 和 JS,都具有阻塞渲染的特性
CSS阻塞
  • 浏览器在构建 CSSOM 的过程中,不会渲染任何已处理的内容。即便 DOM 已经解析完毕了,仍会阻塞构建
  • CSS 是阻塞渲染的资源。需要将它尽早、尽快地下载到客户端,以便缩短首次渲染的时间
JS阻塞
  • 本质上都是对 DOM 和 CSSDOM 进行修改,JS 的执行会阻止 CSSOM,在我们不作显式声明的情况下,它也会阻塞 DOM

  • JS 引擎是独立于渲染引擎存在的。我们的 JS 代码在文档的何处插入,就在何处执行。当 HTML 解析器遇到一个 script 标签时,它会暂停渲染过程,将控制权交给 JS 引擎进行了阻塞

  • 使用 defer 和 async 来避免不必要的阻塞

    • async 模式下,JS 不会阻塞浏览器做任何其它的事情。它的加载是异步的,当它加载结束,JS 脚本会立即执行
    • defer 模式下,JS 的加载是异步的,执行是被推迟的。等整个文档解析完成、DOMContentLoaded 事件即将被触发时,被标记了 defer 的 JS 文件才会开始依次执行

DOM 优化

DOM 为什么这么慢
  • JS 引擎和渲染引擎(浏览器内核)是独立实现的。当我们用 JS 去操作 DOM 时,本质上是 JS 引擎和渲染引擎之间进行了“跨界交流”,因此需要减少对DOM的操作
  • 对 DOM 的修改触发了渲染树(Render Tree)的变化所导致的回流和重绘

回流(重排):修改引发了 DOM 几何尺寸的变化; 重绘:对 DOM 的修改导致了样式的变化、却并未影响其几何属性

给DOM提速:减少 DOM 操作、避免过度渲染
  • 减少 DOM 操作的核心思路,就是让 JS 去给 DOM 分压
  • DOM Fragment 它们本质上都作为脱离了真实 DOM 树的容器出现,用于缓存批量化的 DOM 操作

渲染时机中的EventLoop:宏任务队列微任务队列

  • 初始状态:调用栈空
  • 全局上下文(script 标签)被推入调用栈,同步代码执行
  • 产生的微任务优先继续执行清空
  • 执行渲染操作,更新界面(重点)
  • 检查Web worker 任务对其处理
  • 上述过程循环往复,直到两个队列都清空
渲染的时机
  • 包装成宏任务,因为 script 脚本本身是一个 macro 任务,所以本次执行完 script 脚本之后,下一个步骤就要去处理 micro 队列了,再往下就去执行了一次 render,宏任务在下一轮循环中执行,因此这一次的render其实是一次无效的render

  • 包装成微任务,micro-task 处理完,DOM 修改好了,紧接着就可以走 render 流程了——不需要再消耗多余的一次渲染,不需要再等待一轮事件循环,直接为用户呈现最即时的更新结果

回流与重绘:重绘不一定导致回流,回流一定会导致重绘

  • 触发回流:

    • 改变 DOM 元素的几何属性
    • 改变 DOM 树的结构
    • 获取一些特定属性的值属性:offsetTop、offsetLeft、 offsetWidth、offsetHeight、scrollTop、scrollLeft、scrollWidth、scrollHeight、clientTop、clientLeft、clientWidth、clientHeight 这些属性需要通过即时计算得到。
  • 规避:

    • 利用变量缓存起来,避免频繁改动
    • 避免逐条改变样式,使用类名去合并样式
    • display: none进行dom离线修改完后再放回去
    • 浏览器自己缓存了一个 flush 队列,把我们触发的回流与重绘任务都塞进去,待到队列里的任务多起来、或者达到了一定的时间间隔,或者“不得已”的时候,再将这些任务一口气出队,”不得已“指上面“即时性”的属性

应用篇

首屏体验:lazy load

  • “懒加载”,它是针对图片加载时机的优化,在一些图片量比较大的网站如果我们尝试在用户打开页面的时候,就把所有的图片资源加载完毕,那么很可能会造成白屏、卡顿等现象
当前可视区域的高度
const viewHeight = window.innerHeight || document.documentElement.clientHeight 
元素距离可视区域顶部的高度
getBoundingClientRect() 方法来获取返回元素的大小及其相对于视口的位置

// num用于统计当前显示到了哪一张图片,避免每次都从第一张图片开始检查是否露出
    let num = 0
    function lazyload(){
        for(let i=num; i<imgs.length; i++) {
            // 用可视区域高度减去元素顶部距离可视区域顶部的高度
            let distance = viewHeight - imgs[i].getBoundingClientRect().top
            // 如果可视区域高度大于等于元素顶部距离可视区域顶部的高度,说明元素露出
            if(distance >= 0 ){
                // 给元素写入真实的src,展示图片
                imgs[i].src = imgs[i].getAttribute('data-src')
                // 前i张图片已经加载完毕,下次从第i+1张开始检查是否露出
                num = i + 1
            }
        }
    }
    // 监听Scroll事件
    window.addEventListener('scroll', lazyload, false);
“节流”

判断上次触发的时间和本次触发的时间差是否大于时间间隔的阈值

“防抖”

每次事件被触发时,都去清除之前的旧定时器

可视化监测

  • Performance是 Chrome 提供给我们的开发者工具,用于记录和分析我们的应用在运行时的所有活动

    • FPS:这是一个和动画性能密切相关的指标,它表示每一秒的帧数
    • CPU:表示CPU的使用情况,不同的颜色片段代表着消耗CPU资源的不同事件类型
    • NET:粗略的展示了各请求的耗时与前后顺序
  • Lighthouse 是一个开源的自动化工具,用于改进网络应用的质量,它生成的是一个报告!

思维导图:

前端性能优化方向.png

内容来源:修言-前端性能优化原理与实践