2018你应该知道的Web性能信息采集指南

假设您正在访问一个网站,若是Web内容不在几秒内显示在屏幕上,那么做为用户您可能会选择关闭标签页,转去浏览其余页面从而代替这个网页的内容。可是做为Web开发者,您可能但愿跟踪请求导航的详细信息,这样你就能够知道为何这个网页的速度在变慢。javascript

W3C性能工做组提供了能够用来测量和改进Web应用性能的用户代理(User Agent)特性与API。开发者可使用这些API来收集精确的性能信息,从不一样方面找出Web应用的性能瓶颈并提升应用的性能。css

这些特性和API适用于桌面、移动浏览器以及其余非浏览器环境。html

因为这些特性与API不止适用于浏览器,还适用于非浏览器环境,因此本文会大量使用“用户代理”这个词来代替“浏览器”前端

1. 如何得到高精度的时间?

ECMA-262 规范中定义了 Date 对象来表示自 1970 年 1 月 1 日以来的毫秒数。它足以知足大部分需求,但缺点是时间会受到时钟误差与系统时钟调整的影响。时间的值不老是单调递增,后续值有可能会减小或者保持不变。java

例如,下面这段代码计算出来的“duration”有可能被记录为正数、负数或零。git

const mark_start = Date.now()
doTask() // Some task
const duration = Date.now() - mark_start
复制代码

上面这段代码获取的持续时间“duration”并不精准,它会受到时钟误差与系统时钟调整的影响,因此最终获得的“duration”可能为正数、负数或零,咱们根本不知道它记录的时间到底是不是正确的时间。github

高精度时间(High Resolution Time,简称hr-time)规范定义了Performance对象,经过Performance对象咱们能够得到高精度的时间。canvas

Performance对象包含方法now和属性timeOrigin跨域

  • 方法now被执行后会返回从 timeOrigin 到如今的高精度时间。数组

    当前时间 - performance.timeOrigin

  • 属性timeOrigin返回页面浏览上下文第一次被建立的时间。若是全局对象为WorkerGlobalScope,那么timeOrigin为worker被建立的时间。

    timeOrigin 的时间值不受时钟误差与系统时钟调整的影响。

    例如,当timeOrigin的值被肯定以后,不管将系统时间设置到什么时间,下面代码始终返回timeOrigin最初被赋予的时间:

    new Date(performance.timeOrigin).toLocaleString()
    // 2018/8/6 上午11:41:58
    复制代码

若是两个时间值拥有相同的时间起源(Time Origin),那么使用 performance.now 方法返回的任意两个按时间顺序记录的时间值之间的差值永远不多是负数。

例如,下面这段代码计算出来的“duration”永远不可能为负数。

const mark_start = performance.now()
doTask() // Some task
const duration = performance.now() - mark_start
复制代码

经过performance.timeOrigin + performance.now 能够获得精准的当前时间。该时间不受时钟误差与系统时钟调整的影响。

不受时钟误差与系统时钟调整的影响指的是当timeOrigin的值被肯定以后修改了系统时间,这时候timeOrigin不会受到影响。

const timeStamp = performance.timeOrigin + performance.now()
console.log(timeStamp) // 1533539552977.5718
new Date(timeStamp).toLocaleString()
// "2018/8/6 下午3:10:42"
复制代码

2. 性能时间线(Performance Timeline)

在介绍如何获取性能指标以前,咱们须要先介绍“性能时间线”,它提供了统一的接口来获取各类性能相关的度量数据。它是本文即将要介绍的其余获取性能指标方法的基础。

“性能时间线”自己并不提供任何性能信息,但它会提供一些方法,当您想要得到性能信息时,可使用“性能时间线”提供的方法来获得您想获取的性能信息。

本文后面会详细介绍从“性能时间线”中能够访问哪些性能信息

2.1 扩展Performance对象

“性能时间线”扩展了Performance对象,新增了一些用于从“性能时间线”中获取性能指标数据的属性与方法。

下表给出了在Performance对象上新增的方法:

方法名 做用
getEntries() 返回一个列表,该列表包含一些用于承载各类性能数据的对象,不作任何过滤
getEntriesByType() 返回一个列表,该列表包含一些用于承载各类性能数据的对象,按类型过滤
getEntriesByName() 返回一个列表,,该列表包含一些用于承载各类性能数据的对象,按名称过滤

表中给出了三个方法,使用这些方法能够获得一个列表,列表中包含一系列用于承载各类性能数据的对象。换句话说,使用这些对象能够获得咱们想要得到的各类性能信息。

在术语上这个列表叫作PerformanceEntryList,而列表中的对象叫作PerformanceEntry

不一样方法的过滤条件不一样,因此列表中的PerformanceEntry对象所包含的数据也不一样。

2.2 PerformanceEntry对象

“性能时间线”定义了PerformanceEntry对象,该对象承载了各类性能相关的数据。下表给出了PerformanceEntry对象所包含的属性:

属性名 做用
name 经过该属性能够获得PerformanceEntry对象的标识符,不惟一
entryType 经过该属性能够获得PerformanceEntry对象的类型
startTime 经过该属性能够获得一个时间戳
duration 经过该属性能够获得持续时间

从上表中能够发现,“性能时间线”并无明肯定义PerformanceEntry对象应该返回什么具体内容,它只是定义了一个格式,返回的具体内容会根据咱们获取的性能数据类型的不一样而不一样。本文的后面咱们会详细介绍。

2.3 PerformanceObserver

“性能时间线”还定义了一个很是重要的接口用来观察“性能时间线”记录新的性能信息,当一个新的性能信息被记录时,观察者将会收到通知。它就是PerformanceObserver。例如,能够经过下面代码定义一个长任务观察者:

const observer = new PerformanceObserver(function (list) {
  // 当记录一个新的性能指标时执行
})
// 注册长任务观察者
observer.observe({entryTypes: ['longtask']})
复制代码

上面这段代码使用PerformanceObserver注册了一个长任务观察者,当一个新的长任务性能信息被记录时,回调会被触发。

回调函数会接收到两个参数:第一个参数是一个列表,第二个参数是观察者实例。

在术语上这个列表被称为PerformanceObserverEntryList,而且包含三个方法getEntriesgetEntriesByTypegetEntriesByName。能够经过这三个方法得到PerformanceEntryList列表。这三个方法功能于使用方式均与前面介绍的相同。

3. 如何收集“资源加载”相关性能度量数据?

获取资源加载相关的时间信息可让咱们知道咱们的页面须要让用户等待多久。下面这段简单的JavaScript代码尝试测量加载资源所需的时间:

<!doctype html>
<html>
  <head></head>
  <body onload="loadResources()">
    <script> function loadResources() { const start = new Date().getTime() const image1 = new Image() const resourceTiming = function() { const now = new Date().getTime() const latency = now - start console.log('End to end resource fetch: ' + latency) } image1.onload = resourceTiming image1.src = 'https://www.w3.org/Icons/w3c_main.png' } </script>
    <img src="https://www.w3.org/Icons/w3c_home.png">
  </body>
</html>
复制代码

虽然这段代码能够测量资源的加载时间,但它不能得到资源加载过程当中各个阶段详细的时间信息。同时这段代码并不能投放到生产环境,由于它有不少问题:

  • 在CSS中使用@import url()background: url()加载的资源应该如何测量计时信息?
  • 如何测量经过HTML标签元素加载的资源的计时信息?例如linkimgscript
  • 若是资源是经过xmlhttprequest请求的,如何测量资源的计时信息?
  • 经过fetch方法请求的资源如何测量计时信息?
  • 经过beacon发送的请求如何测量计时信息?
  • 上面代码并不通用,如何测量全部资源的加载信息?
  • 还有不少其余状况都没法测量

幸运的是,W3C性能工做组定义了资源计时(Resource Timing)规范让Web开发者能够获取很是详细的资源计时信息。

下面这个例子能够获取更加详细的资源计时信息:

<!doctype html>
<html>
  <head>
  </head>
  <body onload="loadResources()">
    <script> function loadResources () { const image1 = new Image() image1.onload = resourceTiming image1.src = 'https://www.w3.org/Icons/w3c_main.png' } function resourceTiming () { const resourceList = window.performance.getEntriesByType('resource') for (let i = 0; i < resourceList.length; i++) { console.log('End to end resource fetch: ' + (resourceList[i].responseEnd - resourceList[i].startTime)) } } </script>
    <img id="image0" src="https://www.w3.org/Icons/w3c_home.png">
  </body>
</html>
复制代码

上面代码经过performance.getEntriesByType方法获得一个列表,这个列表就是咱们前面介绍的PerformanceEntryList,并过滤出全部类型为resourcePerformanceEntry对象。

类型为resourcePerformanceEntry对象在术语上被称为PerformanceResourceTiming对象。

PerformanceResourceTiming对象扩展了PerformanceEntry对象并新增了不少属性用于获取详细的资源计时信息,PerformanceResourceTiming对象的全部属性与其对应的做用以下表所示:

属性名 做用
name 请求资源的绝对地址,即使请求重定向到一个新的地址此属性也不会改变
entryType PerformanceResourceTiming对象的entryType属性永远返回字符串“resource”
startTime 用户代理开始排队获取资源的时间。若是HTTP重定则该属性与redirectStart属性相同,其余状况该属性将与fetchStart相同
duration 该属性将返回 responseEndstartTime之间的时间
initiatorType 发起资源的类型
nextHopProtocol 请求资源的网络协议
workerStart 若是当前上下文是”worker”,则workerStart属性返回开始获取资源的时间,不然返回0
redirectStart 资源开始重定向的时间,若是没有重定向则返回0
redirectEnd 资源重定向结束的时间,若是没有重定向则返回0
fetchStart 开始获取资源的时间,若是资源重定向了,那么时间为最后一个重定向资源的开始获取时间
domainLookupStart 资源开始进行DNS查询的时间(若是没有进行DNS查询,例如使用了缓存或本地资源则时间等于fetchStart)
domainLookupEnd 资源完成DNS查询的时间(若是没有进行DNS查询,例如使用了缓存或本地资源则时间等于fetchStart)
connectStart 用户代理开始与服务器创建用来检索资源的链接的时间(TCP创建链接的时间)
connectEnd 用户代理完成与服务器创建的用来检索资源的链接的时间(TCP链接成功的时间)
secureConnectionStart 如资源使用安全传输,那么用户代理会启动握手过程以确保当前链接。该属性表明握手开始时间(若是页面使用HTTPS那么值是安全链接握手以前的时间)
requestStart 开始请求资源的时间
responseStart 用户代理开始接收Response信息的时间(开始接受Response的第一个字节,例如HTTP/2的帧头或HTTP/1.x的Response状态行)
responseEnd 用户代理接收到资源的最后一个字节的时间,或在传输链接关闭以前的时间,使用先到者的时间。或者是因为网络错误而终止网络的时间
transferSize 表示资源的大小(以八位字节为单位),该大小包括响应头字段和响应有效内容主体(Payload Body)
encodedBodySize 表示从HTTP网络或缓存中接收到的有效内容主体(Payload Body)的大小(在删除全部应用内容编码以前)
decodedBodySize 表示从HTTP网络或缓存中接收到的消息主体(Message Body)的大小(在删除全部应用内容编码以后)

因为有一些属性功能比较复杂,下面将针对一些功能比较复杂的属性详细介绍。

3.1 initiatorType

简单来讲initiatorType属性返回的内容表明资源是从哪里发生的请求行为。

initiatorType属性会返回下面列表中列出的字符串中的其中一个:

类型 描述
css 若是请求是从CSS中的url()指令发出的,例如 @import url()background: url()
xmlhttprequest 经过XMLHttpRequest对象发出的请求
fetch 经过Fetch方法发出的请求
beacon 经过beacon方法发出的请求
link 经过link标签发出的请求
script 经过script标签发出的请求
iframe 经过iframe标签发出的请求
other 没有匹配上面条件的请求

3.2 domainLookupStart

准确的说,domainLookupStart属性会返回下列值中的其中一个:

  • 若是使用了持久链接(persistent connection),或者从相关应用缓存(relevant application cache)或从本地资源中获取资源,那么domainLookupStart的值与fetchStart相同
  • 若是用户代理在缓存中具备域信息,那么domainLookupStart等于开始从域信息缓存中检索域数据的时间
  • 用户代理开始对资源进行域名查询前的时间
  • 其余状况为0

3.3 domainLookupEnd

domainLookupEnd属性会返回下列值中的其中一个:

  • domainLookupStart相同,若是使用了持久链接(persistent connection),或者从相关应用缓存(relevant application cache)或本地资源中获取资源,那么domainLookupEnd的值与fetchStart相同
  • 若是用户代理在缓存中具备域信息,那么domainLookupEnd为从域信息缓存中检索域数据结束时的时间
  • 用户代理完成对资源进行域名查询的时间
  • 其余状况为0

3.4 过程模型

下图给出了PerformanceResourceTiming对象定义的时序属性。当从不一样来源获取资源时,括号中的属性可能不可用。用户代理能够在时间点之间执行内部处理。

图1 PerformanceResourceTiming接口定义的时序属性

图1 PerformanceResourceTiming 过程模型

4. 如何收集“网页加载”相关性能度量数据?

精准地测量Web应用的性能是使Web应用更快的一个重要方面。虽然利用JavaScript提供的能力能够测量用户等待时间(咱们常说的埋点),但在更多状况下,它并不能提供完整或详细的等待时间。例如,下面的JavaScript使用了一个很是天真的方式尝试测量页面彻底加载完所须要的时间:

<html>
  <head>
    <script type="text/javascript"> const start = new Date().getTime() function onLoad() { const now = new Date().getTime() const latency = now - start console.log('page loading time: ' + latency) } </script>
  </head>
  <body onload="onLoad()">
    <!- Main page body goes from here. -->
  </body>
</html>
复制代码

上面的代码将计算在执行head标签中的第一行JavaScript以后加载页面所需的时间,可是它没有提供任何有关从服务端获取页面所需的时间信息,或页面的初始化生命周期。

对于这种需求,W3C性能工做组定义了Navigation Timing规范,该规范定义了PerformanceNavigationTiming接口,提供了更有用和更准确的页面加载相关的时间数据。包括从网络获取文档到在用户代理(User Agent)中加载文档相关的全部时间信息。

对于上面那个例子,使用Navigation Timing能够很轻松的用下面的代码作到而且更精准:

<html>
  <head>
    <script type="text/javascript"> function onLoad() { const [entry] = performance.getEntriesByType('navigation') console.log('page loading time: ' + entry.duration) } </script>
  </head>
  <body onload="onLoad()">
    <!- Main page body goes from here. -->
  </body>
</html>
复制代码

上面代码经过performance.getEntriesByType方法获得一个列表,这个列表就是咱们前面2.1节介绍的PerformanceEntryList,并过滤出全部类型为navigationPerformanceEntry对象。

类型为navigationPerformanceEntry对象在术语上被称为PerformanceNavigationTiming对象。

PerformanceNavigationTiming对象扩展了PerformanceEntry对象,经过该对象提供的duration属性能够获得页面加载所消耗的所有时间。

PerformanceNavigationTiming 接口所提供的全部时间值都是相对于 Time Origin 的。因此 startTime 属性的值永远是0

经过该PerformanceNavigationTiming对象能够得到页面加载相关的很是精准的时间信息:

  • name:当前页面的地址
  • entryType:“navigation”
  • startTime:0
  • duration:页面加载所消耗的所有时间(loadEventEnd的时间减去startTime的时间)

PerformanceNavigationTiming对象扩展了PerformanceResourceTiming对象,因此PerformanceNavigationTiming对象具备PerformanceResourceTiming对象的全部属性,可是某些属性的返回值略有不一样:

  • initiatorType:“navigation”
  • workerStart:页面开始注册Service Worker的时间

同时 NavigationTiming 新增了一些属性,下面列表给出了新增的属性:

新增的属性 描述
unloadEventStart 若是被请求的页面来自于前一个同源(同源策略)的文档,那么该属性存储的值是浏览器开始卸载前一个文档的时刻。不然的话(前一个文档非同源或者没有前一个文档)为0
unloadEventEnd 前一个文档卸载完成的时刻。若是前一个文档不存在则为0
domInteractive 指文档完成解析的时间,包括在“传统模式”下被阻塞的经过script标签加载的内容(使用defer或者async属性异步加载的状况除外)
domContentLoadedEventStart DOMContentLoaded事件触发前的时间
domContentLoadedEventEnd DOMContentLoaded事件触发后的时间
domComplete 用户代理将将document.readyState设置为complete的时间
loadEventStart load事件被触发前的时间,若是load事件还没触发则返回0
loadEventEnd load事件完成后的时间,若是load事件还没触发则返回0
redirectCount 页面被重定向的次数
type 页面被载入的方式

type属性的四种取值状况:

  1. navigate:用户经过点击连接或者在浏览器地址栏输入URL的方式进入页面
  2. reload:经过从新加载操做或location.reload()方法
  3. back_forward:经过浏览器history的前进或后退进入页面
  4. prerender:经过prerender的方式启动一个页面

4.1 过程模型

图2给出了PerformanceNavigationTiming对象的时序属性。当页面从不一样来源获取时,括号中的属性可能不可用。

图2 PerformanceNavigationTiming 过程模型

图2 PerformanceNavigationTiming 过程模型

从图2能够看出完整的页面加载时间信息包含不少信息。前端渲染相关的时间只占用不多的一部分(图2最后面两个蓝色部分processingonLoad)。这也是为何咱们在一开始说使用JS埋点的方式去测量页面加载时间很天真。

5. 使用高精度时间戳来度量Web应用的性能

Web开发者须要一种可以**“评估与理解”**其Web应用性能的能力。虽然JavaScript提供了测量应用性能的能力(使用Date.now()方法获取当前时间戳),但这个时间戳的精度在不一样的用户代理下存在必定的差别,而且时间会受到系统时钟误差与调整的影响。

W3C性能工做组定义了User Timing规范,提供了高精度且单调递增的时间戳,使开发者能够更好地测量其应用的性能。

下面代码显示了开发者应该如何使用User Timing规范定义的API来得到执行代码相关的时间信息。

async function run() {
  performance.mark("startTask1")
  await doTask1() // Some developer code
  performance.mark("endTask1")

  performance.mark("startTask2")
  await doTask2() // Some developer code
  performance.mark("endTask2")

  // Log them out
  const entries = performance.getEntriesByType("mark")
  for (const entry of entries) {
    console.table(entry.toJSON())
  }
}
run()
复制代码

5.1 关于User Timing

User Timing规范扩展了Performance对象,并在Performance对象上新增了四个方法:

  • mark
  • clearMarks
  • measure
  • clearMeasures

5.1.1 mark方法

mark方法接收一个字符串类型的参数(mark名称),用于建立并存储一个PerformanceMark对象。更通俗的说,mark方法用于记录一个与名称相关时间戳。

PerformanceMark对象存储了4个属性:

  • name:mark方法的参数
  • entryType:“mark”
  • startTime:mark方法被调用的时间(performance.now()方法的返回值)
  • duration:0

下面代码展现了如何使用mark方法:

performance.mark('testName')
复制代码

当使用mark方法存储了一个PerformanceMark对象后,能够经过前面介绍的getEntriesByName方法获得一个列表,列表中包含一个PerformanceMark对象。代码以下:

const [entry] = performance.getEntriesByName('testName')
console.log(entry) // {"name": "testName", "entryType": "mark", "startTime": 4396.399999997811, "duration": 0}
复制代码

5.1.2 clearMarks方法

顾名思义,clearMarks方法的做用是删除全部给定名称的时间戳数据(PerformanceMark对象)。

clearMarks方法接收一个字符串类型的参数(mark名称),例如:

performance.mark('testName')
performance.clearMarks('testName')
performance.getEntriesByName('testName') // []
复制代码

上面代码使用mark方法记录了一个名为testName的时间戳信息(存储了PerformanceMark对象),随后使用clearMarks方法清除名为testName的时间戳信息,最后尝试获取名为testName的时间戳信息时获得的是一个空列表。

5.1.3 measure方法

虽然mark方法能够记录时间戳信息,可是得到两个mark之间的持续时间仍是有点麻烦,咱们须要先获取两个PerformanceMark对象,而后再执行减法。

针对这个问题User Timing规范提供了measure方法,该方法的做用是使用一个名字将两个PerformanceMark对象之间所持续的时间存储起来。

measure方法的参数:

  1. measureName:名称
  2. startMark:mark名称
  3. endMark:mark名称

mark方法相同,measure方法会建立一个PerformanceMeasure对象并存储起来。PerformanceMeasure对象存储了4个属性:

  • name:参数中提供的measureName
  • entryType:“measure”
  • startTime:PerformanceMark对象的startTime属性,若是没有提供startMark参数,则为0
  • duration:两个PerformanceMark对象的startTime属性的差值,多是负数。

下面代码展现了如何使用measure方法检测代码执行所持续的时间:

async function run() {
  performance.mark('startTask')
  await doTask1() // Some developer code
  performance.mark('endTask')

  performance.measure('task', 'startTask', 'endTask')
  // Log them out
  const [entry] = performance.getEntriesByName('task')
  console.log(entry.duration)
}
run()
复制代码

5.1.4 clearMeasures方法

clearMarks相似,clearMeasures方法的做用是使用参数中提供的名称来删除PerformanceMeasure对象。

6. 如何知道“用户以为网页慢”(如何检测长任务)?

保证UI的流畅很重要,那么如何检测UI是否流畅呢?

根据RAIL性能模型提供的信息,若是Web应用在100毫秒内的时间能够响应用户输入,则用户会以为应用的交互很流畅。若是响应超过100毫秒用户就会感受到应用有点轻微的延迟。若是超过1秒,用户的注意力将离开他们正在执行的任务。

因为JavaScript是单线程的,因此当一个任务执行时间过长,就会阻塞UI线程与其余任务。对于用户来讲,他一般会看到一个“锁定”的页面,浏览器没法响应用户输入。

这种占用UI线程很长一段时间并阻止其余关键任务执行的任务叫作“长任务”

更具体的解释是:超过50毫秒的事件循环任务都属于长任务

那么如何检测应用是否存在“长任务”呢?

一个已知的方式是使用一个短周期定时器,并检查两次调用之间的时间,若是两次调用之间的时间大于定时器的周期时间,那么颇有可能有一个或多个“长任务”延迟了定时器的执行。

这种方式虽然能够实现需求,但它并不完美。它要不停的轮询去检查长任务,在移动端对手机电池寿命不友好,而且也没有办法知道是谁形成了延迟(例如:本身的代码 vs 第三方的代码)。

W3C性能工做组提供了Long Tasks规范,该规范定义了一个接口,使Web开发者能够监测“长任务”是否存在。

使用案例:

const observer = new PerformanceObserver(function(list) {
  const perfEntries = list.getEntries()
  for (let i = 0; i < perfEntries.length; i++) {
    // 处理长任务通知
    // 上报性能检测数据
    // ...
  }
})
// 注册长任务观察者
observer.observe({entryTypes: ['longtask']})

// 模拟一个长任务
const start = Date.now()
while (Date.now() - start < 1000) {}
复制代码

上面的代码注册了“长任务”观察器,它的功能是每当有超过50毫秒的任务被执行时调用回调函数。

2.3节介绍了PerformanceObserver,因此回调函数中的变量perfEntries保存了一个列表,列表中包含了全部承载了长任务数据的对象。

承载了长任务数据的对象在术语上被称为PerformanceLongTaskTiming

PerformanceLongTaskTiming对象中保存了长任务相关的信息,包括如下属性:

  • name:name属性提供了长任务的来源信息,一般返回“self”但也有一些其余来源信息
    • self:长任务来自自身frame
    • same-origin-ancestor:长任务来自一个同源的祖先frame(注册长任务观察者的页面被iframe到一个同源的其余页面时,咱们叫这个其余页面为父级页面,若是这个父级页面出现了长任务,那么在子页面中的长任务观察者会获得通知,这时候name属性的值为same-origin-ancestor
    • same-origin-descendant:长任务来自一个同源的后代frame(与same-origin-ancestor相反,若是当前页面注册了一个长任务观察者并iframe了一个其余页面,这时候iframe中若是存在长任务,则当前页面的长任务观察者会收到通知,这时候name属性的值为same-origin-descendant
    • same-origin:长任务来自一个同源但没法访问的frame
    • cross-origin-ancestor:长任务来自跨域的祖先frame
    • cross-origin-descendant:长任务来自跨域的后代frame
    • cross-origin-unreachable:长任务来自跨域但没法访问的frame
    • multiple-contexts:长任务涉及多个frame(据我我测试,在当前页面注册的长任务观察者,而且在当前页面触发的长任务,可是这个页面是被iframe到另外一个页面,而后父级页面的console中查看子页面打印的PerformanceLongTaskTiming对象的name属性为multiple-contexts
    • unknown:上面这些状况都不符合
  • entryType:属性必须返回“longtask”
  • startTime:长任务开始的时间,该时间是相对于Time Origin的时间
  • duration:长任务的持续时间
  • attribution:一个数组,但长度永远等于1,里面是一个TaskAttributionTiming对象,该对象有如下属性:
    • name:如今name属性老是返回“script”,但将来name属性将用于标识布局,绘制等信息
    • entryType:老是返回“taskattribution”
    • startTime:老是返回0
    • duration:老是返回0
    • containerType:浏览上下文容器类型,例如:“iframe”,“embed”,“object”
    • containerName:浏览上下文容器名称
    • containerId:浏览上下文容器id
    • containerSrc:浏览上下文容器src

frame指的是浏览上下文,例如iframe

7. 如何收集“首屏渲染”相关性能度量数据?

加载并非一个单一的时刻,它是一种体验,没有任何一种指标能够彻底捕获。事实上在页面加载期间有多个时刻能够影响用户将其视为“快”仍是“慢”。

首次绘制(FP,全称First Paint)是第一个比较关键的时刻,其次是首次内容绘制(FCP,全称First Contentful Paint)。

这两个性能指标之间的主要区别在于“首次绘制”是当浏览器首次开始渲染任何能够在视觉上让屏幕发生变化的时刻。相比之下“首次内容绘制”是当浏览器首次从DOM中渲染内容的时刻,内容能够是文本,图片,SVG,甚至是canvas元素。

速度度量
图3 首屏渲染指标

”首次绘制“(First Paint)不包括默认背景绘制(例如浏览器默认的白色背景),可是包含非默认的背景绘制,与iframe。

”首次内容绘制“(First Contentful Paint)包含文本,图片(包含背景图),非白色canvas与SVG。

父级浏览上下文不该该知道子浏览上下文的绘制事件,反之亦然。这就意味着若是一个浏览上下文只包含一个iframe,那么将只有“首次绘制”,但没有“首次内容绘制”。

能够经过下面代码得到首屏渲染性能指标数据:

performance.getEntriesByType('paint')
复制代码

经过上面这行代码能够获得一个列表。列表中包含一个或两个PerformancePaintTiming对象。这取决于“首次内容绘制”是否存在。如图4所示:

获取首屏渲染指标

图4. 获取首屏渲染指标

从图3能够看到PerformancePaintTiming对象包含四个属性,这四个属性的值为:

  • name:若是是首次绘制则name为“first-paint”,若是是“首次内容绘制”则name为“first-contentful-paint”
  • entryType:“paint”
  • startTime:绘制发生的时间,该时间是相对于time origin
  • duration:0

咱们可使用下面的代码注册一个绘制观察器:

const observer = new PerformanceObserver(function(list) {
    const perfEntries = list.getEntries()
    for (let i = 0; i < perfEntries.length; i++) {
        // 处理数据
        // 上报性能检测数据
        // ...
    }
})

// 注册绘制观察者
observer.observe({entryTypes: ["paint"]})
复制代码

8. 总结

本文详细介绍了在Web应用中采集性能信息所须要的一些方法。其中包括:得到不受时钟误差与系统时钟调整影响的高精度时间的方法、收集“页面资源加载”相关的性能度量数据的方法、收集“网页加载”相关的性能度量数据的方法、使用高精度时间戳在应用程序中埋点的方法、监测用户以为网页“慢”的方法以及采集首屏渲染性能指标的方法。

相关文章
相关标签/搜索