写作背景:
领有一个良好用户体验的 网页 对于前端开发同学来说是一件必须做,继续做的一件事件,其中首屏的渲染尤为重要,因为用户与网页产生的交互就是从首屏开始的。接下来将介绍网页性能的几个指标以及如何计算和度量这些指标,通过监控和优化这些性能数据来继续优化网页,以进步用户体验。
Navigation Timing API:
咱们先来看一组 API,Navigation Timing API 提供了用来掂量一个网站性能的数据指标。相比于咱们传统的根底伎俩应用这组 API 能够获取更有用、更准确的数据还能够做更多的数据统计。
Navigation Timing API 的兼容性也是相当的不错,兼容咱们当初罕用的浏览器,包含让人厌恶的 IE9+。残缺的兼容性表见链接:
资源加载过程:
当咱们的一个资源发动拜访(navigationStart)后到资源实现加载(loadEventEnd)经验如下过程:
- ① 首先就是当资源须要重定向的时候会进入 redirect阶段,不须要重定向将会跳过这个阶段;
- ② 接着就会查看资源是否有 HTTP 缓存的存在,当资源没有缓存的状况下回进入下一步;
- ③ DNS 寻址的过程;
- ④ TCP 协定握手建设通信;
- ⑤ 发动申请;
- ⑥ 响应资源;
- ⑦ 对响应的内容进行解决,如:对 DOM 资源加载并解析;
- ⑧ 最初一步实现资源加载。
上面这张图将是对这个过程的规范形容,也是一道前端经典面试题的标准答案,你懂得~
[
](https://www.w3.org/TR/navigat…)
上图是 W3C 第一版的 Navigation Timing 的解决模型(Level 1),从以后浏览器窗口卸载旧页面到加载实现新页面,整个过程一共分为 9 个阶段:
- 提醒卸载旧文档
- 重定向 / 卸载
- 利用缓存
- DNS 解析
- TCP 握手
- HTTP 申请解决
- HTTP 响应解决
- DOM 解决
- 文档装载实现
Level 1 的标准从 2012 年底进入候选倡议阶段应用已将近 10 年之久,也算实现了其历史使命,往年的 3 月 20(2022 年 3 月 10 日)正式进入 Level2 的标准(WorkingDraft),Level2 相比拟 Level1 精度更高。
Level2 解决模型如下:
Level2 将整个过程划分为了 11 个阶段,各阶段指标明细:
序号 | 指标 | 解释 |
---|---|---|
1 | navigationStart | 示意从上一个文档卸载完结时的 unix 工夫戳,如果没有上一个文档,这个值将和 fetchStart 相等。 |
2 | unloadEventStart | 示意前一个网页(与以后页面同域)unload 的工夫戳,如果无前一个网页 unload 或者前一个网页与以后页面不同域,则值为 0。 |
3 | unloadEventEnd | 返回前一个页面 unload 工夫绑定的回掉函数执行结束的工夫戳。 |
4 | redirectStart | 第一个 HTTP 重定向产生时的工夫。有跳转且是同域名内的重定向才算,否则值为 0。 |
5 | redirectEnd | 最初一个 HTTP 重定向实现时的工夫。有跳转且是同域名外部的重定向才算,否则值为 0。 |
6 | fetchStart | 浏览器筹备好应用 HTTP 申请抓取文档的工夫,这产生在查看本地缓存之前。 |
7 | domainLookupStartdomainLookupEnd | DNS 域名查问开始 / 完结的工夫,如果应用了本地缓存(即无 DNS 查问)或长久连贯,则与 fetchStart 值相等。 |
8 | connectStart | HTTP(TCP)开始 / 从新建设连贯的工夫,如果是长久连贯,则与 fetchStart 值相等 |
9 | connectEnd | HTTP(TCP)实现建设连贯的工夫(实现握手),如果是长久接,则与 fetchStart 值相等。 |
10 | secureConnectionStart | HTTPS 连贯开始的工夫,如果不是平安连贯,则值为 0。 |
11 | requestStart | HTTP 申请读取实在文档开始的工夫(实现建设连贯),包含从本地读取缓存。 |
12 | responseStart | HTTP 开始接管响应的工夫(获取到第一个字节),包含从本地读取缓存。 |
13 | responseEnd | HTTP 响应全副接管实现的工夫(获取到最初一个字节),包含本地读取缓存。 |
14 | domLoading | 开始解析渲染 DOM 树的工夫,此时 Document..readyState 变为 loading, 并将抛出 readystatechange 相干事件。 |
15 | domlnteractive | 实现解析 DOM 树的工夫,Document..readyState 变为 interactive, 并将抛出 readystatechange 相干事件,留神只是 DOM 树解析实现,这时候并没有开始加载网页内的资源。 |
16 | domContentLoadedEventStart | DOM 解析实现后,网页内资源加载开始的工夫,在 DOMContentLoaded 事件抛出前产生。 |
17 | domContentLoadedEventEnd | DOM 解析实现后,网页内资源加载实现的工夫(如 JS 脚本加载执行结束) |
18 | domComplete | DOM 树解析实现,且资源也准备就绪的工夫,Document..readyState 变为 complete, 并将抛出 readystatechange 相干事件。 |
19 | loadEventStart | load 事件发送给文档,也即 load 回调函数开始执行的工夫。 |
20 | loadEventEnd | load 事件的回调函数执行结束的工夫。 |
计算页面加载的总时长:
Navigation Timing API 为 window 下挂的 Performance 对象减少了 timing 和 navigation 属性,通过 window.performance.timing
能够失去 PerformanceTiming 接口,咱们能够应用接口外面提供的 loadEventEnd 减去 navigationStart 失去页面加载总的时长。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title> 页面性能 </title>
</head>
<body>
<script>
// 书签反对的 js 脚本格局:javascript:<code>
javascript: (() => {
const perfData = window.performance.timing;
const pageLoadTime = perfData.loadEventEnd - perfData.navigationStart;
console.log("页面加载耗时:", pageLoadTime, "ms");
})();
</script>
</body>
</html>
当你在本人的电脑中运行这段代码的时候会发现,输入的耗时竟然是正数,那是因为本地服务的访问速度是很快了,所以整个页面的加载工夫简直为 0,咱们能够将外面的 JavaScript 脚本保留到你的浏览器书签,在轻易找一个网站期待加载实现后点击这个书签就能够在控制台输入失常的页面加载的残缺工夫了。
下图就是咱们通过上述脚本获取到的页面耗时在 6798 毫秒,那么这个数据是否正确呢?其实浏览器的开发者工具也为咱们统计了这个数据,能够接着看上面的第二张图的右下角。
下图右下角的工夫是浏览器开发者工具为咱们统计到的数据:
依据后面的资源加载的流程图咱们就能够获取到任意一段有意义的工夫,比方:资源申请的耗时咱们就能够应用 responseEnd 减去 requestStart 失去,DOM 加载的整个工夫能够应用 domComplete 减去 domLoading 失去等。
如何失去更准确的数据:
后面咱们应用的 API 所提供的数据通过查看均是毫秒级别的,也看失去 API 均被标注的弃用的标识。那么更高精度的数据咱们就须要应用一个新的 API 来获取,最终失去一个 PerformanceNavigationTiming对象,这个对象提供了纳秒级别的数据统计,咱们一起来执行上面的代码。
<script>
window.onload = function () {
// PerformanceEntryList
const entries = window.performance.getEntries();
console.log(entries);
};
</script>
咱们运行上述代码将在控制台失去上面的数组,其中数组的第一项的 type为 navigate,这示意咱们是通过在浏览器的地址栏输出 URL 关上的页面,也能够是通过点击链接、表单提交、脚本初始化,但不包含刷新页面,回退等,type 的更多形容见 mdn。
上图中的 name 属性在这里也须要阐明一下,能够看到数据第一项的 name 示意这次资源申请的地址,其余的两个是非凡的标识,其内容取决于 PerformanceEntry 对象的 subtype 和PerformanceEntry.entryType,所以顺便摘录了 mdn 上的属性表格,
Value | Subtype | EntryType | 形容 |
---|---|---|---|
URL | PerformanceNavigationTiming | frame, navigation | 文档的地址 |
URL | PerformanceNavigationTiming | resource | 申请资源所解析的 URL |
DOMString | PerformanceMark | mark | 执行 performance.mark()创立标记时应用的名称 |
DOMString | PerformanceMeasure | measure | 执行 performance.measure()时应用的名称 |
DOMString | PerformancePaintTiming | paint | first-paint |
first-contentful-paint |
咱们也能够通过指定 EntryType 来获取对应的信息,上面的代码咱们将只会获取到 first-paint 和 first-contentful-paint 两个元素:
<script>
window.onload = function () {const entries = window.performance.getEntriesByType('paint');
console.log(entries);
};
</script>
常见的性能指标:
咱们在后面输入 PerformanceNavigationTiming 对象时就曾经看到了 name 非 first-paint 和 first-contentful-paint 的对象,这两个就是典型的前端性能指标,其次还有几个,咱们用一个表格来看一下:
指标 | 含意 |
---|---|
FP | First Paint,首次绘制,指浏览器从开始申请网站到屏幕渲染第一个像素点的工夫。 |
FCP | First Contentful Paint,首次内容绘制,指浏览器渲染出第一个内容的工夫,这个内容能够是文本、图片、背景图、非空白的画布、SVG 等,并不包含 iframe 元素。 |
LCP | Largest Contentful Paint,最大内容绘制,指网页被展现在视口中的最大内容的显示工夫。 |
FMP | First Meaningful Paint,首次无效绘制,指网页渲染出第一个要害内容的工夫。区别与 FCP,FMP 指第一块有意义的内容绘制。如:博客网站的要害内容指的是注释,视频网站的要害内容指的是视频播放器,电商网站的要害内容指的是商品列表或商品详情等。因其计算过于简单,失去的后果并不是十分精确,在 Lighthouse6.0 中用 LCP 替换了 FMP。 |
DCL | DOMContentLoaded,指网页中的 DOM 加载并实现解析后触发此事件,但不包含款式、图像等。 |
L | load,指网页实现了所有的加载包含 DOM、款式、图像等内容后触发此事件。 |
下图是在控制台的性能页签抓取的数据:
实时获取性能指标:
上述的性能指标都存在一个问题,就是咱们只能获取在 onload 执行前的性能数据,之后变动的数据将无奈获取。所以在 HTML5 中为咱们新增的 PerformanceObserver 对象,能够应用观察者的模式来拿到每次发生变化后的数据,兼容性见下表:
获取 fp 和 fpc:
- 实例化 PerformanceObserver 对象;
- 在构造函数地位指定处理函数;
- 调用 observe 并指定 entryTypes 使其蕴含 paint;
<script>
const ob = new PerformanceObserver((list) => {list.getEntries().forEach((entrie) => {console.log("entrie:", entrie);
});
});
ob.observe({entryTypes: ["paint"] });
</script>
下图输入的信息就是对应的 fp 和 fcp 的信息了:
获取其余资源的性能数据:
- 调整 observe 函数中 entryTypes 使其蕴含 resource;
- 将 ob 的实例化提到其余脚本执行之前;
<head>
<meta charset="UTF-8" />
<title> 页面性能 </title>
<script>
const ob = new PerformanceObserver((list) => {list.getEntries().forEach((entrie) => {console.log("entrie:", entrie);
});
});
ob.observe({entryTypes: ["resource"] });
</script>
<script src="./index.js"></script>
</head>
下图输入的信息就蕴含了此脚本加载的性能信息:
获取脚本运行到特定地位的性能数据:
- 再次调整observe 函数中 entryTypes 使其蕴含 resource;
- 调用
window.performance.mark("own");
进行打点,own 为自定义标记;
<head>
<meta charset="UTF-8" />
<title> 页面性能 </title>
<script>
const ob = new PerformanceObserver((list) => {list.getEntries().forEach((entrie) => {console.log("entrie:", entrie);
});
});
ob.observe({entryTypes: ["mark"] });
</script>
<script src="./index.js"></script>
<script>
console.log("↓↓↓↓↓↓↓↓↓↓↓");
window.performance.mark("own");
console.log("↑↑↑↑↑↑↑↑↑↑");
</script>
</head>
下图输入的信息就蕴含了执行到此标记时的耗时:
计算 DOM 插入的工夫:
- 编写一个 addDom 函数,外部实现一个 div 插入到 body 的残缺过程;
- 在 addDom 函数的开始和完结地位别离应用 mark 进行打标记;
- 当咱们触发页面中的 add dom 按钮的时候就会接管到蕴含咱们自定义标记两个 PerformanceMark 对象,其中蕴含的工夫相减就能够失去 DOM 插入的工夫了。
内容扩大:
W3C 规范只是举荐规范,并没有强制执行的能力。但在 Web 规范畛域各浏览器厂商对举荐的规范都很器重,踊跃的响应,但并非所有的规范都能失去适配。那么作为一名 Web 前端的开发人员,咱们在应用一些新的 API 的时候也要关注这些 API 目前处于什么样的阶段,兼容性如何,是否牢靠的运行到生产环境。下图是一份 W3C 规范制订的流程示意图:
一份规范的制订从 工作草案 提出由公众和会员进行探讨造成 最终工作草案 后并再次进行探讨及订正,订正结束的最终工作草案将列入 候选举荐规范 ,此时的标准处于稳固状态,能够发展实验性的施行,在继续一段进行的验证和修复后就会进入到 倡议举荐规范 的阶段,W3C 会员将对这份规范进行审阅,如果审阅没有通过将打回到 候选规范举荐 ,更重大的将间接打回到最后的 工作草案 阶段或 彻底移除,审阅通过后 W3C 总监将发表该标准为W3C 举荐规范。
写在最初:
在这一篇中咱们介绍了几个在网页性能紧相干的几项指标,也介绍了从毫秒到纳秒的性能数据获取的 API 和应用的形式,期间咱们也演示了通过浏览器提供的 Performance 性能,Chrome 浏览器还内置了 Lighthouse 性能来为咱们的网页性能打分。咱们通常也能够齐全利用这两个工具,那么在须要定制一些公有的性能监控平台的时候 API 就能够施展更大的性能了。
参考资料:
- https://www.w3.org/TR/navigation-timing/
- https://www.w3.org/TR/navigation-timing-2/
- https://zhuanlan.zhihu.com/p/82981365
团队介绍
高灯科技交易合规前端团队(GFE), 隶属于高灯科技 (北京) 交易合规业务事业线研发部,是一个富裕激情、充斥创造力、保持技术驱动全面成长的团队, 团队平均年龄 27 岁,有在各自畛域深耕多年的大牛, 也有刚刚毕业的小牛, 咱们在工程化、编码品质、性能监控、微服务、交互体验等方向踊跃进行摸索, 谋求技术驱动产品落地的主旨,打造欠缺的前端技术体系。
- 愿景: 成为最值得信赖、最有影响力的前端团队
- 使命: 保持客户体验第一, 为业务发明更多可能性
-
文化: 敢于承当、深刻业务、集思广益、简略凋谢
- Github:github.com/gfe-team
- 团队邮箱:gfe@goldentec.com
著作权归作者所有。商业转载请分割作者取得受权,非商业转载请注明出处。