前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >vue 性能监控分析

vue 性能监控分析

原创
作者头像
Yerik
修改2022-04-15 00:26:36
1.8K0
修改2022-04-15 00:26:36
举报
文章被收录于专栏:烹饪一朵云烹饪一朵云

我们在浏览网站的时候,不可避免的需要消耗自身计算机资源,比如带宽、cpu、存储等等,这些资源会随着访问时间的延长而产生一定的数据碎片,在我们没有关闭浏览器的时候,这些碎片会一直存在的,那么作为开发者我们开发的网站是否优质,是否对用户计算机负担小,打开是否未低时延这些就是我们用来衡量网站是否卓越的指标了。

网站渲染时序.png
网站渲染时序.png

vue作为一款很卓越的前端开发框架,开发起来很舒服,那性能怎样呢?这个时候就需要使用性能检测相关的api来进行性能数据化,方便我们对网站性能进行优化。

打开一个网站发生了什么

打开了一个网站,浏览器达到做了什么呢?下面这个流程很清晰的说明了整个过程

渲染时序.png
渲染时序.png

服务端接收了https请求之后,将会将相关的js代码发送回来,这个时候则对js文件进行渲染,浏览器的渲染流程如下

渲染原理.png
渲染原理.png

当我们在浏览器地址输入URL时,浏览器会发送请求到服务器,服务器将请求的HTML文档发送回浏览器,浏览器将文档下载下来后,便开始从上到下解析,解析完成之后,会生成DOM。如果页面中有css,会根据css的内容形成CSSOM,然后DOM和CSSOM会生成一个渲染树,最后浏览器会根据渲染树的内容计算出各个节点在页面中的确切大小和位置,并将其绘制在浏览器上。

html的解析又会被js打断,解析过程中遇到阻塞,因而在现在浏览器中,为了减缓渲染被阻塞的情况,现代的浏览器都使用了猜测预加载。当解析被阻塞的时候,浏览器会有一个轻量级的HTML或CSS扫描器scanner继续在文档中扫描,查找那些将来可能能够用到的资源文件的url,在渲染器使用它们之前将其下载下来,并且下载是可以并行进行的,并行的上限一般为6。

后续有时间的话,想尝试着总结浏览器渲染原理——像素的一生,最近写的浅析$nextTick和$forceUpdate对这个过程已经有了只言片语的描述,想要更加全面的阐述,还是要更加专门的一篇文章来进行刻意分析。

Performance API

具体参考js标准教程Performance API

主要用到的是performance.timing对象,具体解释见上面的链接,下面这张图对应各个指标的时间点。

image.png
image.png

Performace API允许访问当前页面性能相关的信息,它用于精确度量、控制、增强浏览器的性能表现。这个API为测量网站性能,提供以前没有办法做到的精度。

比如我们想知道,Vue初始化的准确耗时,我们可以使用Date对象的getTime方法,这样去做:

代码语言:javascript
复制
let start = new Date().getTime();

// expose real self
// 初始化核心代码
vm._self = vm
initLifecycle(vm) // $parent/$children等等
initEvents(vm) // 事件监听
initRender(vm) // 插槽、_c...
callHook(vm, 'beforeCreate')
initInjections(vm) // resolve injections before data/props
initState(vm) // 初始化data、prop、method并执行响应式
initProvide(vm) // resolve provide after data/props
callHook(vm, 'created') // 这里可以访问组件状态

let now = new Date().getTime();
let latency = now - start;
console.log("初始化时间:" + latency);

上面这种做法有两个不足之处:

getTime方法(以及Date对象的其他方法)都只能精确到毫秒级别(一秒的千分之一),想要得到更小的时间差别就无能为力了,此外这种写法只能获取代码运行过程中的时间进度,无法知道一些后台事件的时间进度,比如浏览器用了多少时间从服务器加载网页。

为了解决这两个不足之处,ES5引入“高精度时间戳”这个API,部署在performance对象上。它的精度可以达到1毫秒的千分之一(1秒的百万分之一),这对于衡量的程序的细微差别,提高程序运行速度很有好处,而且还可以获取后台事件的时间进度。

Performance API提供了很多方便测试我们程序性能的接口,他除了一些上古版本,在现在的主流浏览器中都能兼容,因此很多优秀的框架也用到了这个API进行测试。

Performance属性

先来看看在Edge浏览器控制台中执行window.performance会出现什么:

edge.png
edge.png

而在chrome上面是这样的

chrome.png
chrome.png

简单解释下每个属性的意义

memory

memory是非标准属性,不是所有浏览都会有,它表明了Performance有多少内存。

代码语言:javascript
复制
memory: MemoryInfo
jsHeapSizeLimit: 4294705152 // JS 对象(包括V8引擎内部对象)占用的内存,一定小于 totalJSHeapSize
totalJSHeapSize: 14701421 // 可使用的内存
usedJSHeapSize: 11103057 // 内存大小限制

timeOrigin

timeOrigin是非标准属性,不是所有浏览都会有,它是一系列时间点的基准点,精确到万分之一毫秒。

代码语言:txt
复制
timeOrigin: 1649942785896.2

navigation

navigation呈现了如何导航到当前文档的信息,解释了我从哪里来?这个问题

代码语言:javascript
复制
navigation: PerformanceNavigation
redirectCount: 0 // 如果有重定向的话,页面通过几次重定向跳转而来
type: 1 
 // 0   即 TYPE\_NAVIGATENEXT 正常进入的页面(非刷新、非重定向等)

 // 1   即 TYPE\_RELOAD       通过 window.location.reload() 刷新的页面

 // 2   即 TYPE\_BACK\_FORWARD 通过浏览器的前进后退按钮进入的页面(历史记录)

 // 255 即 TYPE\_UNDEFINED    非以上方式进入的页面

onresourcetimingbufferfull

onresourcetimingbufferfull是一个在resourcetimingbufferfull事件触发时会被调用的event handler。它的值是一个手动设置的回调函数,这个回调函数会在浏览器的资源时间性能缓冲区满时执行。

代码语言:javascript
复制
function buffer_full(event) {
  console.log("WARNING: Resource Timing Buffer is FULL!");
  performance.setResourceTimingBufferSize(200);
}
function init() {
  // Set a callback if the resource buffer becomes filled
  performance.onresourcetimingbufferfull = buffer_full;
}
<body onload="init()">

上面是MDN官方给的一个example,意思就是当缓冲区满时,执行这个buffer_full函数。

timing

是一系列关键时间点,它包含了网络、解析等一系列的时间数据。我们先来看看关键时间点的时序图:

image.png
image.png

然后我们再来梳理一下每个属性的含义:

代码语言:javascript
复制
timing: PerformanceTiming
// 在同一个浏览器上下文中,前一个网页(与当前页面不一定同域)unload 的时间戳,如果无前一个网页 unload ,则与 fetchStart 值相等
connectEnd: 1649942785899  // HTTP(TCP) 完成建立连接的时间(完成握手),如果是持久连接,则与 fetchStart 值相等,注意如果在传输层发生了错误且重新建立连接,则这里显示的是新建立的连接完成的时间,这里握手结束,包括安全连接建立完成、SOCKS 授权通过
connectStart: 1649942785899 // HTTP(TCP) 开始建立连接的时间,如果是持久连接,则与 fetchStart 值相等,注意如果在传输层发生了错误且重新建立连接,则这里显示的是新建立的连接开始的时间
domComplete: 1649942786530 // DOM 树解析完成,且资源也准备就绪的时间,Document.readyState 变为 complete,并将抛出 readystatechange 相关事件
domContentLoadedEventEnd: 1649942786450  // DOM 解析完成后,网页内资源加载开始的时间,在 DOMContentLoaded 事件抛出前发生
domContentLoadedEventStart: 1649942786450 // DOM 解析完成后,网页内资源加载完成的时间(如 JS 脚本加载执行完毕)
domInteractive: 1649942786450 // 注意只是 DOM 树解析完成,这时候并没有开始加载网页内的资源
domLoading: 1649942786080 // 开始解析渲染 DOM 树的时间,此时 Document.readyState 变为 loading,并将抛出 readystatechange 相关事件
domainLookupEnd: 1649942785899 // DNS 域名查询完成的时间,如果使用了本地缓存(即无 DNS 查询)或持久连接,则与 fetchStart 值相等
domainLookupStart: 1649942785899 // DNS 域名查询开始的时间,如果使用了本地缓存(即无 DNS 查询)或持久连接,则与 fetchStart 值相等
fetchStart: 1649942785899 // 浏览器准备好使用 HTTP 请求抓取文档的时间,这发生在检查本地缓存之前
loadEventEnd: 1649942786531 // load 事件的回调函数执行完毕的时间
loadEventStart: 1649942786530  // load 事件发送给文档,也即 load 回调函数开始执行的时间,注意如果没有绑定 load 事件,值为 0
navigationStart: 1649942785896
redirectEnd: 0 // 最后一个 HTTP 重定向完成时的时间。有跳转且是同域名内部的重定向才算,否则值为 0
redirectStart: 0 // 第一个 HTTP 重定向发生时的时间。有跳转且是同域名内的重定向才算,否则值为 0
requestStart: 1649942785903  // HTTP 请求读取真实文档开始的时间(完成建立连接),包括从本地读取缓存,连接错误重连时,这里显示的也是新建立连接的时间
responseEnd: 1649942786434 // HTTP 响应全部接收完成的时间(获取到最后一个字节),包括从本地读取缓存
responseStart: 1649942786060 // HTTP 开始接收响应的时间(获取到第一个字节),包括从本地读取缓存
secureConnectionStart: 0 // HTTPS 连接开始的时间,如果不是安全连接,则值为 0
unloadEventEnd: 1649942786077 // 和 unloadEventStart 相对应,返回前一个网页 unload 事件绑定的回调函数执行完毕的时间戳
unloadEventStart: 1649942786077 // 前一个网页(与当前页面同域)unload 的时间戳,如果无前一个网页 unload 或者前一个网页与当前页面不同域,则值为 0

Performance方法

在刚刚打印出的Performance对象的原型上挂载了很多方法:

Performance method.png
Performance method.png

我们再来梳理一下每个方法做了什么事:

getEntries()

浏览器获取网页时,会对网页中每一个对象(脚本文件、样式表、图片文件等等)发出一个HTTP请求。performance.getEntries方法以数组形式,返回这些请求的时间统计信息,有多少个请求,返回数组就会有多少个成员。与performance.timing对比,它新增了nameentryTypeinitiatorTypeduration属性。

代码语言:javascript
复制
var entry = {  
    // 资源名称,也是资源的绝对路径
    name: "http://cdn.alloyteam.com/wp-content/themes/alloyteam/style.css",
    // 资源类型
    entryType: "resource",
    // 谁发起的请求
    initiatorType: "link", // link 即 <link> 标签
                           // script 即 <script>
                           // redirect 即重定向
    // 加载时间
    duration: 18.13399999809917,
 
    redirectStart: 0,
    redirectEnd: 0,
 
    fetchStart: 424.57699999795295,
 
    domainLookupStart: 0,
    domainLookupEnd: 0,
 
    connectStart: 0,
    connectEnd: 0,
 
    secureConnectionStart: 0,
 
    requestStart: 0,
 
    responseStart: 0,
    responseEnd: 442.7109999960521,
 
    startTime: 424.57699999795295
};

mark() 和 clearMarks()

mark方法用于为相应的视点做标记。它标记各种时间戳,保存为各种测量值,有了这些值我们便可以批量地分析这些数据了。就像在地图上打点一样,有了这些点以后,我们就可以测量地图上的点之间的距离。

代码语言:javascript
复制
let nameStart = 'markStart';
let nameEnd   = 'markEnd';
// 函数执行前做个标记
window.performance.mark(nameStart);
  for (var i = 0; i < n; i++) {
    doSomething
  }
// 函数执行后再做个标记
window.performance.mark(nameEnd);

clearMarks方法用于清除标记,如果不加参数,就表示清除所有标记。

代码语言:javascript
复制
// 只清除markStart对应的PerformanceMark
window.peformance.clearMarks('markStart');
// 清除全部PerformanceMark
window.performance.clearMarks();
getEntriesByName() 和 getEntriesByType()

返回一个PerformanceEntry对象的列表,基于给定的nameentry type

例如我们可以输出所有保存起来的标记mark

代码语言:javascript
复制
// 看下保存起来的标记 mark
let marks = window.performance.getEntriesByType('mark'); 

console.log(marks);

measure() 和 clearMeasures()

有了刚刚mark()方法做的标记,再加上getEntriesByName()获取到它标记的时间戳,我们就可以用measure()测量两个标记间的时间间隔啦。继续刚刚那个例子:

代码语言:javascript
复制
let nameStart = 'markStart';
let nameEnd   = 'markEnd';
// 函数执行前做个标记
window.performance.mark(nameStart);
  for (var i = 0; i < n; i++) {
    doSomething
  }
// 函数执行后再做个标记
window.performance.mark(nameEnd);
// 测量两个标记间的时间间隔
let name = 'functionMeasure';
window.performance.measure(name, nameStart, nameEnd);

// 获取我们自定义的测量
let entries = window.performance.getEntriesByName(name);

当测量完成之后,我就能用getEntriesByName()来获取到我们自定义的测量值。

当然,除了测量自定义的mark,我们还可以用来测量performance.timing里的属性,例如我们要计算domReay的时间:

代码语言:javascript
复制
// domReady 时间
// 直接计算方式
let t = performance.timing 
let domReadyTime = t.domComplete - t.responseEnd; 
console.log(domReadyTime)

// measure方式
window.performance.measure('domReady','responseEnd' , 'domComplete'); 
let domReadyMeasure = window.performance.getEntriesByName('domReady');  
console.log(domReadyMeasure);

clearMeasuresclearMarks方法类似,用于清除测量,如果不加参数,就表示清除所有测量。

代码语言:javascript
复制
// 只清除domReadyMeasure对应的PerformanceMeasure
window.peformance.clearMeasures('domReadyMeasure');
// 清除全部PerformanceMeasure
window.performance.clearMeasures()

Performance在Vue中的应用

在Vue中,有个全局配置performance,当它设置为true时,可以在浏览器开发工具的性能/时间线面板中启用对组件初始化、编译、渲染和打补丁的性能追踪。

我们以初始化的代码为例,看看Vue怎么去实现性能追踪的。

代码语言:javascript
复制
  import config from '../config'
  import { mark, measure } from '../util/perf'
  
  Vue.prototype._init = function (options?: Object) {
    const vm: Component = this
    // a uid
    vm._uid = uid++

    let startTag, endTag
    /* istanbul ignore if */
    if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
      startTag = `vue-perf-start:${vm._uid}`
      endTag = `vue-perf-end:${vm._uid}`
      mark(startTag)
    }

    // 省略一些balabala......
    
    // expose real self
    // 初始化核心代码
    vm._self = vm
    initLifecycle(vm) // $parent/$children等等
    initEvents(vm) // 事件监听
    initRender(vm) // 插槽、_c...
    callHook(vm, 'beforeCreate')
    initInjections(vm) // resolve injections before data/props
    initState(vm) // 初始化data、prop、method并执行响应式
    initProvide(vm) // resolve provide after data/props
    callHook(vm, 'created') // 这里可以访问组件状态

    /* istanbul ignore if */
    if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
      vm._name = formatComponentName(vm, false)
      mark(endTag)
      measure(`vue ${vm._name} init`, startTag, endTag)
    }

    if (vm.$options.el) {
      vm.$mount(vm.$options.el)
    }
  }
}

我们看到在init核心代码执行之前,会定义两个标记名称startTag, endTag,这个名称跟vue实例的唯一id:vm._uid对应。然后会调用mark方法打一个开始标记mark(startTag)。然后等初始化完成之后,会调用mark方法打一个结束标记mark(endTag),接着执行一个测量measure('vue ${vm._name} init', startTag, endTag)

这个两个方法来自于util/perf.js,看下具体代码:

代码语言:javascript
复制
import { inBrowser } from './env'

export let mark
export let measure

if (process.env.NODE_ENV !== 'production') {
  const perf = inBrowser && window.performance
  /* istanbul ignore if */
  if (
    perf &&
    perf.mark &&
    perf.measure &&
    perf.clearMarks &&
    perf.clearMeasures
  ) {
    mark = tag => perf.mark(tag)
    measure = (name, startTag, endTag) => {
      perf.measure(name, startTag, endTag)
      perf.clearMarks(startTag)
      perf.clearMarks(endTag)
      // perf.clearMeasures(name)
    }
  }
}

可以看到,这两个方法实际上就是对Performance API方法的一个封装:

mark方法就是直接返回的Performancemark方法

measure方法是在执行了Performancemeasure方法后,把开始结束两个mark值删除。

我们在new Vue之前开启performance这个全局配置,来看看最终的测量效果

代码语言:javascript
复制
if (process.env.NODE_ENV !== 'production') {
    Vue.config.performance = true;
}
// 创建实例
const app = new Vue({
    el: '#demo',
    data:{foo:'foo'}
})

然后我们把保存起来的测量measurelog出来,就能找到初始化时的耗时。

参考资料

  1. https://segmentfault.com/a/1190000022682776
  2. https://caniuse.com/nav-timing
  3. https://blog.csdn.net/wangchengiii/article/details/89186315
  4. http://www.alloyteam.com/2015/09/explore-performance/

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 打开一个网站发生了什么
  • Performance API
    • Performance属性
      • memory
      • timeOrigin
      • navigation
      • onresourcetimingbufferfull
      • timing
    • Performance方法
      • getEntries()
      • mark() 和 clearMarks()
      • measure() 和 clearMeasures()
    • Performance在Vue中的应用
    • 参考资料
    领券
    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档