前端性能优化

140 阅读5分钟

性能优化

性能优化是每个前端开发同学必须要了解和掌握的,工作中时时刻刻都用得到。性能优化涉及到多个方面,需要了解更多基础知识,大概分3个方向:资源加载,浏览器渲染,代码执行

性能优化目的

  • 减少耗时:浏览器花更少的时间渲染页面;用户操作了之后更快的响应
  • 减少资源消耗:访问页面消耗更少的带宽资源;部署应用占有更少的服务器资源;

性能优化场景

1、资源加载

资源加载过程: 浏览器 -> http -> server -> 资源

  • 浏览器阶段
    • 1、DNS缓存
      • dns-prefetch : <link rel="dns-prefetch" href="//example.com"> 预先 DNS 查询
    • 2、预加载
      • prefetch : <link rel="prefetch" href="example.js"> 空闲的时候预先下载,仅下载。一般用于下个页面
      • preload : <link rel="preload" href="style.css" as="style"> 预先下载,加载,解析(所以需要 as="style" 指明文件类型). 而不是等到解析到标签时才下载
      • modulepreload : 于 preload 类似,专门用于 ES Moudle
      • preconnect : <link rel="preconnect" href="https://example.com"> 预先建立 http 连接
      • prerender : 预先加载,解析,执行,渲染页面。 场景非常少,消化内存,cpu ,谨慎使用
    • 3、离线包
      • 需要客户端配合,在加载资源的时候,不去server拉取资源,而是使用客户端缓存的资源
  • http阶段
    • 1、长连接: 设置http 头'Connection': 'keep-alive'
    • 2、http2: 多路复用 : 突破浏览器请求6-8并发数, 二进制:减少解析时间 Header压缩:HPACK算法 服务端和客户端都维护索引表
    • 3、强缓存: Expires (过期时间) max-age( 缓存时间 ) 一般用于静态资源 (磁盘缓存)
    • 4、协商缓存: Client : If-Modified-Since/If-None-Match Server : Last-Modified/Etag 返回 304 码
    • 5、gzip: 'Content-Encoding': 'gzip'
  • server阶段
    • 1、硬件 ( cpu , 内存 ,网卡 ) , 扩容
    • 2、读写分离
    • 3、CDN 分发
  • 资源阶段
    • 1、压缩
    • 2、Tree Shaking 文件合并 bundler , 雪碧图, base64 , webp
    • 3、拆分路由chunk

2、浏览器渲染

浏览器构成

浏览器一般有多个进程构成,进程中又采用了多线程模式

  • 浏览器主进程: 协调,管理
  • 渲染进程:处理网页渲染,执行js等
    • 主线程:负责解析 HTML CSS JS , 构建DOM
    • JS引擎线程:V8 负责解析js,并执行js
    • 渲染线程:构建渲染树,布局,并绘制
    • 其他线程:网络,定时器,事件等
  • 网络进程:处理资源加载
  • 插件进程:管理插件
  • GPU进程:处理GPU任务

浏览器渲染过程:

  • 解析HTML , 构建 DOM 树
  • 处理CSS ,构建 CSSOM 树
  • 合并构建渲染树
  • 布局,绘制
  • 上面流程中,遇到 <script> 会暂停,加载,解析,执行完了再继续流程。
  • JS 修改 DOM CSS 会触发浏览器再次执行上面4步,绘制最新页面

浏览器在加载 HTML 后,是一行一行的解析,每一步都是阻塞的,遇到 css/js 资源,会去加载解析执行完了,再解析下一行。 ( 显然这里不是最优解,性能优化关注点 )

性能优化

  • HTML 内容结构调整

    • CSS 放头部,JS放尾部
    • 使用 预加载 (prefetch,preload) 提高空闲资源利用率 , 异步路由,异步组件
    • script 标签使用 async defer 异步加载外部js文件 , async 加载 立即执行( 适合不操作DOM的外部lib ) , defer 加载后 DOMContentLoaded 后执行。( 适合操作 DOM 的外部lib )
  • DOM 元素调整

    • 滑动加载 :仅渲染可视窗口, 剩余部分滑动加载,可使用 IntersectionObserver webApi, 多用于信息流页面
    • 元素销毁 :页面超出可视窗口, 销毁 DOM , 避免超大 DOM 树( 建立包括必要数据,快速重绘 )
    • 骨架屏占位 提升页面体检
  • DOM 元素修改

    • 元素修改 : 使用重绘( 修改颜色,背景等 )避免回流( 修改位置,大小 )。回流会影响文档流,进而导致整个页面重新渲染
    • 元素新增 : 使用框架(vue,react的虚拟DOM,diff算法)、使用 DocumentFragment 替代直接操作 DOM
    • 运用BFC : 独立块级上下文(overflow,display,position),从而减少回流的范围
  • 事件

    • 事件委托 : 减少事件绑定
  • 动画

    • CSS3 Animation : 用 CSS3 动画替代 JS动画。 可以用到GPU加速
    • requestAnimationFrame : 保持屏幕刷新频率同步。
  • Page Visiblility

    • document.visibilityState 不可视时,减少一些不必要资源的运行。 可视后重新运行

    (注意) :不可视的时候,动画或者setTimeout , setInterval等API,是不运行的。可能会导致一些异常,比如按秒计数

3、程序运行时

运行时的性能优化,在大型项目中显得更为重要,关键。 上面是常量, 程序运行时是变量。 随着业务迭代,每时每刻都可能引入新的问题

  • 变量
    • 避免使用全局: 作用域链更长,访问速度更慢
    • 保持变量类型稳定:减少变量的类型转换,有助于V8推测类型 ,也是 TS 的作用之一。
    • 正确使用运算符:数字运算用运算符, 字符串拼接用 `` 符号,也有助与V8优化
    • map set: 用来替代一般Object ,O(1)的时间复杂度,可以提升访问速度
    • WeakMap WeakSet : 有助于 GC
    • for : 避免 for in 会枚举到原型链, 减少 for 中的操作 i < len 替代 i < arr.length
    • 遍历 : 减少不必要的遍历,Array 下的很多方法,要使用得当。
    • 数据结构 : 对于复杂业务极端情况下需要优化数据结构
  • 函数
    • 防抖: 不抖了再执行(简单记忆)用于避免频繁调用
    • 节流: 一节一节的执行(简单记忆)用户避免频繁调用
    • 纯函数:相同输入->相同输出。 纯函数可以被memoized
    • 柯里化(currying): 可以用于固化部分参数,缓存部分计算结果
  • 运行时
    • 少占用主线程 : 使用异步任务,或者 事件循环
    • 避免耗时很久 : 使用 web worker 异步进行 , 或者 使用 WebAssembly
    • 大任务拆解: 大任务执行时间长,可能会导致卡顿。 大于50ms的任务需拆解任务放置下个循环,给到用户响应 和 页面更新渲染时间