作为一名开发来讲,以下场景你有没有遇到:
- 点击这个按钮怎么没反应了
- 页面为什么白了
- 怎么一直正在加载
- 很多用户说图片加载不出来
- ......
那么有一款性能监控产品太重要了,但是性能相关的东西实在太多了。那么从一个熟悉又陌生的api开始,performance。
1.什么是performance
mdn上是这么解释的:Performance
接口可以获取到当前页面中与性能相关的信息。它是 High Resolution Time API 的一部分,同时也融合了 Performance Timeline API、Navigation Timing API、User Timing API 和 Resource Timing API。
直接在控制台打印:
除去eventCounts和memory兼容不是很好的属性,重点看下剩余四个。
2.performace.timing属性
我们配合这张经典图来了解这些属性
页面进来之前:
navigationStart:前一个网页卸载时间。
unloadEventStart:前一个网页的upload事件开始
unloadEventEnd:前一个网页的upload事件结束
redirectStart:网页重定向开始时间
redirectEnd:网页重定向结束时间
页面进来之后:
fetchStart:开始请求网页
domainLookupStart:dns查询开始
domainLookupEnd:dns查询结束
connectStart:向服务器建立握手开始
connectEnd:向服务器建立握手结束
secureConnectionStart:安全握手开始,默认值0。非https的没有
requestStart:向服务器发送请求开始
responseStart:服务器返回数据开始
responseEnd:服务器返回数据结束
解析dom开始:
domLoading:解析dom开始,document.readyState为loading
domInteractive:解析dom结束,document.readyState为interactive
domContentLoadedEventStart:DomContentLoaden所有回调开始执行
domContentLoadedEventEnd:ContentLoaded结束,dom内容加载完毕
loadEventStart:load事件发生前
loadEventEnd:load事件发生后
3.实战一波
有了上边的具体数据,如何进行计算进行监控上报呢。
以下代码中的p均为performance.timing对象
3.1 网络连接相关
// 上一个页面的时间,没多大用途 const pervPage = p.fetchStart - p.navigationStart // 重定向时间 const redirect = p.redirectEnd - p.redirectStart // dns查找时间 const dns = p.domainLookupEnd - p.domainLookupStart // tcp建立时间 const connect = p.connectEnd - p.connectStart // 网络总耗时 const network = p.connectEnd - p.navigationStart
网络建连层如果太慢或者出问题,那么首先应该和运维部门沟通,查找问题,和前端后端关联性不大。
3.2 网络接收相关
// 前端发送到接收的时间 const send = p.responseStart - p.requestStart // 接收数据用时 const receive = p.responseEnd - p.responseStart // 总耗时 const request = p.responseEnd - p.requestStart
3.3 前端渲染
// dom解析时间 const dom = p.domComplete - p.domLoading // loadEvent时间 const loadEvent = p.loadEventEnd - p.loadEventStart // 总耗时 const frontend = p.loadEventEnd - p.domLoading
3.4 关键阶段
// 页面完全加载的时间 const load = p.loadEventEnd - p.navigationStart // dom准备时间 const domReady = p.domainLookupStart - p.navigationStart // 可操作时间 const interactive = p.domInteractive - p.navigationStart // 首字节时间 const ttfb = p.responseStart - p.navigationStart
3.5 使用
// dom解析完成 let isDOMReady = false; // callback 就是获取上边计算performance各个指标的回调 function domready (callback) { if (isDOMReady) return; let timer = null; let runCheck = () => { if (performance.timing.domComplete) { clearTimeout(timer); callback(); isDOMReady = true; // 1、停止循环检测 2、运行callback } else { // 再去循环检测 timer = setTimeout(runCheck, 100); } } if (document.readyState === "interactive") { callback(); return; } document.addEventListener('DOMContentLoaded', () => { // 开始循环检测 是否 DOMContentLoaded 已经完成了 runCheck(); }) }
// 在onload事件中 let isOnload = false; function onl oad (callback) { if (isOnload) return; let timer = null; let runCheck = () => { if (performance.timing.loadEventEnd) { clearTimeout(timer); callback(); isOnload = true; // 1、停止循环检测 2、运行callback } else { // 再去循环检测 timer = setTimeout(runCheck, 100); } } if (document.readyState === "interactive") { callback(); return; } window.addEventListener('load', () => { runCheck(); }) }
在domready和onload方法中,可以避免出现负数问题。
4. 资源监控
performance提供了getEnteries方法,用来获取加载的资源文件,返回数组形式。
同样的,我们也可以封装个方法,根据这些属性,计算出想要的信息。代码中r代表数组某一项
const resourceData = { initiatorType: r.initiatorType, name: r.name, duration: parseInt(r.duration), // 连接过程 redirect: r.redirectEnd - r.redirectStart, // 重定向的时间 dns: r.domainLookupEnd - r.domainLookupStart, // dns查找的时间 connect: r.connectEnd - r.connectStart, // tcp连接的时间 network: r.connectEnd - r.startTime, // 网络总耗时 // 接收过程 send: r.responseStart - r.requestStart, // 发送开始到接收的总时长 receive: r.responseEnd - r.responseStart, // 接收的总时长 request: r.responseEnd - r.requestStart, // 接收的总耗时 // 核心指标 ttfb: r.responseStart - r.requestStart, // 首字节时间 }
但是,如果我们开发是一个商城网站,里边会有很多css、js、img等资源,他们都值得监控吗?
可以封装一个方法,传入link、script、或者图片名称包含包含某些字段的,或者随机抽取几个,我们去自定义去监听规则。有兴趣可以搜索一下大量日志log上报策略。
5. ajax请求监控
在实战项目中,肯定会涉及到网络请求和后端的交互,如何监听ajax请求的相关数据呢?
通过修改XMLHttpRequest原型,自定义open和send方法来达到目的。
const ajax = () => { let xhr = window.XMLHttpRequest; if (xhr._eagle_monitor_flag) return; xhr._eagle_monitor_flag = true; let _originOpen = xhr.prototype.open; xhr.prototype.open = function (method, url, async, user, password) { // 往xhr上自定义属性 this._eagle_xhr_info = { url, method, status: null } return _originOpen.apply(this, arguments); } let _originSend = xhr.prototype.send; xhr.prototype.send = function () { let _self = this; this._eagle_start_time = Date.now(); let ajaxEnd = (eventType) => () => { if (_self.response) { // 统计返回数据的大小 let responseSize = null; switch (_self.responseType) { case 'json': // JSON兼容性问题 && stringify报错 responseSize = JSON.stringify(_self.response).length; break; case 'arraybuffer': responseSize = _self.response.byteLength; break; default: responseSize = _self.responseText.length; // 简单使用responseText的值 } _self._eagle_xhr_info.event = eventType; _self._eagle_xhr_info.status = _self.status; _self._eagle_xhr_info.success = _self.status === 200; _self._eagle_xhr_info.duration = Date.now() = _self._eagle_start_time; _self._eagle_xhr_info.responseSize = responseSize; _self._eagle_xhr_info.requestSize = value ? value.length : 0 // value一定有length?百度查一下兼容写法; _self._eagle_xhr_info.type = 'xhr'; // 打印_eagle_xhr_info,进行额外操作 // console.log(_self._eagle_xhr_info) // callback(_self._eagle_xhr_info) } }; // 这三种状态都代表着请求已经结束了 // 需要统计一些信息,并进行上报 this.addEventListener('load', ajaxEnd('load'), false); this.addEventListener('error', false); this.addEventListener('abort', false); return _originSend.apply(this, arguments); } }标签:eagle,const,前端,self,xhr,let,监控,._,性能 From: https://www.cnblogs.com/zmyxixihaha/p/18134552