前端性能优化
通常性能和体验是相关的, 抛开体验谈性能是没有意义的, 性能优化的最终目的是在性能和体验总达到一个平衡, 使用户能在浏览网站时感受的舒适、自然.
使用 RAIL 模型来评估网站
RAIL 是一个以用户为中心的性能模型, 它提供一种考虑性能的结构哦. 该模型将用户的体验分解为一些关键操作如点击、滚动、加载等, 并定义每个操作的性能目标.RAIL 代表 Web 应用生命周期的四个不同方面: 响应、动画、空闲和加载. 用户对这些上下文中的每一个都有不同的性能期望, 因此居于上下文和关于用户如何感知延迟的UX研究来定义性能目标
人类非常擅长跟踪运动, 当动画不流畅时, 人能轻易感受到卡顿.
用户对性能延迟的感知
0~16ms. 感觉很流畅, 通常也是浏览器的刷新率 😁
0~100ms. 感觉响应是实时的 😁
100~1000ms 可以感觉到是异步的, 通常是加载操作的延时. 🙂
1000ms+ 可以明显感受到延时, 可能导致失去对任务的关注. 🙁
10000ms+ 通常会失去耐心, 进而放弃任务 😭
根据设备和网络环境的不同, 用户对延迟的理解也不同. 通常认为在 Wi-Fi 环境下, 延迟会更低, PC 机加载网站会在 1s 内完成, 而较慢的移动设备在 3G 环境下则需要更多的时间, 通常认为 5s 是更现实的目标.
目标和原则
响应: 在 50ms 内处理事件
动画: 在 10ms 内产生一帧
空闲: 最大化空闲时间, 以更快速的响应用户事件
加载: 在 5s 内加载完成
浏览器提供了多种工具来审查网站的性能
用 Throttle your CPU 来模拟低性能设备
用 Throttle the network 来模拟较慢的链接
用 View main thread activity 来查看录制时主线程上发生的事件
在 View main thread activities in a table 中依据占用最多的时间的活动进行排序
用 Analyze frames per second 衡量动画是否真正流畅
使用 Performance Monitor 来监视 CPU 使用, JS 堆大小, DOM 节点, 每秒布局等
使用 Visulize network requests 来可视化网络请求
使用 Caputre screenshots while recording 在录制时捕获屏幕快照, 以准确播放页面加载或动画触发时页面的样子
使用 View interactions 来快速识别页面与用户交互后的情况
使用 Scroll performance issue 来发现滚动时性能问题
使用 View paint events 来准确识别损害动画性能的绘画事件
网络性能
一个完整的 HTTP 请求需要经过 DNS 查找、TCP 握手、SSL 握手、数据请求、数据响应、链接关闭等过程
可以在浏览器的网络面板中查看时序信息, 如果使用了服务端timing, 可以通过响应头 server-timing 返回, 会在时序信息中展示, 方便定位网路问题
server-timing: inner; dur=8
server-timing: cdn-cache;desc=MISS,edge;dur=0,origin;dur=23
可以通过执行 performance.getEntriesByType('resource') 来查看具体的时间参数
Queuing: 排队的时间
有更高优先级的请求, 通常图片等媒体资源会被认为优先级低
超过最大的 TCP 链接数, 仅适用于 HTTP/1.0 和 HTTP/1.1.
浏览器正在磁盘缓存中短暂分配空间
Stalled: 请求因 Queuing 的原因暂停.
Proxy negotitation: 和代理服务协商的时间
DNS 查询: DNS 解析的时间
Initial connection: 建立链接的时间, 包括 TCP 握手/重试、协商 SSL 的时间
SSL: 建立 SSL 链接的时间
Request sent: 发送请求
ServiceWorker Preparation: 启动 service Worker 的时间
Request to ServiceWorker: 请求被发送到 service worker 的时间
Waiting(TTFB): 浏览器等待第一个响应字符的时间
Content Download: 接受响应的时间
Receiving Push: 浏览器接收 HTTP/2 推送数据的时间
Reading Push: 浏览器读取之前接收的本地数据的时间
在 Chrome 中为每个主机设置了 6 个链接的上限, 超过限制需要排队等待. 我们重复请求一个图片 20 次, 然后按 Connection ID 排序可以看到同一个 ID 的请求是一个接一个发出的, 并不是并发的, 并且从 ConnectionID 可以看到及时发出 20 个请求也只有 6 个.
通过上面这些信息我们可以诊断一个请求慢的原因.
一般会遇到 TTFB 的时间过长
较高的等待时间(超过 200ms)可能的问题是客户端和服务端的链接速度很慢或者服务器响应较慢, 可以通过在本地起服务的方式来确定属于那种问题.
如果链接慢, 可以考虑将内容托管在 CDN 上, 或者更换你一个节点的服务器
如果是服务器慢, 这需要进一步定位慢的原因, 如数据库查询慢, 或者执行慢, 也可以考虑实现缓存来缓解.
内容下载慢
这种情况改善服务器通常无济于事, 主要还是减少内容大小, 比如压缩内容, 分片加载等.
页面性能
Lighthouse 是一个 Chrome 扩展, 也可以脱离 Chrome 作为一个 Node 模块使用. 它可以自动对一个页面做一系列审查, 然后提供一个负载性能报告以及相应的改善建议.可以配合 Performance 记录的数据对页面做全面的分析.
LightHouse
以下是对 react 官网的性能分析报告
lighthouse 使用两个对数正太分布曲线模型来计算得分. 模型的参数来自于实际网站的性能数据. 橙色区域几近于直线.
计算加权平均数后的得分, 不同版本的权重不一样, 我们可以自定义每个指标的权重来设定我们自己的体验标准.点击指标下方的 “See calculator” 可以查看每一指标的权重(下方的图片也可以试试看哦).
First Contentful Paint
FCP 是测量用户浏览页面后浏览器渲染第一个 DOM 内容所花费的时间, 页面上的图像、非白色的 Canvas 元素、SVG 被视为 DOM 内容, iframe 内部不被算在内.
对于 FCP 特别重要的一个问题是字体加载时间, 可以通过使用 font-display 来确保 webfont 在字体加载期间可见来加快字体加载速度.
Speed Index
速度用于衡量页面加载过程中可视内容显示的速度. Lighthouse 首先捕获加载页面的视频, 然后逐帧分析视觉进度. lighthouse 使用 Speedline(一个 node 模块) 来生成 Speed Index 的得疯
Time to interactive
TTI 测量页面达到完全可交互所花费的时间.
该页面显示游泳的内容, 有 FCP 测得
已为大多数可见的元素注册事件处理器
该页面在 50 毫秒内响应用户的交互
Total Blocking Time
TBT 度量阻止页面响应用户输入的总时间.在 FCP 和 TTI 之间的, 任何超过 50ms 的任务都是一项漫长的任务, 50ms 之后的时间是阻塞部分.
Largest Contentful Paint
LCP 测量视口中最大的内容元素显示的时间, 近似于页面的主要内容对用户可见.
img 元素
image 元素内的 svg 元素
video 元素( 作为封面图的图片 )
通过 url 功能加载背景图片的元素
包含文本节点或其他内联级文本子节点的块级元素
对所有元素, 不考虑通过 css 应用的任何边距 填充或者边框
Cumulative Layout Shift
CLS 是衡量用户视觉稳定性的重要指标, 它有助于量化用户经历意外的板式移位的频率. 通常页面内容的意外移动是由于异步加载资源或将 DOM 元素添加到现有内容上方而发生的.
CLS 会测量页面整个生命周期总发生的每个意外的布局变动并计算他们的总和
在图片和视频元素上添加 size 属性或者使用 css 保留所需要的空间
除非响应用户交互动作,否则不要在现有内容上方插入内容
尽可能使用 trancform 来显示动画.
First CPU Idle (v6 版中已移除)
FCI 测量页面达到最小交互要求所花费的时间.
First Meaningful Paint (v6 版中已移除)
FMP 衡量用户何时可以看到页面的内容. FMP 的原始分数是用户启动页面加载与呈现首屏内容之间的时间.
Max Potential First Input Delay
FID 衡量用户可能会遇到最坏情况下的首次输入延迟. 首次输入延迟是指用户首次与网站进行交互到浏览器实际能够响应该交互的时间. lighthouse 通过查找 FCP 之后最长任务的持续时间来计算 FDI. FCP 之前的任务将被排除在外.
报告中找出了一些具有较大价值的优化点(这些并不直接与分数相关, 通常它们能提升网站的整体性能和体验), 可以帮助我们快速找到提高网站性能的指标
Eliminate render-blocking resources(消除阻止渲染的资源)
没有 async defer 属性的 script 标签, 没有 media 与用户设备匹配的属性的样式表 link 标签属于阻止渲染的资源. 可以通过 Coverage 面板查看关键资源, 其中蓝色标示的部分是首选渲染所需要的或者核心功能所需要的代码, 红色部分是页面核心功能中未使用或对应样式不是立即可见的样式.
Properly size images(适合尺寸的图片)
对页面伤的每个图像, lighthouse 都会将渲染的图片大小和图片的实际大小进行比较, 如果渲染的大小比实际尺寸小 4kb, 这认为是不合适的图片
推迟屏幕外图片的加载
Lighthouse 根据您在 CSS 中找到的注释和空格字符,提供了可能节省的费用的估算值
缩小 JavaScript 文件可以减少有效负载大小和脚本解析时间
上边已经通过 Coverage 显示了未使用的 CSS 和 JS
Lighthouse 收集页面上的所有 JPEG 或 BMP 图像,将每个图像的压缩级别设置为 85,然后将原始版本与压缩版本进行比较。如果潜在节省量为 4KiB 或更大,则 Lighthouse 将图像标记为可优化.
Lighthouse 收集页面上的每个 BMP,JPEG 和 PNG 图像,然后将每个图像转换为 WebP,已确认是否可以节省流量, 如果能节省超过 8Kb, 则会报告问题.
lighthouse 对每一个原始大于 1.4kb 的资源使用 Gzip 压缩, 如果能节省超过 10%, 这报告这一问题.
寻找可能的区分优先级的关键请求, 可以通过 <link rel='preconnect' > 提前建立链接, <link rel='dns-prefetch' >是另一个备用方案, 不过它只用于解析 DNS.
如果 10s 内不实用这个链接, 这浏览器会关闭它.
如果浏览器请求主文档的时间超过 600ms, 则会报告这一问题, 因为其他的请求会依赖它.
避免页面多次重定向
预加载关键请求 使用<link rel=preload href="style.css" as=style /> lighthouse 将请求链中的第三级请求视为可预加载的请求
lighthouse 查找那些潜在的可以使用视频代替 GIF 动画的地方, 并计算可以节省的流量
lighthouse 将查找那些导致主线程阻塞的第三方代码, 如果总阻塞时间超过 250ms, 则会报告这一问题
当无法合成动画时, lighthouse 将会报告这一问题. 参见: 高性能动画和合成器属性和管理成计数
还有一些价值较小的优化点
Performance 性能分析
如果说 lighthouse 用来发现问题, 这 Performance 则更侧重于解决问题, 它不仅能解决渲染上的问题, 还能帮助解决 Javascript 的运行问题.
最上方的区域表示了整体的走势
上方的红色部分表示会影响用户体验的时间段
蓝色代表网络和 HTML 解析
绿色的折线图表示帧率
黄色代表执行脚本
紫色代表渲染
绿色代表绘制视图
灰色代表系统占用
下方的蓝色折线代表堆占用
竖线代表特定的事件, 图中从左往右分别是 First Paint、FirstContentful Paint、Largest Contentful Paint、 DOMContentLoaded 和 Onload
network 显示了页面之间请求的依赖关系
frames 显示了页面的渲染耗时, 以及每一帧的截图
火焰图显示了每一个任务的代码调用关系, 我们能看到有些 task 的右侧会有一部分是有红色的栅格线 右上角还有一个红色的角标, 则表示这个任务的执行超过 50ms, 可能会影响与用户的交互. 他还给出了每一个事件、函数的调用依赖关系以及他们各自的执行时间.
这里面还能看到各种浏览器的事件, 比如 Recalculate style、 Layout、Paint、Scroll、GC Event 、Parse Html 等等.
火焰图中的函数调用越宽说明执行时间越长, 越深表明调用栈越深. 通常来说尖尖的调用堆栈是正常的, 而平顶山式的调用, 则容易出现问题, 因为某个函数占用时间较长, 应该特别关注一下, 这可能就是我们需要优化的点.
选中一个函数调用后, 会在下方看到整个调用树, 包括函数的位置, 执行时间.
资源占用图
除了这些以外, 我们还能看到一些其他的指标比如 GPU, 不过通常不太会用到.
知道了每一帧时的浏览器状态, 我们可以很精确地分析代码中的问题.
最佳实践
减少 HTTP 请求(仅适用 HTTP/1 或者 HTTP/1.1)
当资源比较小, 当数量较多时, 可以通对资源进行合并, 以减少 HTTP 请求数, 比如对代码文件, 样式表用 webpack、rollup 等进行打包, 对小图标进行雪碧图处理等, 可以将细碎的小文件, 合并成一个大文件, 这时请求资源大小虽然没有变, 但是请求链接数量会减少, 这样可以省去链接时间, 等待时间, 可以加快资源的请求.
同时可以在不同的域名部署服务, 请求是对不同的服务发送请求, 可以避免浏览器的同域链接限制
使用 HTTP/2
http/2 对比 http/1 有很多优势, 比如二进制分帧, 多路复用, 头部压缩,服务端推送等. 多路复用可以通过一个 TCP 链接发送多个请求, 这样省掉了链接建立和销毁的时间, 头部压缩可以减少请求和响应发送的内容大小.
[详情]
服务端渲染
服务端渲染可以让浏览器直接获取用户需要看到的首屏内容. 不需通过脚本来生成响应的 DOM 结构, 可以在主观上加快网页的加载速度.并且服务端渲染对 SEO 友好, 但需要服务器产生较大的压力.
静态资源 CDN 部署
CDN(内容分发网络)在多个网路节点部署服务, 这样可以让资源离用户更近, 网路节点更少,从而缩短请求时间, 使用 CDN 时需要注意控制缓存, 防止 CDN 资源更新受阻.
善用缓存
缓存用的好可以大大加快用户对网站的访问速度, 但如果没有配置好, 这容易出现网站不能及时更新的问题
强缓存在缓存有效的情况下( 没有超过 Cache-Control 设定的的 max-age, 没有超过 Expires 设定的时间 )不会向服务器发出请求, 而是直接以缓存响应该请求. 命中强缓存时, http 请求的状态码时 200.
如果在强缓存生效期间, 资源被修改, 那么浏览器是拿不到最新资源的.
Pragma 和 Cache-Control 共存时的优先级 Pragma 的优先级高, 但 Pragma 并不是规范的缓存配置.
Cache-Control 在响应头中可能有几种值: no-store(不做任何缓存)、no-cache( 不做强缓存 )、max-age( 强缓存时长, 以秒为单位 )、public/private( 是否只能被单个用户使用, 中间代理是否缓存 )、must-revalidate( 厄秘次访问都需要校验 ).
在 Chrome 中命中强缓存会有两种情况
from memory cache: 一般缓存更新频率较高的资源
from disk cache: 缓存更新频率较低的资源
协商缓存是指在不确定缓存是否依然有效的情况, 浏览器会向资源发送请求, 并携带上次成功请求该资源的一些缓存信息, 有服务器决定是使用缓存还是使用服务端数据, 如果继续使用缓存这响应 304.
启用协商缓存需要服务端响应 ETag(文件 hash 值)/Last-Modified(最后修改时间, 精确到秒), 其中 ETag 的优先级更高, 在发起请求是会通过 If-None-Match 携带 ETag 信息, 以 If-Modified-Since 携带 Last-modified 信息.
随着前端工程化的逐渐兴起, 现在通常的做法是给资源( 非入口资源 index.html , 通常这个文件比较小)一个较长时间的强缓存(比如一年时间), 但有新版本发布时每个资源的文件名上都会附带其自身的 hash 值, 这样在下次请求的时候将不会命中缓存.
文件压缩
通过文件压缩可以减少资源的下载时间, 对代码可以使用混淆压缩, 对图片可以使用 ImageOptim 压缩, 还可以对资源开启 gzip 压缩.
图片优化
延迟加载
但图片需要展示的时候再加载图片
响应式图片
根据设备或显示的不同, 加载不同的图片
图片压缩
图片压缩有两种, 一种是有损压缩, 一种是无损压缩; 有损压缩一般压缩率较高, 但会损失图片清晰度, 无损压缩压缩率一般较低, 但会保持清晰度. ImageOptim
CSS 效果
对于可以使用 CSS 实现的效果, 尽可能使用 css 效果来实现, 这样可以大大减少内容的体积, CSS 实现的效果很容易保持清晰度, 并且可控, 比如做动画, 变形等.
合理拆分
当一个资源话费较长的下载时间, 可以考虑对资源进行合理的分割, 通常将公共库拆分出来用于缓存, 然后按路径对页面资源分包, 只有进入到这个页面, 对应的资源才会加载.
按需加载(懒加载)
和图片的延迟加载类似, 只在需要时才加载相关的代码, 可以减少不必要的代码.
https://www.yuque.com/roadup/frontend/oge0du
版权声明: 本文为 InfoQ 作者【roadup】的原创文章。
原文链接:【http://xie.infoq.cn/article/127207d0b598f273d89974e48】。
本文遵守【CC BY-NC】协议,转载请保留原文出处及本版权声明。
评论