1.为什么要进行Web性能优化
互联网的发展非常快,网站内容是越来越多、功能越来越强大、页面越做越漂亮。内容多了后意味着网站速度会受到影响。但是我们的用户却希望网站速度越来越快,所以我们前端工程师只有不断的对我们的网站去进行一个持续的优化,才能保证在这个发展的过程中,我们网站的速度始终可以跟得上用户的体验的需求。 就拿亚马逊来说,一旦网络有了100毫秒的延迟,就意味着它们可能会少卖掉1%的货物。谷歌搜索对网速也要求很高,搜索页面0.5秒的延迟可能就会导致网络流量20%的降幅。同样的,如果电子交易平台慢上五毫秒,那么一位证券经纪人可能每毫秒就少赚400万美元,这一结果实在是非常惊人。
2.性能指标和优化目标
性能指标一:Network
图片中各个时间段的意思:
- Queueing:浏览器会在以下情况对请求进行排队:
-
- 有更高优先级的请求
-
- 已为该来源打开了六个TCP连接,这是限制。仅适用于HTTP / 1.0和HTTP / 1.1
-
- 浏览器正在磁盘缓存中短暂分配空间
- Stalled:出于Queueing描述的任何原因发生导致该请求被拖延
- DNS Lookup:浏览器对请求的IP地址进行DNS查找所消耗的时间
- Initial conncection: 在浏览器发送请求之前,必须建立TCP连接,这是发起连接所消耗的时间
- SSL: 如果你的页面是通过SSL/TLS这类安全协议加载资源, 这段时间就是浏览器建立安全连接的过程
- Request sent:请求发送消耗的时间
- Waiting (TTFB):浏览器正在等待响应的第一个字节。TTFB代表到第一个字节的时间(Time To First Byte)。时间为浏览器请求发送到服务器的时间+服务器处理请求时间+响应报文的第一字节到达浏览器的时间组合。
- Content Download:资源下载所消耗的时间
其他的一些字段:
- Proxy negotiation:浏览器与代理服务器协商消耗的时间
- ServiceWorker Preparation. 浏览器正在启动 service worker.
- Request to ServiceWorker. 正在向 service worker发送请求.
- Receiving Push. 浏览器正在接收HTTP/2服务器推送的响应数据。.
- Reading Push. 浏览器正在读取先前接收的本地数据。
关于灰色的文字含义 关于Size,其中黑色表示从网络请求过来的资源大小,灰色表示资源实际大小。
Size列多了一行灰色的文字,表示该资源的实际大小 Size列的第一行数据表示请求头和请求体的大小之和,由于HTTP请求的多样,会导致第一行数据的大小和第二行数据大小的不同,有可能第一行的数据比第二行的数据大,也可能第一行的数据比第二行的数据小,一般有以下几种原因:
- 有响应头,甚至包含cookie(第一行 > 第二行)
- 请求被缓存了(一般情况下,第一行 < 第二行)
- 服务端gizp压缩(一般情况下,第一行 < 第二行)
参考文章:
性能指标二:页面审核工具 Chrome Lighthouse
Lighthouse 是一种开源的自动化工具,用于提高网页质量。你可以在任何网页上运行它。它能够针对性能、可访问性、渐进式 Web 应用等进行审核。
具体使用可以参考文章:
性能指标三:利用 show frames per second meter
Mac 利用 command + shift + P 打开命令行工具 搜索 show frames per second meter。
3.RAIL测量模型
四个方面:Response响应、Animation动画、Idle空闲、Load加载。
Response: 事件处理最好在50ms内完成
- 用户的输入到响应的时间不超过100ms,给用户的感受是瞬间就完成了。
Animation: 在10ms内产生一帧
- 产生每一帧的时间不要超过10ms,为了保证浏览器60帧,每一帧的时间在16ms左右,但浏览器需要用6ms来渲染每一帧。
- 旨在视觉上的平滑。用户对帧率变化感知很敏感。
Idle: 最大化空闲时间
- 最大化空闲时间,以增大50ms内响应用户输入的几率
Load: 传输内容到页面可交互的时间不超过5秒
- 针对与用户的设备和网络功能相关的快速加载性能进行优化。目前,第一次加载的目标是在3G连接速度较慢的中档移动设备上,加载页面在5秒或更短时间内进行交互。
- 对于第二次打开,尽量不超过2秒
分析RAIL用的工具
- Chrome DevTools
- Lighthouse
- WebPageTest
参考文章:
4.常用的性能测量APIs
①关键时间节点(Navigation Timing, Resource Timing)
关于前端性能指标,W3C 定义了强大的 Performance API。本次主要围绕getEntriesByType获取性能数据的一种方式。performance 还提供了 getEntries 以及 getEntriesByName 等其他方式,由于“时间限制”,具体区别不在此赘述,各位看官可以移步到此: Performance Timeline Level 2或者Performance MDN
利用performance.getEntriesByType获取含有页面加载各阶段的时间值 PerformanceEntry 对象,大家可以在控制台输出navTimes尝试下。
const navTimes = performance.getEntriesByType('navigation')[0]
第 1 步 开始计时
- startTime:记录开始时间。
第 2 步 重定向
- redirectStart:重定向开始时间。
- redirectEnd:重定向结束时间。
第 3 步 浏览器与服务器连接
- fetchStart:浏览器发起 HTTP 请求时间。
- domainLookupStart:DNS 查询开始时间。
- domainLookupEnd:DNS 查询结束时间。
- connectStart:TCP 连接开始时间。
- connectEnd:TCP 连接结束时间。
第 4 步 浏览器与服务器数据交互
- secureConnectionStart:浏览器跟服务器建立安全连接的时间。
- requestStart:浏览器向服务器开始发送数据的时间。
- responseStart:服务器向浏览器开始发送数据的时间。
- responseEnd:服务器向浏览器结束发送数据的时间。
第 5 步 浏览器 DOM 解析
- domLoading:DOM 开始解析的时间。
- domInteractive:DOM 解析完成,开始加载内嵌资源的时间。
- domContentLoadedEventStart:需要被执行的脚本已经被解析的时间。
- domContentLoadedEventEnd:需要立即执行的脚本已经被执行的时间。
- domComplete:文档解析完毕的时间。
因此计算首次可交互时间:
window.addEventListener('load', (event) => {
// Time to Interactive
let timing = performance.getEntriesByType('navigation')[0];
let diff = timing.domInteractive - timing.fetchStart;
console.log("TTI: " + diff);
})
可以配合获取数据之后,需要向服务端上报的行为,实现简单的前端性能监控。
常见的时间计算
- DNS 解析耗时: domainLookupEnd - domainLookupStart
- TCP 连接耗时: connectEnd - connectStart
- SSL 安全连接耗时: connectEnd - secureConnectionStart
- 网络请求耗时 (TTFB): responseStart - requestStart
- 数据传输耗时: responseEnd - responseStart
- DOM 解析耗时: domInteractive - responseEnd
- 资源加载耗时: loadEventStart - domContentLoadedEventEnd
- First Byte时间: responseStart - domainLookupStart
- 白屏时间: responseEnd - fetchStart
- 首次可交互时间: domInteractive - fetchStart
- DOM Ready 时间: domContentLoadEventEnd - fetchStart
- 页面完全加载时间: loadEventStart - fetchStart
- http 头部大小: transferSize - encodedBodySize
- 重定向次数:performance.navigation.redirectCount
- 重定向耗时: redirectEnd - redirectStart
参考文章:
②网络状态(Networks APIs)
var connection = navigator.connection || navigator.mozConnection || navigator.webkitConnection;
var type = connection.effectiveType;
function updateConnectionStatus() {
console.log("Connection type changed from " + type + " to " + connection.effectiveType);
type = connection.effectiveType;
}
connection.addEventListener('change', updateConnectionStatus);
③客户端与服务端协商 (HTTP Client Hints)
研究中。。。
④网页显示状态(UI APIs)
// 见面可见性的状态监听
let vEvent = 'visibilitychange';
if (document.webkitHidden != undefined) {
// webkit prefix detected
vEvent = 'webkitvisibilitychange';
}
function visibilityChanged() {
if (document.hidden || document.webkitHidden) {
console.log("Web page is hidden.")
} else {
console.log("Web page is visible.")
}
}
document.addEventListener(vEvent, visibilityChanged, false);