jieye の 数字花园

Search

Search IconIcon to open search

Web性能

Last updated Aug 19, 2022

# 性能优化涉及到的分类

# 涉及到的功能点

# 资源合并与压缩

# http请求的过程及潜在的性能优化点

# 浏览器的一个请求从发送到返回都经历了什么

动态的加载静态的资源

# 资源的合并与压缩设计到的性能点

# html压缩

HTML代码压缩就是压缩这些在文本文件中有意义,但是在HTML中不显示的字符,包括空格,制表符,换行符等,还有一些其他意义的字符,如HTML注释也可以被压缩

意义

# 如何进行html的压缩

# cssjs压缩

# css的压缩

压缩的方式

# js的压缩与混乱

# js压缩的方式

# 不合并文件可能存在的问题

# 文件合并缺点

# 文件合并对应缺点的处理

# 文件合并对应方法

# CDN减少打包体积

使用 cdn 文件来减少工程到打包体积,也可以按需加载。

在 /public/index.html 中引入需要的js和css文件

img

去掉 package.json 中对于 vue、element-ui 等相关资源的依赖

src/main.js ,去掉 vue、element-ui 等相关资源的 import 和 vue.use 这些语句

img

配置externals。由于使用 Vue Cli 3 默认配置,新建出来的项目没有了 build 目录,首先得在项目根目录下,新建 vue.config.js 文件,里面添加以下代码:

1
2
3
4
5
6
7
8
9
module.exports = {
    configureWebpack:{
        externals:{
            'Vue': 'Vue',
            'element-ui': 'element-ui',
            'clipboard':'VueClipboard'
        }
    }
}

# 去除 SourceMap

由于打包后的文件经过了压缩、合并、混淆、babel编译后的代码不利于定位分析bug。

1
2
3
module.exports = {
  productionSourceMap: false,
}

# gzip 压缩

gzip 压缩效率非常高,通常可以达到 70% 的压缩率,也就是说,如果你的网页有 30K,压缩之后就变成了 9K 左右。

1
2
3
4
5
//npm i -D compression-webpack-plugin
configureWebpack: config => {
  const CompressionPlugin = require('compression-webpack-plugin')
  config.plugins.push(new CompressionPlugin())
}

# 图片相关优化

# 一张JPG的解析过程

jpg有损压缩:虽然损失一些信息,但是肉眼可见影响并不大

# png8/png24/png32之间的区别

1
文件大小`  +   `色彩丰富程度

png32是在png24上支持了透明,针对不同的业务场景选择不同的图片格式很重要

# 不同的格式图片常用的业务场景

# 不同格式图片的特点

# 不同格式图片的使用场景

# 图片压缩的几种情况

# css和js的装载与执行

# HTML页面加载渲染的过程

# 一个网站在浏览器端是如何进行渲染的

# HTML渲染过程中的一些特点

# 懒加载与预加载

# 懒加载

img src被设置之后,webkit解析到之后才去请求这个资源。所以我们希望图片到达可视区域之后,img src才会被设置进来,没有到达可视区域前并不现实真正的src,而是类似一个1px的占位符。

1
场景:电商图片

# 预加载

1
场景:抽奖

# 懒加载原生jszepto.lazyload

1
原理

先将img标签中的src链接设为同一张图片(空白图片),将其真正的图片地址存储再img标签的自定义属性中(比如data-src)。当js监听到该图片元素进入可视窗口时,即将自定义属性中的地址存储到src属性中,达到懒加载的效果。

注意问题:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
var viewheight = document.documentElement.clientHeight   //可视区域高度

function lazyload(){
    var eles = document.querySelectorAll('img[data-original][lazyload]')

    Array.prototype.forEach.call(eles,function(item,index){
        var rect;
        if(item.dataset.original === '') return;
        rect = item.getBoundingClientRect(); //返回元素的大小及其相对于视口的

        if(rect.bottom >= 0 && rect.top < viewheight){
            !function(){
                var img = new Image();
                img.src = item.dataset.url;
                img.onload = function(){
                    item.src = img.src
                }
                item.removeAttribute('data-original');
                item.removeAttribute('lazyload');
            }()
        }
    })
}

lazyload()
document.addEventListener('scroll',lazyload)

# 预加载原生jspreloadJS实现

# 预加载实现的几种方式

1
2
3
4
<img src="https://user-gold-cdn.xitu.io/2019/2/21/1690d1b216cbfa18" style="display: none"/>
<img src="https://user-gold-cdn.xitu.io/2019/2/21/1690d1b21b70c8d2" style="display: none"/>
<img src="https://user-gold-cdn.xitu.io/2019/2/21/1690d1b216e17e26" style="display: none"/>
<img src="https://user-gold-cdn.xitu.io/2019/2/21/1690d1b217b3ae59" style="display: none"/>
1
2
var image = new Image();
image.src = "www.pic26.com/dafdafd/safdas.jpg"
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
var xmlhttprequest = new XMLHttpRequest();

xmlhttprequest.onreadystatechange = callback;

xmlhttprequest.onprogress = progressCallback;

xmlhttprequest.open("GET","http:www.xxx.com",true);

xmlhttprequest.send();

function callback(){
    if(xmlhttprequest.readyState == 4 && xmlhttprequest.status == 200){
        var responseText = xmlhttprequest.responseText;
    }else{
        console.log("Request was unsuccessful:" + xmlhttprequest.status);
    }
}

function progressCallback(){
    e = e || event;
    if(e.lengthComputable){
        console.log("Received"+e.loaded+"of"+e.total+"bytes")
    }
}   
1
PreloadJS模块

# 重绘与回流

# css性能让javascript变慢

要把css相关的外部文件引入放进head中,加载css时,整个页面的渲染是阻塞的,同样的执行javascript代码的时候也是阻塞的,例如javascript死循环。

1
2
一个线程   =>  javascript解析
一个线程   =>  UI渲染

这两个线程是互斥的,当UI渲染的时候,javascript的代码被终止。当javascript代码执行,UI线程被冻结。所以css的性能让javascript变慢。

1
频繁触发重绘与回流,会导致UI频繁渲染,最终导致js变慢

# 什么是重绘和回流

# 回流

# 重绘

# 关系

用到chrome 分析 performance

1
回流必将引起重绘,但是重绘不一定会引起回流

# 避免重绘、回流的两种方法

# 触发页面重布局的一些css属性

1
优化点:使用不触发回流的方案替代触发回流的方案

# 只触发重绘不触发回流

# 新建DOM的过程

# 浏览器绘制DOM的过程是这样子的:

# chrome创建图层的条件

1
将频繁重绘回流的DOM元素单独作为一个独立图层,那么这个DOM元素的重绘和回流的影响只会在这个图层中

总结:对布局属性进行动画,浏览器需要为每一帧进行重绘并上传到 GPU 中对合成属性进行动画,浏览器会为元素创建一个独立的复合层,当元素内容没有发生改变,该层就不会被重绘,浏览器会通过重新复合来创建动画帧

1
gif图

# 总结

# 实战优化点总结

https://lavas.baidu.com/guide/v1/foundation/lavas-start)

# 缓存

缓存算是很重要了,面试经常问到,故整理。

总体感知一下它的匹配流程,如下:

  1. 浏览器发送请求前,根据请求头的expires和cache-control判断是否命中(包括是否过期)强缓存策略,如果命中,直接从缓存获取资源,并不会发送请求。如果没有命中,则进入下一步。
  2. 没有命中强缓存规则,浏览器会发送请求,根据请求头的last-modified和etag判断是否命中协商缓存,如果命中,直接从缓存获取资源。如果没有命中,则进入下一步。
  3. 如果前两步都没有命中,则直接从服务端获取资源。

img

# 强缓存

强缓存:不会向服务器发送请求,直接从缓存中读取资源。

# 强缓存原理

强制缓存就是向浏览器缓存查找该请求结果,并根据该结果的缓存规则来决定是否使用该缓存结果的过程,强制缓存的情况主要有三种(暂不分析协商缓存过程),如下:

img

img

img

那么强制缓存的缓存规则是什么? 当浏览器向服务器发起请求时,服务器会将缓存规则放入HTTP响应报文的HTTP头中和请求结果一起返回给浏览器,控制强制缓存的字段分别是ExpiresCache-Control,其中Cache-Control优先级比Expires高。

# Expires

缓存过期时间,用来指定资源到期的时间,是服务器端的具体的时间点。也就是说,Expires=max-age + 请求时间,需要和Last-modified结合使用。Expires是Web服务器响应消息头字段,在响应http请求时告诉浏览器在过期时间前浏览器可以直接从浏览器缓存取数据,而无需再次请求。

Expires 是 HTTP/1 的产物,受限于本地时间,如果修改了本地时间,可能会造成缓存失效。

# Cache-Control

在HTTP/1.1中,Cache-Control是最重要的规则,主要用于控制网页缓存,主要取值为: - public:所有内容都将被缓存(客户端和代理服务器都可缓存) - private:所有内容只有客户端可以缓存,Cache-Control的默认取值 - no-cache:客户端缓存内容,但是是否使用缓存则需要经过协商缓存来验证决定 - no-store:所有内容都不会被缓存,即不使用强制缓存,也不使用协商缓存 - max-age=xxx (xxx is numeric):缓存内容将在xxx秒后失效

需要注意的是,no-cache这个名字有一点误导。设置了no-cache之后,并不是说浏览器就不再缓存数据,只是浏览器在使用缓存数据时,需要先确认一下数据是否还跟服务器保持一致,也就是协商缓存。而no-store才表示不会被缓存,即不使用强制缓存,也不使用协商缓存

# nginx设置

强缓存需要服务端设置expirescache-controlnginx代码参考,设置了一年的缓存时间:

1
2
3
4
5
6
7
8
9
location ~ .*\.(ico|svg|ttf|eot|woff)(.*) {
  proxy_cache               pnc;
  proxy_cache_valid         200 304 1y;
  proxy_cache_valid         any 1m;
  proxy_cache_lock          on;
  proxy_cache_lock_timeout  5s;
  proxy_cache_use_stale     updating error timeout invalid_header http_500 http_502;
  expires                   1y;
}

浏览器的缓存存放在哪里,如何在浏览器中判断强制缓存是否生效?这就是下面我们要讲到的from disk cachefrom memory cache

# from disk cache和from memory cache

细心地同学在开发的时候应该注意到了Chrome的网络请求的Size会出现三种情况from disk cache(磁盘缓存)from memory cache(内存缓存)、以及资源大小数值。

img

浏览器读取缓存的顺序为memory –> disk。 以访问https://github.com/xiangxingchen/blog为例 我们第一次访问时https://github.com/xiangxingchen/blog

img

关闭标签页,再此打开https://github.com/xiangxingchen/blog

img

F5刷新时

img

简单的对比一下

img

# 协商缓存

协商缓存就是强制缓存失效后,浏览器携带缓存标识向服务器发起请求,由服务器根据缓存标识决定是否使用缓存的过程,主要有以下两种情况:

img

img

# Last-Modified和If-Modified-Since

  1. 浏览器首先发送一个请求,让服务端在response header中返回请求的资源上次更新时间,就是last-modified,浏览器会缓存下这个时间。
  2. 然后浏览器再下次请求中,request header中带上if-modified-since:[保存的last-modified的值]。根据浏览器发送的修改时间和服务端的修改时间进行比对,一致的话代表资源没有改变,服务端返回正文为空的响应,让浏览器中缓存中读取资源,这就大大减小了请求的消耗。

由于last-modified依赖的是保存的绝对时间,还是会出现误差的情况:

  1. 保存的时间是以秒为单位的,1秒内多次修改是无法捕捉到的;
  2. 各机器读取到的时间不一致,就有出现误差的可能性。为了改善这个问题,提出了使用etag。

# ETag和If-None-Match

etaghttp协议提供的若干机制中的一种Web缓存验证机制,并且允许客户端进行缓存协商。生成etag常用的方法包括对资源内容使用抗碰撞散列函数,使用最近修改的时间戳的哈希值,甚至只是一个版本号。 和last-modified一样. - 浏览器会先发送一个请求得到etag的值,然后再下一次请求在request header中带上if-none-match:[保存的etag的值]。 - 通过发送的etag的值和服务端重新生成的etag的值进行比对,如果一致代表资源没有改变,服务端返回正文为空的响应,告诉浏览器从缓存中读取资源。

etag能够解决last-modified的一些缺点,但是etag每次服务端生成都需要进行读写操作,而last-modified只需要读取操作,从这方面来看,etag的消耗是更大的。

二者对比 - 精确度上:Etag要优于Last-Modified。 - 优先级上:服务器校验优先考虑Etag。 - 性能上:Etag要逊于Last-Modified

# 用户行为对浏览器缓存的影响

  1. 打开网页,地址栏输入地址: 查找 disk cache 中是否有匹配。如有则使用;如没有则发送网络请求。
  2. 普通刷新 (F5):因为 TAB 并没有关闭,因此 memory cache 是可用的,会被优先使用(如果匹配的话)。其次才是 disk cache
  3. 强制刷新 (Ctrl + F5):浏览器不使用缓存,因此发送的请求头部均带有 Cache-control:no-cache(为了兼容,还带了 Pragma:no-cache),服务器直接返回 200 和最新内容。

# 总结

img

期望大规模数据能自动化缓存,而不是手动进行缓存,需要浏览器端和服务器端协商一种缓存机制

  • Cache-Control所控制的缓存策略
  • last-modified 和 etage以及整个服务端浏览器端的缓存流程
  • 基于node实践以上缓存方式

# httpheader

# 可缓存性

# 到期

# 重新验证和重新加载

重新验证

重新加载

# Expires

# Last-modified和If-Modified-since

last-modified有什么缺点?

# Etag和 If-none-match

好处:

# Service Worker

ServiceWorker 是运行在浏览器后台进程里的一段 JS,它可以做许多事情,比如拦截客户端的请求、向客户端发送消息、向服务器发起请求等等,其中最重要的作用之一就是离线资源缓存。

ServiceWorker 拥有对缓存流程丰富灵活的控制能力,当页面请求到 ServiceWorker时,ServiceWorker 同时请求缓存和网络,把缓存的内容直接给用户,而后覆盖缓存。

img

注意:需要HTTPS才可以使用 ServiceWorker

# 缓存流程图

# 服务端性能优化

1
服务端用的node.js因为和前端用的同一种语言,可以利用服务端运算能力来进行相关的运算而减少前端的运算

# vue渲染面临的问题

1
2
3
    先加载vue.js
=>  执行vue.js代码
=>  生成html

以前没有前端框架时,

# 多层次的优化方案

# Web性能监控

google 开发者提出了一种 RAIL 模型来衡量应用性能,即:Response、Animation、Idle、Load,分别代表着 web 应用生命周期的四个不同方面。并指出最好的性能指标是:100ms 内响应用户输入;动画或者滚动需在 10ms 内产生下一帧;最大化空闲时间;页面加载时长不超过 5 秒。

0

我们可转化为三个方面来看:响应速度、页面稳定性、外部服务调用

# 1. 页面访问速度:白屏、首屏时间、可交互时间

我们来看看 google 开发者针对用户体验,提出的几个性能指标

1

这几个指标其实都是根据用户体验,提炼出对应的性能指标

img

# 1)first paint (FP) and first contentful paint (FCP)

首次渲染、首次有内容的渲染
这两个指标浏览器已经标准化了,从 performance 的 The Paint Timing API 可以获取到,一般来说两个时间相同,但也有情况下两者不同。

img

# 2)First meaningful paint and hero element timing

首次有意义的渲染、页面关键元素
我们假设当一个网页的 DOM 结构发生剧烈的变化的时候,就是这个网页主要内容出现的时候,那么在这样的一个时间点上,就是首次有意义的渲染。这个指标浏览器还没有规范,毕竟很难统一一个标准来定义网站的主体内容。
google lighthouse 定义的 first meaningful paint:
https://docs.google.com/document/d/1BR94tJdZLsin5poeet0XoTW60M0SjvOJQttKT-JK8HI/view

# 3)Time to interactive

可交互时间

# 4)长任务

浏览器是单线程的,如果长任务过多,那必然会影响着用户响应时长。好的应用需要最大化空闲时间,以保证能最快响应用户的输入。

img

# 2. 页面稳定性:页面出错情况

资源加载错误
JS 执行报错

# 3. 外部服务调用

CGI 耗时
CGI 成功率
CDN 资源耗时

# 监控的分类?

web 性能监控可分为两类,一类是合成监控(Synthetic Monitoring,SYN),另一类是真实用户监控(Real User Monitoring,RUM)

# 1.合成监控

合成监控是采用 web 浏览器模拟器来加载网页,通过模拟终端用户可能的操作来采集对应的性能指标,最后输出一个网站性能报告。例如:Lighthouse、PageSpeed、WebPageTest、Pingdom、PhantomJS 等。

# 1. Lighthouse

Lighthouse 是 google 一个开源的自动化工具,运行 Lighthouse 的方式有两种:一种是作为 Chrome 扩展程序运行;另一种作为命令行工具运行。 Chrome 扩展程序提供了一个对用户更友好的界面,方便读取报告。通过命令行工具可以将 Lighthouse 集成到持续集成系统。
展示了白屏、首屏、可交互时间等性能指标和 SEO、PWA 等。
腾讯文档移动端官网首页测速结果:

img

# 2. PageSpeed

https://developers.google.com/speed/pagespeed/insights/
不仅展示了一些主要的性能指标数据,还给出了部分性能优化建议。
腾讯文档移动端首页测速结果和性能优化建议:

img

# 3. WebPageTest

WebPageTest
给出性能测速结果和资源加载的瀑布图。

img

# 4. Pingdom

https://www.pingdom.com/
注意:Pingdom 不仅提供合成监控,也提供真实用户监控。

img
合成监控方式的优缺点:
优点:
无侵入性。
简单快捷。
缺点:
不是真实的用户访问情况,只是模拟的。
没法考虑到登录的情况,对于需要登录的页面就无法监控到。

# 2.真实用户监控

真实用户监控是一种被动监控技术,是一种应用服务,被监控的 web 应用通过 sdk 等方式接入该服务,将真实的用户访问、交互等性能指标数据收集上报、通过数据清洗加工后形成性能分析报表。例如 FrontJs、oneapm、Datadog 等。

img

# 1. oneapm

https://www.oneapm.com/bi/feature.html
功能包括:大盘数据、特征统计、慢加载追踪、访问页面、脚本错误、AJAX、组合分析、报表、告警等。

img

# 2. Datadog

https://www.datadoghq.com/rum/

img

# 3. FrontJs

https://www.frontjs.com/
功能包括:访问性能、异常监控、报表、趋势等。

img
这种监控方式的优缺点:
优点:
是真实用户访问情况。
可以观察历史性能趋势。
有一些额外的功能:报表推送、监控告警等等。
缺点:
有侵入性,会一定程度上响应 web 性能。

# performance 分析

在讲如何监控之前,先来看看浏览器提供的 performance api,这也是性能监控数据的主要来源。
performance 提供高精度的时间戳,精度可达纳秒级别,且不会随操作系统时间设置的影响。
目前市场上的支持情况:主流浏览器都支持,大可放心使用。

img

# 基本属性

img

performance.navigation: 页面是加载还是刷新、发生了多少次重定向

img

performance.timing: 页面加载的各阶段时长

img

各阶段的含义:

img

performance.memory: 基本内存使用情况,Chrome 添加的一个非标准扩展

img

performance.timeorigin: 性能测量开始时的时间的高精度时间戳

img

# 基本方法

performance.getEntries()
通过这个方法可以获取到所有的 performance 实体对象,通过 getEntriesByName 和 getEntriesByType 方法可对所有的 performance 实体对象 进行过滤,返回特定类型的实体。
mark 方法 和 measure 方法的结合可打点计时,获取某个函数执行耗时等。

img

performance.getEntriesByName()
performance.getEntriesByType()
performance.mark()
performance.clearMarks()
performance.measure()
performance.clearMeasures()
performance.now()

# 提供的 API

performance 也提供了多种 API,不同的 API 之间可能会有重叠的部分。

# 1. PerformanceObserver API

用于检测性能的事件,这个 API 利用了观察者模式。
获取资源信息

img

监测 TTI

img

监测 长任务

img

# 2. Navigation Timing API

https://www.w3.org/TR/navigation-timing-2/
performance.getEntriesByType(“navigation”);

img

img

不同阶段之间是连续的吗? —— 不连续
每个阶段都一定会发生吗?—— 不一定

重定向次数:performance.navigation.redirectCount
重定向耗时: redirectEnd - redirectStart
DNS 解析耗时: domainLookupEnd - domainLookupStart
TCP 连接耗时: connectEnd - connectStart
SSL 安全连接耗时: connectEnd - secureConnectionStart
网络请求耗时 (TTFB): responseStart - requestStart
数据传输耗时: responseEnd - responseStart
DOM 解析耗时: domInteractive - responseEnd
资源加载耗时: loadEventStart - domContentLoadedEventEnd
首包时间: responseStart - domainLookupStart
白屏时间: responseEnd - fetchStart
首次可交互时间: domInteractive - fetchStart
DOM Ready 时间: domContentLoadEventEnd - fetchStart
页面完全加载时间: loadEventStart - fetchStart
http 头部大小: transferSize - encodedBodySize

# 3. Resource Timing API

https://w3c.github.io/resource-timing/
performance.getEntriesByType(“resource”);

img

img

123456// 某类资源的加载时间,可测量图片、js、css、XHRresourceListEntries.forEach(resource => { if (resource.initiatorType == ‘img’) { console.info(Time taken to load ${resource.name}: , resource.responseEnd - resource.startTime); }});

这个数据和 chrome 调式工具里 network 的瀑布图数据是一样的。

# 4. paint Timing API

https://w3c.github.io/paint-timing/
首屏渲染时间、首次有内容渲染时间

img

# 5. User Timing API

https://www.w3.org/TR/user-timing-2/#introduction
主要是利用 mark 和 measure 方法去打点计算某个阶段的耗时,例如某个函数的耗时等。

# 6. High Resolution Time API

https://w3c.github.io/hr-time/#dom-performance-timeorigin
主要包括 now() 方法和 timeOrigin 属性。

# 7. Performance Timeline API

https://www.w3.org/TR/performance-timeline-2/#introduction

# 总结

基于 performance 我们可以测量如下几个方面:
mark、measure、navigation、resource、paint、frame。

let p = window.performance.getEntries();
重定向次数:performance.navigation.redirectCount
JS 资源数量:p.filter(ele => ele.initiatorType === “script”).length
CSS 资源数量:p.filter(ele => ele.initiatorType === “css”).length
AJAX 请求数量:p.filter(ele => ele.initiatorType === “xmlhttprequest”).length
IMG 资源数量:p.filter(ele => ele.initiatorType === “img”).length
总资源数量: window.performance.getEntriesByType(“resource”).length

不重复的耗时时段区分:
重定向耗时: redirectEnd - redirectStart
DNS 解析耗时: domainLookupEnd - domainLookupStart
TCP 连接耗时: connectEnd - connectStart
SSL 安全连接耗时: connectEnd - secureConnectionStart
网络请求耗时 (TTFB): responseStart - requestStart
HTML 下载耗时:responseEnd - responseStart
DOM 解析耗时: domInteractive - responseEnd
资源加载耗时: loadEventStart - domContentLoadedEventEnd

其他组合分析:
白屏时间: domLoading - fetchStart
粗略首屏时间: loadEventEnd - fetchStart 或者 domInteractive - fetchStart
DOM Ready 时间: domContentLoadEventEnd - fetchStart
页面完全加载时间: loadEventStart - fetchStart

JS 总加载耗时:

123const p = window.performance.getEntries();let cssR = p.filter(ele => ele.initiatorType === “script”);Math.max(…cssR.map((ele) => ele.responseEnd)) - Math.min(…cssR.map((ele) => ele.startTime));

CSS 总加载耗时:

123const p = window.performance.getEntries();let cssR = p.filter(ele => ele.initiatorType === “css”);Math.max(…cssR.map((ele) => ele.responseEnd)) - Math.min(…cssR.map((ele) => ele.startTime));

# 如何监控?

在了解了 performance 之后,我们来看看,具体是如何监控的?

img

总体流程:性能指标收集与数据上报—数据存储—数据聚合—分析展示—告警、报表推送

这里主要讲述如何收集性能数据。
性能指标收集注意项:1)保证数据的准确性 2)尽量不影响应用的性能

# 1. 基本性能上报

采集数据:将 performance navagation timing 中的所有点都上报,其余的上报内容可参考 performance 分析一节中截取部分上报。例如:白屏时间,JS 和 CSS 总数,以及加载总时长。
其余可参考的上报:是否有缓存?是否启用 gzip 压缩、页面加载方式。
在收集好性能数据后,即可将数据上报。
那选择什么时机上报?
google 开发者推荐的上报方式:

img

# 2. 首屏时间计算

我们知道首屏时间是一项重要指标,但是又很难从 performance 中拿到,来看下首屏时间计算主要有哪些方式?
https://web.dev/first-meaningful-paint/
1)用户自定义打点—最准确的方式(只有用户自己最清楚,什么样的时间才算是首屏加载完成)
2)lighthouse 中使用的是 chrome 渲染过程中记录的 trace event
3)可利用 Chrome DevTools Protocol 拿到页面布局节点数目。思想是:获取到当页面具有最大布局变化的时间点
4)aegis 的方法:利用 MutationObserver 接口,监听 document 对象的节点变化。
检查这些变化的节点是否显示在首屏中,若这些节点在首屏中,那当前的时间点即为首屏渲染时间。但是还有首屏内图片的加载时间需要考虑,遍历 performance.getEntries() 拿到的所有图片实体对象,根据图片的初始加载时间和加载完成时间去更新首屏渲染时间。
5)利用 MutationObserver 接口提供了监视对 DOM 树所做更改的能力,是 DOM3 Events 规范的一部分。
方法:在首屏内容模块插入一个 div,利用 Mutation Observer API 监听该 div 的 dom 事件,判断该 div 的高度是否大于 0 或者大于指定值,如果大于了,就表示主要内容已经渲染出来,可计算首屏时间。
6)某个专利:在 loading 状态下循环判断当前页面高度是否大于屏幕高度,若大于,则获取到当前页面的屏幕图像,通过逐像素对比来判断页面渲染是否已满屏。https://patentimages.storage.googleapis.com/bd/83/3d/f65775c31c7120/CN103324521A.pdf

img

# 3. 异常上报

1)js error
监听 window.onerror 事件
2)promise reject 的异常
监听 unhandledrejection 事件

1
2
3
4
window.addEventListener("unhandledrejection", function (event) {
    console.warn("WARNING: Unhandled promise rejection. Shame on you! Reason: "
        + event.reason);
});

3)资源加载失败
window.addEventListener(’error’)
4)网络请求失败
重写 window.XMLHttpRequest 和 window.fetch 捕获请求错误
5)iframe 异常
window.frames[0].onerror
6)window.console.error

# 4. CGI 上报

大致原理:拦截 ajax 请求
数据存储与聚合
一个用户访问,可能会上报几十条数据,每条数据都是多维度的。即:当前访问时间、平台、网络、ip 等。这些一条条的数据都会被存储到数据库中,然后通过数据分析与聚合,提炼出有意义的数据。例如:某日所有用户的平均访问时长、pv 等。

数据统计分析的方法:平均值统计法、百分位数统计法、样本分布统计法。