前端业务开发的通用经验 - 性能优化

性能优化体系四要素:指标、监控、性能分析、优化方案。javascript

指标

什么是性能?前端业务开发关注的性能,主要有两个:加载速度、渲染效率。两者通常也合称【性能体验】。css

加载速度

衡量加载速度的传统指标通常是:首字节、DOMContentLoaded、onLoad。传统指标的问题是,彻底站在技术视角衡量,并不能表明用户实际的体验。目前受业界普遍承认的是谷歌提出的一套面向用户的指标 Progressive Web Metricshtml

  • FP(First Paint)第一个像素绘制出来了
  • FCP(First Contentful Paint)白屏结束,有可见元素出现了
  • FMP(First Meaningful Paint)对用户有意义的内容出现了
  • TTI(Time to Interactive)没有长任务阻塞,能够交互了(图中 First Idle 位置)

通常用 FCP 定义【白屏时间】,用 FMP 定义【首屏时间】。FP 和 FCP 可直接经过浏览器 api 得到,但 FMP 显然没法由浏览器来定义。通用实现的一种基本思路是检测 DOM 节点变化最大的时刻,固然最精确的仍是得靠自定义埋点,毕竟这个指标归根结底是用于帮助开发掌握页面在线上运行的实际情况,也只有开发最清楚页面内容的加载过程。TTI 最好也用自定义埋点,靠技术手段检测会比较麻烦前端

不过真从用户体验角度看,FP 和 FCP 意义也不是很大,而 FMP 又难以标准化定义,考虑到这些问题,谷歌又提出了三个新的指标:java

  • Largest Contentful Paint (LCP):页面最大的元素出现了(算法
  • Total Blocking Time (TBT):FCP 到 TTI 之间长任务的时长总和
  • Cumulative Layout Shift (CLS):衡量可视区元素向下跳动的程度(一般是由于上方元素突现或高度变化致使页面重绘)

这三个指标连同其余通用指标,归结成了一个新项目:web vitals。具体介绍可参考这篇文章:The New Generation of Performance Metrics for Better User Experiencereact

在实践中,咱们须要在关键路径上的各个节点添加监控埋点,从 url 发出请求,到 DNS 解析,到服务端处理(若是是服务端渲染的话),到 html 开始加载,到阻塞类资源加载,到业务代码开始执行,到接口返回,到内容正式渲染。这样才有利于分析到底是哪一个环节影响了最终性能。git

张克军的这张图描绘了网页通用的加载模型,在实际业务中一般还存在其余一些环节。好比移动端 hybrid 页面或 RN 页面的容器初始化过程,好比前置校验用户登陆态、获取用户定位等等。github

上述指标只是衡量了单个页面的性能,看不到产品总体的性能,所以大厂基本都在搞一个叫【秒开率】的指标。秒开率是指整个样本集中,某指标小于 1s 的占比。秒开率回答了这样一个问题:整个产品有多少页面的性能指标(通常取 90 分位线)小于 1s。web


渲染效率

渲染效率就是指页面流畅度,看交互动效是否有卡顿(掉帧)。衡量渲染效率的指标主要是 FPS。查看 FPS 最简单的办法有两个,一是用 stats.js,二是在 chrome devtools 中经过 command + shift + p 调出命令窗口,输入 fps 便可调出帧率面板。ajax

因为 UI 线程和 js 线程共用同一个线程,js 任务的运行可能致使 UI 卡顿、卡死,所以浏览器还提供了一个用于监控 long task 的 api。

参考:百度APP流畅度全流程质量监控实践(一) 流畅度现状分析


监控

一般咱们发现某个页面性能已经不好了,因而一顿专项优化,最后各项指标都达到了预设目标,但问题是怎么保证性能长期不会继续劣化呢?为了知道页面在线上运行的实际性能情况,首先就得把监控体系创建起来,能对采集到的指标数据作各类聚合展现以便随时看到页面指标数据变化,能按期自动生成报表,能对指标异常变化(好比突增、骤降)添加报警。

为了采集性能数据,浏览器提供了一系列专门用于监控性能的 API,例如 navigation-timingresource-timinguser-timing 等。通用指标可封装 sdk 进行采集上报,sdk 应该同时提供上报自定义测速点的能力。

除了线上监控,性能保障还有赖于事前防控。好比在上线流程,应该有一道性能检测环节(checklist),防止一些明显的、常见的、比较极端的性能劣化场景,好比上了一个特别大的资源而不自知(不当心全量引入了 lodash、加了张没压缩的大图、引入了无用的资源等)。最简单的思路,就是上传到 ligthouse,若是检测得分低于阈值,就直接禁止上线。这种检测既能够在上线前执行,也能够按期循环执行。


性能分析

一般监控已经能提供比较详细的性能情况数据,不过排查问题时一般还须要一些工具辅助以得到更加细节的信息。

浏览器提供了一系列衡量性能的工具,可查看资源加载时序分析网页性能分析代码执行耗时分析渲染流畅度等。

现代框架通常也提供了运行时的性能分析工具。好比 react profiler,可方便的看到组件级乃至方法级的执行耗时(针对【渲染效率】)。

其余分析工具


加载性能优化

性能优化的基本思路,是搞清楚整个加载链路出问题的环节,而后针对性的修复。具体的手段,主要有四种类型:

  • 体量规划
  • 时序规划
  • 距离规划
  • 定向优化

其实还有一种严格说只能算体验优化的手段,就是从交互上让用户感受得快,好比骨架屏,还有图片的渐进式加载(可参考 how medium does progressive image loading)。


体量规划

一般来讲,加载资源的总量越小,加载性能越好。针对 web 而言,主要是限制请求资源的体积。单从资源体积的角度看,理想状况是彻底的按需加载,即每时每刻仅加载当前须要的资源。

  • 只请求当前环境须要的资源
  • 减少资源体积(雅虎14条之四、10)
    • uglify
    • Gzip(服务端开启,只压缩文本文件,不要压缩图片这类的二进制文件,缘由
    • 图片优化(压缩、像移动端同样针对屏幕尺寸和分辨率加载不一样大小/质量的图片、根据网络情况加载不一样质量的图片、用 webp 格式)
    • 控制 cookie 大小
    • 控制 header 大小
    • 用字符数最少的代码:例如用 void 0 代替 undefined
  • 减少无效资源(雅虎14条之四、12)

时序规划

小学奥数里有个主题叫统筹规划,其中有一类问题就是看如何安排各类事情的执行流,以最小化总时间。时序规划也是相似,最基本的两种思路,就是并发和前置,要么一块儿搞,要么提早搞。

  • 并行
  • 前置
    • SSR:将模板渲染前置到服务端处理
    • prefetch、prerender、preload
    • 容器预初始化
    • 管道:无需等全部数据加载完才处理,而是加载一部分就处理一部分(html 的渲染就是这样的)

距离规划

距离规划的基本原理是:传输速度有上限,所以距离越近,时间越短。距离最近的是寄存器/内存,最远的是服务器。

  • CDN (雅虎14条之2)
  • 缓存
    • http 缓存(雅虎14条之三、13)
    • 使用可缓存的 get 请求(雅虎14条之14)
    • 服务器缓存
    • localStorage
    • web worker
  • kv-storage
  • stale-while-revalidate
  • 避免重定向(雅虎14条之11)
  • 服务端推送(单向传输,避免一来一回)

一般可以被缓存而且缓存能起到较大做用的,是不常变化、会被反复用到的静态信息。常常变化的信息,或者不多重复使用的信息,会影响缓存的命中率。要利用缓存,在设计上就须要考虑动静分离,好比与用户状态无关的配置信息和与用户状态相关的动态信息分开使用不一样接口。

为了让常常变化的数据也能使用缓存来提升效率,也有一种思路:每次都先从缓存里取数据,而后每次都发送请求更新缓存,以时间换空间,以 1 次延迟的代价,来提升接口请求的性能。这种策略还有个专门的规范叫 stale-while-revalidate,基于 react hook 实现的网络请求方法库 swr 已经内置了该策略。


定向优化

前三种思路属于通用思路,好比 CPU 的性能优化也会采用这些思路。而定向优化是指针对环境的特征,提供专门的优化方案。就 Web 而言,特定环境主要是指:浏览器的请求、加载和渲染机制;http 协议;js 引擎。

针对浏览器机制


针对 http 1.1

  • 减小请求次数(雅虎14条之1)
    • 合并资源(bundle / spites)或合并资源的请求(CDN Combo)
    • 合并多个 ajax 请求
    • CSS inline
    • 使用 CSS、SVG、Inline Image、Icon-font 代替图片
    • 避免使用 @import 和 iframes
    • 控制域名数量,减小 DNS 查询(雅虎14条之9)
  • 选择更先进的协议:UDP、QUIC、SPDY、http 2
  • 《Web 性能权威指南》

针对 js 引擎

  • 使用更高效的 api
    • jsperf.com/
    • 编写有利于引擎优化的代码。好比按照 asm.js 规范编写的代码,将直接得到引擎层面的优化支持
    • 《High Performance Javascript》

优化手段有不少,但收益并不相同,像针对 js 引擎的优化,基本只有框架会去作。有明显收益的,主要是缓存、按需加载、减少资源体积、请求并发/前置,可参考淘宝天猫首页性能对赌优化回忆录(连接: pan.baidu.com/s/1mgILtDfD… 提取码: impp),能够说是把这些手段用到了极致。


渲染效率优化

优化渲染效率,提升交互反馈及时性和动画流畅度,也能够运用和加载性能优化同样的思路。提升动画帧率的一个关键点,是处理动画渲染和 js 的关系,避免在动画期间执行 js 长任务,毕竟 60FPS 意味着每一帧只有 16ms,若是这 16ms 全用来执行 js 了,那么动画必然会因掉帧而出现卡顿。

  • 体量规划
    • 按需绘制,只渲染须要的元素
    • 函数节流,下降 js 执行频率
    • 优雅降级,例如针对低端设备,动效元素少用阴影和渐变色(纯色、扁平化)
  • 时序规划
    • 提早渲染好组件,再执行动画
  • 距离规划
    • 离线更新(例如使用 Document Fragment、离屏 canvas)
  • 浏览器渲染层面
    • 控制 DOM 和 CSS 的层级深度
    • 合并多个 DOM 操做
    • 少用 js 动画,如必要,使用专门的动画 api:requestAnimationFrame
    • 使用 requestIdleCallback 执行 js 任务
    • 硬件加速(hardware-acceleration
    • 减小重排
  • 其余参考
相关文章
相关标签/搜索