前言
最近有幸参与一个前端质量监控类项目的重构,算是个人初次接触到前端质量监控相关的知识,对于其中的性能统计部分很感兴趣,查询资料之后写了文章,作为个人学习记录,如有错误,敬请斧正
1. 页面整体性能
通过浏览器提供的 window.performance.timing 方法,我们能够得到网页每个处理阶段的精确时间。打开一个页面后,这些信息会被浏览器记录下来,我们直接在控制台输出,就可以查看结果
该对象下有多个字段,每个字段都对应着浏览器加载一个页面的某个阶段,下面这张图详细的展示了浏览器加载一个页面的详细过程
https://segmentfault.com/img/…
window.performance = {
timing: {
// 同一个域下,前一个网 unload 的时间戳,
// 如果直接复制地址进入或者不是同域
// 则与 fetchStart 值相等
navigationStart: 1441112691935,
// 前一个网页 unload 的时间戳,
// 如果无前一个网页 unload
// 或者前一个网页与当前页面不同域,则值为 0
unloadEventStart: 0,
// 和 unloadEventStart 相对应
// 返回前一个网页 unload 事件绑定的回调函数执行完毕的时间戳
unloadEventEnd: 0,
// 第一个 HTTP 重定向发生时的时间。
// 有跳转且是同域名内的重定向才算
// 否则值为 0
redirectStart: 0,
// 最后一个 HTTP 重定向完成时的时间
// 有跳转且是同域名内部的重定向才算
// 否则值为 0
redirectEnd: 0,
// 浏览器准备好使用 HTTP 请求抓取文档的时间
// 发生在检查本地缓存之前
fetchStart: 1441112692155,
// DNS 域名查询开始的时间
// 如果使用了本地缓存(即无 DNS 查询)或持久连接
// 则与 fetchStart 值相等
domainLookupStart: 1441112692155,
// DNS 域名查询完成的时间
// 如果使用了本地缓存(即无 DNS 查询)或持久连接
// 则与 fetchStart 值相等
domainLookupEnd: 1441112692155,
// HTTP(TCP)开始建立连接的时间
// 如果是持久连接,则与 fetchStart 值相等
// 注意如果在传输层发生了错误且重新建立连接,
// 则显示的是新建立的连接开始的时间
connectStart: 1441112692155,
// HTTP(TCP)完成建立连接的时间(完成握手)
// 如果是持久连接,则与 fetchStart 值相等
// 注意如果在传输层发生了错误且重新建立连接,
// 则显示的是新建立的连接完成的时间
// 注意这里握手结束,包括安全连接建立完成、SOCKS 授权通过
connectEnd: 1441112692155,
// HTTPS 连接开始的时间,
// 如果不是安全连接,则值为 0
secureConnectionStart: 0,
// HTTP 请求读取真实文档开始的时间(完成建立连接
// 包括从本地读取缓存
// 连接错误重连时,这里显示的也是新建立连接的时间
requestStart: 1441112692158,
// HTTP 开始接收响应的时间(获取到第一个字节 )
// 包括从本地读取缓存
responseStart: 1441112692686,
// HTTP 响应全部接收完成的时间(获取到最后一个字节)
// 包括从本地读取缓存
responseEnd: 1441112692687,
// 开始解析渲染 DOM 树的时间
// 此时 Document.readyState 变为 loading
// 并将抛出 readystatechange 相关事件
domLoading: 1441112692690,
// 完成解析 DOM 树的时间
// Document.readyState 变为 interactive
// 并将抛出 readystatechange 相关事件
// 注意只是 DOM 树解析完成
// 此时并没有开始加载网页内的资源
domInteractive: 1441112693093,
// DOM 解析完成后,网页内资源加载开始的时间
// 在 DOMContentLoaded 事件抛出前发生
domContentLoadedEventStart: 1441112693093,
// DOM 解析完成后,
// 网页内资源加载完成的时间
// 如 JS 脚本加载执行完
domContentLoadedEventEnd: 1441112693101,
// DOM 树解析完成
// 且资源也准备就绪的时间
// Document.readyState 变为 complete
// 此时抛出 readystatechange 相关事件
domComplete: 1441112693214,
// load 事件发送给文档
// 也即 load 回调函数开始执行的时间
// 注意如果没有绑定 load 事件,值为 0
loadEventStart: 1441112693214,
// load 事件的回调函数执行完毕的时间
loadEventEnd: 1441112693215
}
}
通过这些值,我们就能得到某个阶段具体的时间差,进行一些简单的计算,就能够得到网页的各项性能数据,便于我们在某个阶段做优化
// DOM 解析时间
var domParseTime = domComplete – responseEnd;
// DNS 解析时间
var domainLookUpTime = domainLookupEnd – domainLookupStart;
2. 页面中各个资源的性能
上面我们用 window.performance.timing 方法得到的是整个页面的耗时,在一些情况下,我们想要知道页面上某个静态资源的加载时间,那么我们就可以用三个方法来获取这些信息
window.performance.getEntries // 返回网页中所有资源和标记的数据
window.performance.getEntriesByType // 根据 entryType 返回数据
window.performance.getEntriesByName // 根据 name 返回数据
其中 window.performance.getEntries 可以获取到所有资源信息和所有标记信息 (何为标记信息,我们后面会讲到), 而我们想看的只是 entryType: “resource” 这些资源的信息,所以我们可以直接使用 window.performance.getEntriesByType(‘resource’) 方法来获得我们的信息,该方法会返回一个数组,包含字段如下
var entries = [{
// 资源的绝对路径
name: “http://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js”,
// 资源类型
entryType: “resource”,
// css: 通过 css 请求的资源。类型不定
// img: 图片
// link: 通过 link 标签请求的资源: css/favicon
// script: js 文件
// xmlhttprequest: 一般为 GET 方式的数据接口
initiatorType: “script”,
// 加载时间
duration: 18.13399999809917,
// 这些字段的意义同上面
// 不同之处只表示这一个资源的时间
redirectStart: 0,
redirectEnd: 0,
fetchStart: 233.346999992829828,
domainLookupStart: 0,
domainLookupEnd: 0,
connectStart: 0,
connectEnd: 0,
secureConnectionStart: 0,
requestStart: 0,
responseStart: 0,
responseEnd: 442.7109999960521,
startTime: 424.57699999795295
}];
这些数据里面,initiatorType 的值需要注意下, initiatorType 并不是指资源的类型,而是指 请求该资源的请求类型, 如上图所示 initiatorType: ‘css’ 的时候,资源文件并不是一个 css 文件,而是一张 img 图片,表明该图片是通过 css 去请求到的 (通常是设置背景图),而直接在网页中通过 <img /> 或在代码中通过 new Image 请求的图片,initiatorType 值都为 img,我们以下面这段代码为例
<!DOCTYPE html>
<html lang=”en”>
<head>
<meta charset=”utf-8″>
<link rel=”stylesheet” href=”index.css”>
<link rel=”stylesheet” href=”http://apps.bdimg.com/libs/bootstrap/3.3.4/css/bootstrap.css”>
<script src=”http://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js”></script>
<style>
body > div {
height: 300px;
width: 300px;
margin: 10px;
float: left;
}
.css-img div {
background: url(‘http://temp.im/110×110/FF2D55/000’) no-repeat;
}
</style>
</head>
<body>
<div class=”css-img”>
<p> 通过 CSS 加载的图片 </p>
<div></div>
</div>
<div class=”normal-img”>
<p> 通过 img 标签直接加载 </p>
<img src=”http://temp.im/178×178/007AFF/fff” />
</div>
<div class=”new-img”>
<p> 通过 new Image() 加载的图片 </p>
</div>
</body>
<script>
window.onload = function () {
var tempImage = new Image();
tempImage.src = “http://temp.im/288×288/FF9500/000”;
document.querySelector(‘.new-img’).appendChild(tempImage)
}
</script>
</html>
打开页面之后在控制台输出结果, 也验证了结果
另外值得注意的一个点是,除了这三张图片,我们在页面中还加载了 2 个 css 文件和 1 个 js 文件,分别是
index.css // 本地文件
bootstrap.min.css // 远程
jquery.min.js // 远程
仔细观察的话,会发现这几个文件有一些不同之处,我们对数据稍作处理,方便参看
我们会发现,其中的 bootstrap.min.css 的很多字段数据都为 0,查询资料之后才知道,该方法通常情况下只能获得本域名下资源的加载信息 (所以 index.css 的信息直接就能获取到)
如果想要获取远程资源的加载信息的只有在 response 里面显式的设置 Timing-Allow-Origin:* 类似于 (Acces-Control-Allow-Origin 的设置)
我们直接打开了 jquery.min.js 连接, 看到了 Timing-Allow-Origin 字段,所以我们能够获得该资源的准确信息, 而在 bootstrap.min.css 的返回中,我们没有找到 Timing-Allow-Origin 字段, 所以数据都显示为 0
3. 代码执行计时
通常情况下,我们想要取得一段代码的执行时间,会做以下操作
var start = +new Date();
for (var i = 0; i < 100; i++) {
ret.push(i);
}
var end = +new Date();
console.log(‘ 执行时间 ’, end – start);
但不足之处在于, new Date 的精确度只到秒, 而有时候我们的代码执行在 1s 之内,计算出的差值为 0,此时就显得无能为力。所以,我们可能通过如下两种方式,解决这个问题
1. 通过 window.performance.now
var start = window.performance.now();
for (var i = 0; i < 100; i++) {
ret.push(i);
}
var end = window.performance.now();
console.log(‘ 执行时间 ’, end – start);
使用方式十分简单,该方法能够计算出精确到 百万分之一秒 的时间差。与 new Date 不同之处在于 window.performance.now 方法返回的是相对于 performance.timing.navigationStart 即页面初始化的时间,但我们的关注点是差值, 所以时间从哪儿算起,对于我们来说并不重要
2. 使用 window.performance.mark 和 window.performance.measure
// 标记开始
window.performance.mark(“start”);
for (var i = 0; i < 100; i++) {
ret.push(i);
}
// 标记结束
window.performance.mark(“end”);
// 计算差值并命名为 difference, 无返回
window.performance.measure(“difference”, “start”, “end”);
通过 window.performance.mark 方法,我们在指定地方打上一个记号,此时不会返回任何值,会被记录到 performance 对象里, entryType 被默认标记为了 mark。
然后通过 window.performance.measure 计算出差值,并通过第一个参数对其赋名为 difference, 此时也没有返回值,这些值也被记录到了 performance 对象里,它们的 entryType 被默认标记为了 measure.
所以我们可以通过两种方式来查看差值
window.performance.getEntriesByType(‘measure’)
`window.perform
esByName(‘difference’)`
![图片上传中 …]
在使用完了之后,我们还可以对这些标识,进行统一的清理
// 清除指定标记点
window.performance.clearMarks(‘start’);
// 清除所有标记点
window.performance.clearMarks();
// 清除指定差值数据
window.performance.clearMeasures(‘difference’);
// 清除所有差值数据
window.performance.clearMeasures();
通过这两种方式,都可以精确的计算出十分精确的时间差值,那如何做选择呢? 我的建议是,如果只是单纯的想要得到少数的差值,直接在代码中使用 now, 而如果需要统计大量的值, 就应该使用 mark 和 measure 方法,因为这些值都会存在 performance 对象里,我们可以在任意需要的时候,通过读取数组,十分便捷的对数据做统一处理与管理
API 支持 && 参考
浏览器对 Performance 支持情况
http://www.infoq.com/cn/articles/html5-performance-api-monitoring
http://ju.outofmemory.cn/entry/204774