性能监控
平台上面有各个业务的性能指标及其对应场景下的性能标准,一旦遇到性能问题,就能直接判断当前性能数据有没有问题,然后提示问题是出在前端、后端,还是网络层,进而从前端性能体系来系统考虑性能优化。
我们更多希望在什么场景下,遇到了什么性能问题,围绕什么样的性能指标,采取了哪些性能优化手段,最后取得了什么样的结果,而不仅仅是只了解有哪些优化手段。
页面性能直接影响用户的实际体验。研究表明:用户最满意的打开网页时间是2~5s,如果等待时间超过10s,那么多数用户会关闭这个网页。性能是影响用户体验至关重要的因素,也是开发人员重点关注的部分。
开发人员在开发环境下访问页面时,打开速度通常很快,但用户的访问速度却远远不及预期,这是因为开发环境下的硬件设备和网络情况通常远远优于用户的。性能往往是由多方面因素共同决定的,页面打开的速度只是最终呈现的结果。如果开发人员想衡量页面的性能,就必须建立一套能够客观衡量的指标,并基于这些指标完善监控系统。
常用性能标准
- Navigation Timing:提供了文档导航过程中完整的计时信息,即一个文档从发起请求到加载完毕各阶段的性能耗时。
- Performance Timeline:提供了获取各种类型(navigation、resource、paint等)的性能时间线的方法
- Resource Timing:提供文档中资源的计时信息
- Paint Timing:记录在页面加载期间的一些关键时间点
- Long Tasks API:检测长任务的存在,长任务会在很长一段时间内独占UI线程,并组织其他关键任务的执行---例如响应用户输入。
Performance API
浏览器及其底层引擎提供的功能越来越强大,开发人员可以构建更复杂的页面应用。页面的性能变得越来越重要,开发人员迫切希望有一套标准能够用来评估和了解应用程序的性能特征。因此,W3C在2010年8月成立了Web Performance Working Group,其目的是提供可以衡量页面性能的API,这个API就是window.performance。
performance.now()方法
为了测算一个任务的耗时,最容易想到的方式是先分两次调用Date.now()方法,然后做差值来衡量过程中消耗的时间。
const startTime = Date.now();
//做具体的任务doTask()
//最终耗时
const taskCostTime = Date.now() - startTime;
这种方式存在以下两个问题。
- 在部分场景(如游戏、Benchmark等)下的精度不够,只能精确到毫秒(ms)。
- 使用Date.now()方法获取的是时间戳(1970年到现在经过的秒数),它依赖于用户端操作系统的时间。如果用户记录了startTime并修改了本地时间,就会出现任务耗时异常的情况(虽然这种情况并不常见)。
Benchmark(基准测试)是一种测试性能的方式,如通过短时间内运行多次一个函数来测试调用其所耗费的时间。因为函数单次执行耗时一般很少,所以Benchmark对于时间的精度要求很高。为了提供更高精度、更可靠的性能计时,Performance API提供了performance.now()方法。performance.now()方法具有以下特性。
- 精度精确到微秒(μs)。
- 获取的是把页面打开时间点作为基点的相对时间,不依赖操作系统的时间。
使用performance.now()方法和Date.now()方法的差异如图所示。
部分浏览器尚不支持performance.now()方法,在这种情况下可以使用当前时间戳(页面打开的时间戳)进行模拟。
performance.now = function(){
//performance.timing.navigationStart表示页面打开的时间戳,非高精度时间
return Date.now() - performance.timing.navigationStart
}
Navigation Timing API
window.performance会返回一个Performance类型的对象,其中,performance.timing包含了各种与浏览器性能有关的时间数据,提供浏览器各处理阶段的耗时, 浏览器加载页面的过程被区分成了9个阶段,performance.timing将每个阶段的关键节点发生变更时的毫秒时间戳都进行了标记,每个节点的时间戳的含义如下。
字段 | 含义 |
---|---|
navigationStart | 在同一个浏览器上下文中,前一个页面卸载结束时的时间戳。如果没有前一个页面,这个值会和fetchStart相同 |
redirectStart | 第一个HTTP开始重定向时的时间戳。如果没有重定向,或者重定向过程中的某一个域名不同源,则返回值为0 |
redirectEnd | 第一个HTTP重定向结束时的时间戳。如果没有重定向,或者重定向过程中的某一个域名不同源,则返回值为0 |
fetchStart | 浏览器准备好使用HTTP请求获取文档时的时间戳,这发生在检查缓存之前 |
domainLookupStart | 域名查询开始时的时间戳。如果使用了持久连接,或者域名查询的信息已经存储到了缓存或者本地资源上,则这个值将和fetchStart相同 |
domainLookupEnd | 域名查询结束时的时间戳。如果使用了持久连接,或者域名查询的信息已经存储到了缓存或者本地资源上,则这个值将和fetchStart相同 |
connectStart | HTTP开始向服务器发送请求时的时间戳。如果使用了持久连接,或者域名查询的信息已经存储到了缓存或者本地资源上,则这个值将和fetchStart相同 |
secureConnectionStart | 浏览器与服务器开始安全连接的握手时的时间戳。如果当前网页不需要建立安全连接,则返回值为0 |
connectEnd | 浏览器与服务器之间建立连接时的时间戳。如果使用了持久连接,或者域名查询的信息已经存储到了缓存或者本地资源上,则这个值将和fetchStart相同。连接建立指所有握手和认证过程全部结束 |
requestStart | HTTP请求读取真实文档开始的时间,包括从本地缓存读取 |
requestEnd | HTTP请求读取真实文档结束的时间,包括从本地缓存读取 |
responseStart | 浏览器从服务器收到(或从本地缓存读取)第一字节时的时间戳。如果传输层在开始请求之后失败并且连接被重新发起,则该属性将被重置为新请求的发起时间 |
responseEnd | 浏览器从服务器收到(或从本地缓存/资源读取)最后一字节时(如果在此之前HTTP连接已经关闭,则返回关闭时)的时间戳 |
unloadEventStart | unload事件发生时的时间戳。如果没有前一个页面,或者前一个页面的域名或重定向过程中的某个域名与当前域名不同源,则返回值为0 |
unloadEventEnd | unload事件结束时的时间戳。如果没有前一个页面,或者前一个页面的域名或重定向过程中的某个域名与当前域名不同源,则返回值为0 |
domLoading | 当前网页DOM结构开始解析时,即document.readyState属性变为loading,相对应的readystatechange事件触发时的时间戳 |
domInteractive | 当前网页DOM结构解析结束,开始加载内嵌资源时,即document.readyState属性变为interactive,相应的readystatechange事件触发时的时间戳 |
domContentLoadedEventStart | 网页domContentLoaded事件发生的时间 |
domContentLoadedEventEnd | 网页domContentLoaded事件脚本执行完毕的时间,domReady的时间 |
domComplete | 当前文档解析完成时,即document.readyState变为complete时,相对应的readystatechange事件触发时的时间戳 |
loadEventStart | load事件被发送时的时间戳。如果这个事件还未被发送,则返回值为0 |
loadEventEnd | :load事件结束,即加载事件完成时的时间戳。如果load事件还未被发送,或者尚未完成,则返回值为0 |
通过对以上指标取差值,可以得到每个阶段耗费的时间,从而建立更加直观的指标,示例如下。
字段 | 描述 | 计算方式 | 意义 |
---|---|---|---|
unload | 前一个页面卸载耗时 | unloadEventEnd – unloadEventStart | - |
redirect | 重定向耗时 | redirectEnd – redirectStart | 重定向的时间 |
appCache | 缓存耗时 | domainLookupStart – fetchStart | 读取缓存的时间 |
dns | DNS 解析耗时 | domainLookupEnd – domainLookupStart | 可观察域名解析服务是否正常 |
tcp | TCP 连接耗时 | connectEnd – connectStart | 建立连接的耗时 |
ssl | SSL 安全连接耗时 | connectEnd – secureConnectionStart | 反映数据安全连接建立耗时 |
ttfb | Time to First Byte(TTFB)网络请求耗时 | responseStart – requestStart | TTFB是发出页面请求到接收到应答数据第一个字节所花费的毫秒数 |
response | 响应数据传输耗时 | responseEnd – responseStart | 观察网络是否正常 |
dom | DOM解析耗时 | domInteractive – responseEnd | 观察DOM结构是否合理,是否有JS阻塞页面解析 |
dcl | DOMContentLoaded 事件耗时 | domContentLoadedEventEnd – domContentLoadedEventStart | 当 HTML 文档被完全加载和解析完成之后,DOMContentLoaded 事件被触发,无需等待样式表、图像和子框架的完成加载 |
resources | 资源加载耗时 | domComplete – domContentLoadedEventEnd | 可观察文档流是否过大 |
domReady | DOM阶段渲染耗时 | domContentLoadedEventEnd – fetchStart | DOM树和页面资源加载完成时间,会触发domContentLoaded 事件 |
首次渲染耗时 | 首次渲染耗时 | responseEnd-fetchStart | 加载文档到看到第一帧非空图像的时间,也叫白屏时间 |
首次可交互时间 | 首次可交互时间 | domInteractive-fetchStart | DOM树解析完成时间,此时document.readyState为interactive |
首包时间耗时 | 首包时间 | responseStart-domainLookupStart | DNS解析到响应返回给浏览器第一个字节的时间 |
页面完全加载时间 | 页面完全加载时间 | loadEventStart - fetchStart | - |
onLoad | onLoad事件耗时 | loadEventEnd – loadEventStart | - |
Navigation Timing API存在以下几个问题。
- 没有使用高精度时间,其主要是精度较低或容易因为客户端系统时间改变而受到干扰。
- 可拓展性较差,刚开始时Performance API中只包含一些关键时间点,所以用一个对象的属性值就能完成描述。如果需要的信息越来越多,以及部分性能信息只需要在关注的时候才获取,那么在这个对象上不停地添加新的字段显然会使其过于臃肿。
所以,有了Performance Entry API,Navigation Timing API就升级为Navigation Timing API Level 2,并作为Performance Entry API的一部分。而原来的Navigation Timing API作为Navigation Timing API Level 1已经被废弃。
Peformance Entry API
Navigation Timing提供了页面加载等性能相关的信息,但和性能相关的信息远不止这些,为了提供更多详细的信息,如某个资源文件的加载耗时、传输容量等,浏览器提供了Peformance Entry API。
performance.getEntries()
Peformance Entry API的主要API是performance.getEntries(),可以获取页面中所有与资源加载相关的性能信息,包括针对这个资源具体的加载时间线、实际解码后的体积和传输容量等。 Peformance Entries不仅包含资源加载的性能信息,还包含Element Timing、First Paint和First Contentful Paint等信息。事实上Performance API的新提案基本上都构建在Peformance Entry API的基础上。
performance.getEntriesByType()
可以使用performance.getEntriesByType()获取某个特定类型的Performance Entry列表。performance.getEntriesByType()返回的Performance Entry的类型有以下几种。
- navigation
- resource
- mark
- measure
- paint
- frame。
performance.getEntriesByName()
可以使用performance.getEntriesByName()根据name获取Performance Entry列表,在某些已经知道确切name的场景下比较方便。例如,可以通过如下方式获取first-paint的Performance Entry。
performance.getEntriesByName('first-paint')[0]
需要注意的是,Performance Entry的name是允许重复的,所以通过这个API得到的仍然是一个列表。
PerformanceObserver
使用performance.getEntries等方法获取性能信息的前提是对应的Performance Entry已经产生,如对应的资源加载已经完成、页面已经完成渲染等,在加载资源前是获取不到任何其加载的性能信息的。
为了能够判断相应的时机,可以用浏览器提供的PerformanceObserver来监听Performance Entry相关的事件。
const observer = new PerformanceObserver((list,observer)=>{
//处理resource事件
const entries = list.getEntries();
entries.forEach(entry=>{
//处理Performance Entry
})
})
observer.observe({entryTypes:['resource']})
这样,当有对应的Performance Entry加入列表时,会通知对应的Observer来处理。
Resource Timing
Resource Timing是基于Performance Entry API构建的针对资源相关信息的API,可以从中获得加载某个具体资源的相关信息。
加载时间
使用Resource Timing可以获取每个资源详细的加载时间,如Redirect、DNS、TCP等。
容量信息
资源的传输容量也和性能息息相关,所以Resource Timing还提供了一些和传输容量相关的属性,具体如下。
- decodedBodySize:解压缩后的体积。
- encodedBodySize:压缩后的体积。
- transferSize:传输的容量,若该值为0则表示从缓存加载,故传输容量为0。
Navigation Timing Level 2
Navigation Timing Level 2是Navigation Timing API的新版规范,主要基于Performance Entry API,并且支持高精度时间。后续新的一些有关Navigation Timing的规范都会在这个规范的基础上进行演进。需要在load事件之后获取,否则有些内容为0
正如上面所说,Navigation Timing Level 2这样的新版规范同样构建在Resource Timing 的基础上,可以通过如下方式获取和页面导航相关的性能信息。
performance.getEntriesByType('navigation')
Navigation Timing Level 2中没有navigationStart,取而代之的是startTime(其实就是0),而其他属性不再以navigationStart为偏移值,而是以startTime为偏移值。
Navigation Timing Level 2的兼容性可能还存在问题,建议在前端使用此API采集性能数据时做好兼容性处理,在不支持的情况下降级到Navigation Timing Level 1。
Paint Timing
Paint Timing提供和页面绘制相关的性能信息,其Performance Entry的类型为paint。FP、FCP等值就可以使用performance.getEntriesByType('paint')来获取。
User Timing
上面介绍的大多是浏览器本身提供的性能信息,但在实际开发中,对于很多时间点或时间段,浏览器并不理解。例如,当引入一个比较大的组件,并且打算对引入这个组件的耗时进行度量时,就需要使用一种方法告知浏览器这段耗时的实际意义。
时间点
performance.mark()是用于标记某个时间点的方法,如标记开始加载和加载完成的时间点。
//标记开始加载
performance.mark('load-component-start')
import('component').then(comp=>{
//标记加载完成
performance.mark('load-component-end')
})
标记后生成对应的name,以及entryType为标记的Performance Entry,可以采用如下方式获取所有已经标记的时间点。
performance.getEntriesByType('mark')
时间段
在更多的情况下,需要度量的是一段时间的时长,使用performance.mark()标记了若干时间点后,就可以使用performance.measure()度量两个时间点之间的差值。同样,这次度量会产生一个对应的Performance Entry。
//标记开始加载
performance.mark('load-component-start')
import('component').then(comp=>{
//标记加载完成
performance.mark('load-component-end')
//度量加载耗时
performance.measure('customTiming','load-component-start','load-component-end')
})
这样就会得到一个类型为measure的Performance Entry。另外,在DevTools的Performance面板中,可以看到度量的时间段。
Long Tasks API
流畅度(或者说FPS)降低的根本原因是UI线程被阻塞,而这种阻塞是由一些长时间未能完成的长任务导致的,如长时间的JavaScript任务执行或代价高昂的浏览器重绘、回流等。而使用Long Tasks API可以定位这些阻塞UI线程的长任务。
const observer = new PerformanceObserver(function(list){
const perfEntries = list.getEntries()
for(let i = 0;i<perfEntries.length;i++){
//处理PerformanceLongTaskTiming对象
}
})
observer.observe({entryTypes:["longtask"],buffered:true})
PerformanceLongTaskTiming对象中包含的长任务的基本信息如下。
- startTime:长任务开始的时间(从navigationStart开始计算,单位是毫秒)。
- duration:长任务占用的时间,也是按毫秒计算的。
- name:目前总是self,按照标准定义未来还存在其他值。
- attribution[0].name:目前只有script,按照标准未来可能存在layout等值。
目前,Long Tasks API在大多数浏览器中(截至Chrome 93)还没有得到完整的实现,只能通过Long Tasks API得到JavaScript执行造成的长任务(一般大于50ms会被统计),但在大多数场景下这已经足够实用。页面的流畅度并不总是一个需要开发人员关注的指标,大多数场景页面的复杂度还不至于对流畅度造成大的影响。但是,当流畅度出现问题时,或者当前场景对于用户来说是一个高频交互的页面,就可以通过一些指标来度量页面流畅度的整体状况。例如,对于一个无限滚动加载的长列表页面,随着用户的使用流畅度可能会越来越差,通过指标来度量用户在体验方面的变化有助于发现和解决这些问题。
以用户为中心的性能指标
传统的性能指标专注于容易衡量的技术细节,但是它们很难反应出用户所真正关心的是什么。如果你仅仅是把加载速度优化的更快,你很快就会发现网站的用户体验依然很差。这就是创建用户为中心的性能指标的原因,它们专注于用户视角下的浏览体验。
核心Web指标的构成会随着时间的推移而发展。2020年的指标构成侧重于用户体验方面的加载性能、交互性和视觉稳定性,核心性能指标(及各指标相应的阈值)。
用户对页面是否快的看法,会受到加载体验中不同时刻的影响。以下指标视图从四个不同的角度捕捉这一点:
用户体验 | 指标 |
---|---|
发生了吗? | FP(First Paint),FCP(First Contentful Paint) |
内容有用吗? | FMP(First Meaningful Paint),SI(Speed Index) |
内容可用吗? | TTI(Time to Interactive) |
令人愉悦吗? | FID(First Input Delay) |
以用户为中心的性能指标衡量页面显示有用内容的速度,用户是否可以与之交互,以及这些交互是否流畅且无延迟。
FP(First Paint):首次渲染的时间点。FP时间点之前,用户看到的都是没有任何内容的白色屏幕。
FCP(First Contentful Paint):首次有内容渲染的时间点。在用户访问Web网页的过程中,FCP时间点之前,用户看到的都是没有任何实际内容的屏幕。FCP反应当前Web页面的网络加载性能情况、页面DOM结构复杂度情况、inline script的执行效率的情况。当所有的阶段性能做的非常好的情况下,首次出现内容的时间就会越短,用户等待的时间就会越短,流失的概率就会降低。
FMP(First Meaningful Paint):首次绘制有意义内容的时间点。当整体页面的布局和文字内容全部渲染完成后,可人为是完成了首次有意义内容的绘制。FMP通常被认为是用户获取到了页面主要信息的时刻,也就是说此时用户的需求是得到了满足的,所以产品通常也会关注FMP指标。
前端业界现在比较认可的一个计算FMP的方式就是页面在加载和渲染过程中最大布局变动之后的那个绘制时间即为当前页面的FMP。
FMP代码实现原理:通过MutationObserver监听每一次页面整体的DOM变化,触发MutationObserver的回调。在回调计算出当前DOM树的分数,分数变化最剧烈的时刻,即为FMP的时间点。
理论依据:认为DOM结构变化的时间点与之对应渲染的时间点近似相同。所以FMP的时间点为DOM结构变化最剧烈的时间点。DOM结构变化的时间点可以利用MutationObserver API来获得。
SI(Speed Index):衡量页面可视区域加载速度,帮助检测页面的加载体验差异。
TTI(Time to Interactive):测量页面从开始加载到主要子资源完成渲染,并能够快速、可靠的响应用户输入所需的时间。TTI反映页面可用性的重要指标,TTI值越小,代表用户可以更早的操作页面,用户体验就更好。
FID(First Input Delay):测量从用户第一次与页面交互(比如当他们单击链接、点击按钮等等)知道浏览器对交互作出响应,实际能够开始处理事件,处理程序所经过的时间。
通常情况下,Input Delay是因为浏览器主线程在忙于执行其他操作,无暇处理用户的交互操作。
- FID反映用户对页面交互性和响应性的第一印象,良好的第一印象有助于用户建立对整个应用的良好印象。
- 页面加载阶段,资源的处理任务最终,也最容易产生输入延迟。因此关注FID指标对于提升页面的可交互性有很大收益。
- FID和页面加载完成后的Input Delay具有不同的解决方案。针对FID,我们一般建议通过Code Splitting等方式减少页面加载阶段JS的加载、解析和执行事件。而页面加载完成后的Input Delay,通常是由于开发人员代码编写不当、引起JS执行时间过长而产生的。
业界持续研究和开发用来描述良好用户体验的关键指标,随着对于用户体验的理解的深入和测量能力的增强,指标也同样需要随之进化。
这个过程的结果是三个全新的性能指标,它们填补了用户体验故事中的空白。分别是:
- LCP(Largest Contentful Paint)
- TBT(Total Blocking Time)
- CLS(Cumulative Layout Shift)
LCP(Largest Contentful Paint):最大的内容在可视区域内变得可见的时间点。为什么需要LCP?
指标 | 定义 | 存在的问题 |
---|---|---|
FCP | 首次内容绘制时间 | 通常与用户无关 |
FMP | 首次绘制有意义内容的时间点 | 非标准化并且难以在浏览器之间统一实现约20%的情况下不准确 |
SI | 跟踪在视口中加载内容的视觉进程 | 复杂的指标,难以解释。计算密集,不可用于线上监控 |
LCP则不同:它容易理解,给出与FMP相似的结果,容易计算和上报。
TBT(Total Blocking Time):量化主线程在空闲之前的繁忙程度,有助于理解在加载期间,页面无法响应用户输入的时间有多久。
长任务:如果一个任务在主线程上运行超过50毫秒,那么它就是长任务。超过50ms后的任务耗时,都算做任务的阻塞时间。一个页面的TBT,是从FCP到TTI之间所有长任务的阻塞时间的综合。
CLS(Cumulative Layout Shift):量化了在页面加载期间,视口中元素的移动程度。CLS通过测量偏移度,来帮助你解决这个问题。它引入了用户体验中一个全新的类别---可预测性。CLS分数越低越好,因为这意味着在整个页面交互过程中发生的内容的偏移较少。
单次布局偏移的计算公式:score = 距离系数*影响分数
所以最终以用户为中心的性能指标定义为:
用户体验 | 指标 |
---|---|
发生了吗? | FP(First Paint),FCP(First Contentful Paint) |
内容有用吗? | FMP(First Meaningful Paint),SI(Speed Index),LCP(Largets Contentful Paint) |
内容游泳吗? | Time to Interactive(TTI),TBT(Total Blocking Time) |
令人愉悦吗? | First Input Delay(FID),CLS(Cumulative Layout Shift) |
Google提供了许多性能测量和性能报告工具,但对于刚接触开发的人员而言,大量的工具和指标令人应接不暇。部分开发人员只想评估网站的性能、判断用户体验的好坏,不需要成为性能专家。因此,Google启动了Web Vitals计划,和W3C的Web Performance Working Group协作,致力于打造一组新的标准化API和指标,从而更准确地测量用户的网页性能体验。Web Vitals计划的目的是简化性能评估的手段,帮助开发人员专注于最重要的指标,即核心Web指标。
核心Web指标的构成会随着时间的推移而发展。2020年的指标构成侧重于用户体验方面的加载性能、交互性和视觉稳定性,核心性能指标(及各指标相应的阈值)。
Google提供开源工具库web-vitals,开发人员可以在项目中引入、调用相应方法,获取对应的性能指标数据。
import {getLCP,getFID,getCLS} from 'web-vitals';
getCLS(console.log);
getFID(console.log);
getLCP(console.log);
如何根据性能指标衡量站点满意度
首先思考拿什么数据定义指标阈值,其次定义一个指标的基准线,然后有一个公式,根据不同指标的达标度得到一个总得满意度的评分。
定义指标阈值
没有一个完美的阈值,并且我们有时可能需要从多个合理的候选阈值中进行选择。我们不会想弄清完美的阈值是多少,相反的,我们专注于认清哪一个候选阈值最符合我们的标准。
通常选择样本量的第75个百分位数来设置阈值,基于以下两个标准:
- 确保对页面或网站的大多数访问都达到了目标性能水平。
- 不受到异常值的过渡影响。
关键指标的基准线
Metric Name | Good(ms) | Needs Improvement(ms) | Poor(ms) |
---|---|---|---|
FP(First Paint) | 0-1000 | 1000-2500 | Over 2500 |
FCP(First Contentful Paint) | 0-1800 | 1800 -3000 | Over 3000 |
LCP(Largest Contentful Paint) | 0-2500 | 2500-4000 | Over 4000 |
TTI(Time to Interactive) | 0-3800 | 3800-7300 | Over 7300 |
FID(First Input Delay) | 0-100 | 100-300 | Over 300 |
CLS(Cumulative Layout Shift) | 0-0.1(无单位) | 0.1-0.25(无单位) | Over 0.25(无单位) |
这里为什么没有FMP、TBT和SI呢?经过测试,LCP非常近似于FMP的时间点,FMP渐渐可以通过CLP代替。SI的计算逻辑比较复杂,更常用在lighthouse中,而非线上监控。虽然TBT可以在线上进行测量,但不建议这样做,因为用户交互会影响网页的TBT,从而导致报告中出现大量差异,线上监控推荐使用FID。
站点性能满意度计算
调整性能指标的计算权重,页面性能评分=各指标性能评分与其权重面积之和
白屏
白屏指在当前用户浏览的页面中,界面的可见区域内不存在任何可浏览的内容。导致白屏的原因大致可以分为JavaScript执行错误和请求未返回两类。
JavaScript执行错误导致的白屏一般伴随着功能流程的阻断出现,并且很难通过等待或者页面刷新等方法修复。这类问题出现的原因通常是前端逻辑错误,或是后端接口的脏数据导致的前端数据解析逻辑错误,最终导致运行异常的JavaScript代码触发了页面崩溃,上述问题需要开发人员介入才能修复。例如,如果React中的组件发生了异常,并且外部没有使用componentDidCatch或者getDerivedStateFromError捕获错误,那么React组件render挂载的目标节点下的DOM树会被清空,页面就会出现白屏。在这种情况发生后,如果开发人员没有及时处理,用户经过多次尝试依然无法恢复,就会进行投诉。
请求未返回导致的白屏又可以细分为可恢复和不可恢复两类。
可恢复的白屏常见于第一次进入页面时,由于资源加载过慢或者接口请求未返回,所以浏览器无法执行下一步骤。这种白屏通常是网络状况太差或者设备性能太差等原因导致的,一般在浏览器返回后,就能恢复页面渲染,可以通过监控首屏时间来发现。如果生产环境的首屏时间呈异常上升趋势,那么一定是页面白屏时间过长导致的,开发人员应该及时关注并排查近期改动的代码。
不可恢复的白屏非常少见。这种白屏一般是CDN服务器异常、域名劫持等原因导致的,可以通过资源保障来建立防御性措施。
经过以上分析,开发人员需要重点关注的是JavaScript执行错误导致的白屏问题。请求未返回导致的白屏可以通过性能监控和资源保障进行防御,也可以通过实时监控进行处理。
相关文章
- PerformanceObserver.observe方法用于观察传入的参数中指定的性能条目类型的集合。当记录一个指定类型的性能条目时,性能监测对象的回调函数将会被调用
- entryType
- paint-timing
- event-timing
- LCP
- FMP
- time-to-interactive
- MDN上网站性能数据衡量
TIP
可以使用https://gtmetrix.com/来测试网站加载速度。