Skip to content

Performance

在前端性能监控方面,主要有以下几个指标:

  • 白屏时间 : 从打开网站到有内容渲染出来的时间节点。
  • 首屏时间 : 首屏内容渲染完毕的时间节点。
  • 用户可操作时间节点 : domready 触发节点
  • 总下载时间 : window.onload 的触发节点

分析

白屏时间

白屏时间指的是从用户进入网站(输入 url、刷新、跳转等方式)的时刻开始计算,一直到页面内容展示出来的时间节点 这个过程主要包括 : dns 查询、 建立 tcp 连接、发送首个 http 请求(如果使用 HTTPS 还要介入 TLS 的验证时间)、返回 html 文档、文档解析完毕。

首屏时间

window.performance 统计

timing整体结构图

参数含义默认值备注
navigationStart前一个网页卸载的时间fetchStart..
unloadEventStart前一个网页的 unload 事件开始0...
unloadEventEnd前一个网页的 unload 事件结束0...
redirectStart重定向开始时间0...
redirectEnd重定向结束时间0...
---------
fetchStart开始请求网页0...
domainLookupStartDNS 查询开始0...
domainLookupEndDNS 查询结束0...
connectStart向服务器建立握手开始时间0...
connectEnd向服务器建立握手结束0...
secureConnectionStart安全握手开始时间0...
---------
requestStart浏览器发起请求的时间节点(请求服务器、缓存、本地资源)0...
responseStart浏览器接收到从服务器端响应的第一个子节的时间节点0...
responseEnd浏览器接收到从服务器端响应的最后一个子节的时间节点0...
---------
domLoading浏览器发起请求的时间节点(请求服务器、缓存、本地资源)0...
domInteractive浏览器发起请求的时间节点(请求服务器、缓存、本地资源)0...
DOMContentLoadedEventStartDOMContentLoaded 事件触发时间节点0...
DOMContentLoadedEventEndDOMContentLoaded 事件结束时间节点0...
domCompletehtml 文档完全解析完毕的时间节点0...
loadEventStartdocument.onload 事件触发时间节点
loadEventEnddocument.onload 事件结束时间节点

DNS 查询耗时 = domainLookupEnd - domainLookupStart TCP 链接耗时 = connectEnd - connectStart request 请求耗时 = responseEnd - responseStart 解析 dom 树耗时 = domComplete - domInteractive 白屏时间 = domloadng - fetchStart domready 时间 = domContentLoadedEventEnd - fetchStart onload 时间 = loadEventEnd - fetchStart

js
var timing = performance.timing;
var times = {};
// 页面加载完成时间
times.loadPageTiming = timing.loadEventEnd - timing.navigationStart;
// 解析DOM树结构的时间
// [原因] 分析你的DOM树是否嵌套太多了
// 问题: 为什么 使用domComplete 而不是使用 domContentLoadedEventStart
times.domReady = timing.domContentLoadedEventStart - timing.responseEnd;
times.domReadyDomComplate = timing.domComplete - timing.responseEnd;
console.log(`domComplete - domContentLoadedEventStart =  ${timing.domComplete - timing.domContentLoadedEventStart}`);
// 重定向时间
times.redirect = timing.redirectEnd - timing.redirectStart;
// DNS查询时间
// [ 原因:] DNS预加载做了没有?页面中是否存在很多不同的域名 导致域名查询时间太长
times.lookupDomain = timing.domainLookupEnd - timing.domainLookupStart;
// [重要] 读取页面第一个子节的时间, 即用户从输入URL 到获取到页面资源的时间
times.ttfp = timing.responseEnd - timing.navigationStart;
// 内容加载的时间, 即页面子节大小
// 注意: 这不是后台返回数据的时间  那个时间应该是 后台发出数据包 到资源加载完成时间,其从后台发出资源到客户端接收到第一个子节时间不能获取,
// 所以request 的时间是小于后台反应时间的,而 timing.responseEnd - timing.requestStart 也大于此时间
times.request = timing.responseEnd - timing.responseStart;
//  执行 load回调函数的时间   --- 即开发者在 document.addEventListener('load',() => {  ..... }) 里面代码的执行时间
// 原因 : 太多不必要的操作都在 load回调中执行,考虑过延迟加载,按需加载策略
times.loadEventExeuctime = timing.loadEventEnd - timing.loadEventStart;
// DNS缓存时间
times.appcache = timing.domainLookupEnd - timing.fetchStart;

分析

  1. DNS 寻址时间:t.domainLookupEnd - t.domainLookupStart。

优化方法:检查页面是否添加了 DNS 预解析代码。

html
<link rel="dns-prefetch" href="//haitao.nosdn1.127.net" />

是否合理利用域名发散域名收敛的策略。

  1. TCP 连接耗时:t.connectEnd - t.connectStart。

  2. 首包时间: t.responseStart - t.navigationStart。

优化方法:是否加 cdn,数据可否静态化等。

  1. request 请求耗时:t.responseEnd - t.requestStart。

优化方法:返回内容是否已经压缩过,静态资源是否打包好等。

  1. 白屏时间。

白屏时间是最影响用户体验的,时间越久,用户等待就越久。

  1. 解析 DOM 树结构的时间:t.domComplete - t.domLoading。

优化方法:检查 dom 节点是否过多,dom 是否嵌套过深。

  1. 页面加载完成的时间:t.loadEventEnd - t.fetchStart。

优化方法:考虑延迟加载,懒加载,部分加载,减少首屏渲染时间。

首屏时间-打点

  1. js 后面的点 - js 前面的点 -= js 的加载时间
html
<script type="text/javascript">
	console.log(`jsStartTime = ${+new Date()}`);
</script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-select/1.12.4/js/bootstrap-select.min.js"></script>
<script type="text/javascript">
	console.log(`jsEndTime = ${+new Date()}`);
</script>

JsEndTime – JsStartTime = js 文件的加载时间,对吗?

不对!明显地,这个等式忽略了 js 的执行时间。js 执行代码是需要花费时间的,特别是做一些复杂的计算或频繁的 dom 操作,这个执行时间有时会达到几百毫秒。

那么,JsEndTime – JsStartTime = js 文件的加载执行时间?

依然不对!因为 CSS 文件的加载执行带来了干扰。

为什么? 因为 浏览器对资源文件的加载 是并行的,所以我们要知道 加载是并行的,执行是串行的

所以当前面没有 js,css 的文件,那么在 JsEndTime – JsStartTime = js 文件的加载时间 + 执行时间

而如果 前面存在一个执行很长时间的资源文件, 那么在他们并行加载的时候,执行到 starttime 的时候可能此资源已经加载完成只是没有执行,那么这时候 JsEndTime – JsStartTime = js 文件的执行时间

  1. 资源文件中 加载另外的资源文件,那么这另外的资源文件的加载执行会阻塞后面资源文件的执行么(即阻塞后面文件的执行)

不会

所以在首屏优化的时候可以

  • 无关紧要”的 js 不要放在负责渲染的 js 前面,这里的“无关紧要”是指和首屏渲染无关

  • 可以看到,动态加载的 js 的执行是不会受到 html 后面外联的 js 的阻塞的影响,即是说,它的执行和后面 js 的执行顺序是不确定的。因此我们要小心处理好文件的依赖关系。当然还可以采用最不容易出错的方法:负责动态加载 js 的文件是 html 里面外联的最后一个文件

  1. 如果 html 的返回头包含 chunk,则它是边返回边解析的,不然就是一次性返回再解析。这个是在服务器配置的

header中的Transfer-Encoding

如果包含这个返回头,那 html 文件是边返回边解析的,此时上面的计算方法是合理的。如果不包含这个头,则 html 文件是整一个返回来后才开始解析,此时上面的计算方法就少算了 html 的加载时间,也就不够精准。

  • 如果我们想说明直出的优化程度,最好先瞧瞧你的 html 返回头。如果不包含 chunk 返回头,考虑拿 HTML5 performance 里面的 navigationStart 作为打点 1(这个 API 也是 Android4.4 及以上才支持),要不就要评估文件大小变化做点修正了;

  • 对于没有启用 chunk 的 html,建议不要 inline 太多跟渲染首屏内容无关的 js 在里面,这样会影响渲染时间

  1. 写在 html 里面的 script 节点的加载和解析会影响 domContentLoaded 事件的触发时间

我们有时会用 domContentLoaded 事件代替 onload 事件,在页面准备好的时候做一些处理。然而要知道,domContentLoaded 里面的 dom 不止包含我们常说的普通 dom 节点,还包括 script 节点。

js 文件的加载时间会影响这个事件的触发事件。那 js 代码的解析时间会不会影响?所以 js 文件加载执行会影响 domContentLoaded 事件的执行时机。

  • 如果我们打算在 domContentLoaded、onLoad 事件里面做一些特殊处理且这些处理比较重要(如跟渲染有关),那我们最好就不要在 html 里面直接外联一些跟渲染无关的 js 文件,可以考虑改用动态加载

方法

  • performance.mark()

资源文件标记点

  • performance.measure()

在浏览器性能 entries 中缓存一个 两个 mark 标记的时间测量数据

js
performance.mark('teststart');

for (var i = 0; i < 1000; i++) {
	for (var j = 0; j < 1000; j++) {
		var a = i + j;
	}
}

performance.mark('testend');
performance.measure('test', 'teststart', 'testend');
console.dir(performance.getEntries());

mark和measure

可见这两个方法主要用于时间点标记和记录两个时间点间隔。

重点

  1. entryType = 'mark' | 'measure' ,所以我们可以通过 performance.getEntriesByType('mark' | 'measure' ) 获取两个种类的测量数据,当然我们也可以通过 performance.getEntriesByName('teststart' | 'test' ) 去按照 name 去获取。

  2. measure 的 duration 表示 markend - markstart 的时间差

  • performance.clearMarks()

  • performance.clearMeasures()


获取性能资源

  • performance.getEntries()

  • performance.getEntriesByName()

  • performance.getEntriesByType()

js
var resourceArr = [];
performance.getEntriesByType('resource').forEach(resource => {
	// 这边记录的是资源文件的加载和处理时间
	resourceArr.push({
		// DNS解析时间  此资源是否使用了 DNS预解析
		appcache: resource.domainLookupEnd - resource.domainLookupStart,
		// 资源加载时间,即资源的大小加载时间
		request: resource.responseEnd - resource.responseStart,
		// 从加载到完成的整体用时
		loadTiming: resource.responseEnd - resource.fetchStart,
		//  资源名称
		name: resource.name,
		// 资源的类型  link script 还是 img
		resoureceType: resource.initiatorType,
		//
		entryType: resource.entryType,

		// 资源的大小
		size: resource.encodedBodySize
	});
});

console.table(resourceArr);

mark和measure

牵扯的事件

  • onload 事件

触发的时机是 页面上所有的 DOM、样式表、脚本、图片、flash 等资源全部加载完成

  • DOMContentLoaded 事件

触发的时机是:DOM 加载完成,不包含样式表、脚本、图片、flash 等资源文件

所以我们一般 会在 DOMContentLoaded 事件触发的时候去 给元素绑定事件,因为我们根本不需要去等待图片等资源文件加载完成去绑定执行。

如果是 webkit 引擎则轮询 document 的 readyState 属性,当值为 loaded 或者 complete 时则触发 DOMContentLoaded 事件,对 webkit525 之后版本直接可以注册 DOMContentLoaded 事件

  • document.readyState

研究首屏时间?你先要知道这几点细节