写点什么

window.performance(前端性能监控并进行上报)

作者:不叫猫先生
  • 2023-06-06
    北京
  • 本文字数:4493 字

    阅读完需:约 15 分钟

window.performance(前端性能监控并进行上报)

Web Performance 提供了可以通过的函数(performance 属性提供)测试当前网页或者 web 应用的性能,获取更为精确的原始数据,以毫秒为单位。一般放在 window.onload 事件中读取各种数据,有些值必须在页面完全加载之后才能得出。

图示

API 详解

navigationStart 表示从上一个文档卸载结束时的 unix 时间戳,如果没有上一个文档,这个值将和 fetchStart 相等。


  • redirectStart, redirectEnd 如果页面是由 redirect 而来,则 redirectStart 和 redirectEnd 分别代表 redirect 开始和结束的时间节点。跳转且是同域名内部的重定向才算,否则值为 0。fetchStart:浏览器准

  • fetchStart 浏览器发起任何请求之前的时间值,发生在检查本地缓存之前,在 fetchStart 和 domainLookupStart 之间,浏览器会检查当前文档的缓存

  • domainLookupStart domainLookupEnd 分别代表 DNS 查询的开始和结束时间节点。如果浏览器没有进行 DNS 查询(比如使用 cache),则都等于 fetchStart

  • connectStart、connectEnd 分别代表 TCP 建立连接和连接成功的时间节点。如果浏览器没有进行 TCP 连接(比如持久化连接 webscoket),则两者都等于 domainLookupEnd

  • secureConnectionStart 可选。如果页面使用 HTTPS,它的值是安全连接握手之前的时刻。如果该属性不可用,则返回 undefined。如果该属性可用,但没有使用 HTTPS,则返回 0。该 api 位于 connectStart、connectEnd 之间。

  • requestStart 代表浏览器发起请求的时间节点,请求的方式可以是服务器,缓存,本地资源。

  • responseStart, responseEnd 分别代表浏览器收到从服务器端(或缓存、本地资源)响应回的第一个字节和最后一个字节数据的时刻;-domLoading 代表浏览器开始解析 html 文档的时间节点。我们知道 IE 浏览器下的 document 有 readyState 属性,domLoading 的值就等于 readyState 改变为 loading 的时间节点

  • domInteractive 代表浏览器解析 html 文档的状态为 interactive 时的事件节点,即完成解析 DOM 树的时间,Document.readyState 变为 interactive,并将抛出 readystatechange 相关事件。此时只是 DOM 树解析完成,这时候并没有开始加载网页内的资源(外链 css、js)。

  • domContentLoadEventStart 代表 domContentLoaded 事件触发的时间节点:页面文档完全加载并解析完成之后,会触发 DOMContentLoaded,Html 文档不会等待样式文件,图片文件,自框架页面的加载(load 事件可用来检测页面是否完全加载完毕)

  • domContentLoadEventEnd 代表 domContentLoaded 事件完成的时间节点,此时用户可以对页面进行操作

  • domCompleteHtml 文档完全解析完成的时间节点

为什么使用 Image 对象.gif 文件上报

  • 防止跨域只要能上报数据,无论是请求 GIF 文件还是请求其他普通文件(JS)或者是请求接口,服务器端其实并不关心具体的上报方式。但是图片的 src 属性并不会跨域,并且同样可以发起请求

  • 防止阻塞页面加载,影响用户体验通常,创建资源节点后只有将对象注入到浏览器 DOM 树后,浏览器才会实际发送资源请求。反复操作 DOM 不仅会引发性能问题,而且载入 js/css 资源还会阻塞页面渲染,影响用户体验。但是图片请求例外。构造图片打点不仅不用插入 DOM,只要在 js 中 new 出 Image 对象就能发起请求,而且还没有阻塞问题,在没有 js 的浏览器环境中也能通过 img 标签正常打点,这是其他类型的资源请求所做不到的

  • 相比 PNG/JPG,GIF 的体积最小最小的 BMP 文件需要 74 个字节,PNG 需要 67 个字节,而合法的 GIF,只需要 43 个字节。同样的响应,GIF 可以比 BMP 节约 41%的流量,比 PNG 节约 35%的流量

上报数据封装

performance.js


/* eslint-disable no-console */import { VueRouter } from 'vue-router/types/router';import { BaseTrack } from './track';
export class Performance { // TODO 注意上报的单位 现在是毫秒 public static readonly timing = window.performance && window.performance.timing; //初始化 public static init() { if (!this.timing) { console.warn('当前浏览器不支持performance API'); return; } //页面资源加载完后,上报数据 window.addEventListener('load', () => { BaseTrack.track(this.getTimings()); }); } //自定义计算白屏时间 public static record(router?: VueRouter) { const setFPT = () => { if (window.performance && window.performance.now) { this.customFPT = window.performance.now(); } }; return { created: () => { if (router) { //如果是单页面应用,等路由加载完之后,记录时间 router.onReady(() => { setFPT(); }); } else {
setFPT(); } }, }; }
//封装上报数据 public static getTimings(): { [key in string]: number } { if (!this.timing) { console.warn('当前浏览器不支持performance API'); return {}; }
return { redirect: this.getRedirectTime(), dns: this.getDnsTime(), tcp: this.getTcpTime(), ttfb: this.getTimeOfFirstByte(), req: this.getReqTime(), ppdt: this.getParsePureDomTime(), dclt: this.getDomContentLoadTime(), fpt: this.getFirstPaintTime(), load: this.getLoadTime(), }; }
private static customFPT: number = 0;
private static getRedirectTime() { // 重定向耗时 return Performance.timing.redirectEnd - Performance.timing.redirectStart; }
private static getDnsTime() { // dns查询耗时 return Performance.timing.domainLookupEnd - Performance.timing.domainLookupStart; }
private static getTcpTime() { // tcp连接耗时 return Performance.timing.connectEnd - Performance.timing.connectStart; }
private static getTimeOfFirstByte() { // 读取页面第一个字节耗时 return Performance.timing.responseStart - Performance.timing.navigationStart; }
private static getReqTime() { // request请求耗时 return Performance.timing.responseEnd - Performance.timing.responseStart; }
private static getParsePureDomTime() { // 解析纯DOM树耗时, 不包含js css等资源的加载和执行 return Performance.timing.domInteractive - Performance.timing.domLoading; }
private static getDomContentLoadTime() { // 页面资源加载耗时, 包含vue, js css等资源的加载和执行 return Performance.timing.domComplete - Performance.timing.domInteractive; }
private static getFirstPaintTime() { // first paint time, 首次渲染时间, 即白屏时间 // getEntriesByName 浏览器兼容性不好,故有时需要自己去定义 return Math.round( (window.performance.getEntriesByName && window.performance.getEntriesByName('first-paint') && window.performance.getEntriesByName('first-paint')[0] && window.performance.getEntriesByName('first-paint')[0].startTime) || this.customFPT, ); }
private static getLoadTime() { // 页面load总耗时 return Performance.timing.loadEventStart - Performance.timing.navigationStart; }
private static toSeconds(time: number) {}}
复制代码

上报

track.ts


/* eslint-disable no-console */import queryString from 'query-string';import { v4 as uuid } from 'uuid';
let image: HTMLImageElement | null;
export class BaseTrack {
public static track(params: { [key: string]: any }) { try { // if (process.env.NODE_ENV !== 'production') { // // 非生产环境不上报 // return; // } //封装上报数据参数 const qs = queryString.stringify({ timestamp: Date.now(), traceId: this.getTraceId(), url: location.href, ...params, }); //上报 this.reportByImg(qs); } catch (e) { console.error(e); } }
private static serverUrl: string = 'https://open-demo-qiuku.cn-beijing.log.aliyuncs.com/logstores/qiuku-demo/track_ua.gif?APIVersion=0.6.0&';
private static reportByImg(qs: string, retryTimes: number = 3) { const retry = () => { image = null; retryTimes && this.reportByImg(qs, retryTimes - 1); }; return new Promise((resolve) => { try { image = new Image(); image.src = this.serverUrl + qs; //请求失败,再次请求,默认请求失败后请求最多三次 image.onerror = () => { retry(); }; } catch (e) { console.error(e); } }); } //获取追踪Id,标记是哪个用户 private static getTraceId() { try { const traceKey = 'qiuku_track_id'; let traceId = localStorage.getItem(traceKey); if (!traceId) { traceId = uuid(); localStorage.setItem(traceKey, traceId!); } return traceId; } catch (e) { return ''; } }}
复制代码


main.ts


// import '@babel/polyfill';
import Vue from 'vue';import App from './App.vue';import './registerServiceWorker';import router from './router';import { Performance } from '../src/utils/performance';Vue.config.productionTip = false;
//初始化Performance.init();//自定义分析dom渲染的时间Vue.directive('analysis', { inserted(el, options) { console.log(`${options.value} = ${Date.now() - window.performance.timing.navigationStart}`); }});new Vue({ router, render: (h) => h(App), mixins: [Performance.record(router)],}).$mount('#app');
复制代码


发布于: 刚刚阅读数: 5
用户头像

还未添加个人签名 2022-10-18 加入

前端领域优质创作者、阿里云专家博主,专注于前端各领域技术,共同学习共同进步,一起加油呀!

评论

发布
暂无评论
window.performance(前端性能监控并进行上报)_Performance_不叫猫先生_InfoQ写作社区