性能优化
性能优化是每个前端开发同学必须要了解和掌握的,工作中时时刻刻都用得到。性能优化涉及到多个方面,需要了解更多基础知识,大概分3个方向:资源加载,浏览器渲染,代码执行
性能优化目的
- 减少耗时:浏览器花更少的时间渲染页面;用户操作了之后更快的响应
- 减少资源消耗:访问页面消耗更少的带宽资源;部署应用占有更少的服务器资源;
性能优化场景
1、资源加载
资源加载过程: 浏览器 -> http -> server -> 资源
- 浏览器阶段
- 1、DNS缓存
- dns-prefetch :
<link rel="dns-prefetch" href="//example.com">预先 DNS 查询
- dns-prefetch :
- 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 ,谨慎使用
- prefetch :
- 3、离线包
- 需要客户端配合,在加载资源的时候,不去server拉取资源,而是使用客户端缓存的资源
- 1、DNS缓存
- 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'
- 1、长连接: 设置http 头
- 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 元素调整
- 滑动加载 :仅渲染可视窗口, 剩余部分滑动加载,可使用
IntersectionObserverwebApi, 多用于信息流页面 - 元素销毁 :页面超出可视窗口, 销毁 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的任务需拆解任务放置下个循环,给到用户响应 和 页面更新渲染时间