总理念
优化什么?为什么优化?怎么优化?
优化什么
1. 指标设定
选择要设定的指标,看看那些需要优化,为什么优化,优化到什么标准是合格的,什么标准是不合格,优化以后能带来什么利益
1.1. 白屏
什么叫白屏时间呢?它指的是从输入内容回车(包括刷新、跳转等方式)后,到页面开始出现第一个字符的时间。这个过程包括 DNS 查询,建立 TCP 连接,发送首个HTTP请求(如果使用HTTPS还要介入 TLS 的验证时间),返回HTML文档,HTML文档 Head 解析完毕。它的标准时间是 300ms。
1.2. 首屏
首屏时间=白屏时间+渲染时间。它是指从浏览器输入地址并回车后,到首屏内容渲染完毕的时间。这期间不需要滚动鼠标或者下拉页面,否则无效。
1.3. 指标标准-分位值,时间值(1s),秒开率
分位值: 以 P99 为例,我们是把所有首屏时间排序,得出排在第 99 位的首屏时间就是 P99。
秒开率: 即 1s 内打开用户的占比
时间值: 如果一个站点对时间敏感,首屏时间在 1s 内,用户感觉会很快;如果首屏时间超过 2.5s,用户就会感觉很慢。但是在 1s 内打开页面,人们对这么短的时间并不敏感,体验不出 10ms 和 50ms 有什么差别。
1.4. 性能瓶颈的问题
客户端请求阶段的瓶颈点
客户端缓存(强缓存/协商缓存) dns解析(dns缓存) http请求 (6.1缓存原则)
服务端数据处理阶段的瓶颈点
数据缓存 资源压缩 重定向
页面解析和渲染阶段的瓶颈点
DOM 树 规范的dom树层级,有正确的闭合,否则时间会很长 jsde加载会阻页面的加载,导致页面加载缓慢 CSSOM树 合理的css布局可以减少重绘和重载,导致页面加载缓慢
1.5. 手段
场景一、弱网
- 合并请求,减少资源请求数量
- 图片暂时使用base64或者本地图片,或者使用占位符
- 资源的懒加载
场景二、首屏vue前端项目
- 利用MutationObserver 接口提供了监视对 DOM 树所做更改的能力。它被设计为旧的 Mutation Events 功能的替代品,该功能是 DOM3 Events 规范的一部分。
- DOM 变化最大时对应的时间获取
- 图片加载完成最大的时间
function CScor(el, tiers, parentScore) {
let score = 0;
const tagName = el.tagName;
if ("SCRIPT" !== tagName && "STYLE" !== tagName && "META" !== tagName && "HEAD" !== tagName) {
const childrenLen = el.children ? el.children.length : 0;
if (childrenLen > 0) for (let childs = el.children, len = childrenLen - 1; len >= 0; len--) {
score += calculateScore(childs[len], tiers + 1, score > 0);
}
if (score <= 0 && !parentScore) {
if (!(el.getBoundingClientRect && el.getBoundingClientRect().top < WH)) return 0;
}
score += 1 + .5 * tiers;
}
return score;
}
function calFinallScore() {
try {
// 1. 一开始,函数检查了一个叫做sendMark的变量,如果为真,则函数立即返回,不执行后续代码。
if (this.sendMark) return;
// 2. 然后,函数计算了从页面开始获取(fetch)到当前的时间,并将其存储在变量time中。
const time = Date.now() - performance.timing.fetchStart;
// 3. isCheckFmp是一个布尔值,用于检查是否应该进行页面加载性能评分的计算。
var isCheckFmp = time > 30000 || SCORE_ITEMS && SCORE_ITEMS.length > 4 && time - (SCORE_ITEMS && SCORE_ITEMS.length && SCORE_ITEMS[SCORE_ITEMS.length - 1].t || 0) > 2 * CHECK_INTERVAL || (SCORE_ITEMS.length > 10 && window.performance.timing.loadEventEnd !== 0 && SCORE_ITEMS[SCORE_ITEMS.length - 1].score === SCORE_ITEMS[SCORE_ITEMS.length - 9].score);
// 4. 如果observer存在并且isCheckFmp为真,则断开observer与目标节点的连接,并将SCORE_ITEMS的副本存储到全局变量SCORE_ITEMS_CHART中。
if (this.observer && isCheckFmp) {
// 5. 然后,计算了fmps(一种用于测量网页加载完成时间的性能指标)。
this.observer.disconnect();
window.SCORE_ITEMS_CHART = JSON.parse(JSON.stringify(SCORE_ITEMS));
let fmps = getFmp(SCORE_ITEMS);
let record = null
for (let o = 1; o < fmps.length; o++) {
if (fmps[o].t >= fmps[o - 1].t) {
let l = fmps[o].score - fmps[o - 1].score;
(!record || record.rate <= l) && (record = {
t: fmps[o].t,
rate: l
});
}
}
// 然后,计算了fmps(一种用于测量网页加载完成时间的性能指标)。
this.fmp = record && record.t || 30001;
// 遍历fmps数组,找出评分变化最大的一项。
// 在try-catch块中,函数检查了所有的图片并计算了最晚加载完成的图片的时间。
try {
this.checkImgs(document.body)
let max = Math.max(...this.imgs.map(element => {
if(/^(\/\/)/.test(element)) element = 'https:' + element;
try {
return performance.getEntriesByName(element)[0].responseEnd || 0
} catch (error) {
return 0
}
}))
// 8. 如果record存在并且其时间t在合理范围内,则更新性能数据。
record && record.t > 0 && record.t < 36e5 ? this.setPerformance({
fmpImg: parseInt(Math.max(record.t , max))
}) : this.setPerformance({});
} catch (error) {
// 9. 如果在上述过程中发生错误,则清空性能数据。
this.setPerformance({});
// console.error(error)
}
} else {
// 10. 如果observer不存在或者isCheckFmp为假,则函数会在CHECK_INTERVAL时间后再次调用自身,直到满足条件为止。
setTimeout(() => {
this.calFinallScore();
}, CHECK_INTERVAL);
}
} catch (error) {
// 11. 最后,如果在函数执行过程中发生了任何未被捕获的错误,那么这些错误会被最外层的catch块捕获,但函数不会对这些错误做任何处理。
}
}
场景三、 白屏指标采集
html页面加载过程
客户端发起请求 -> 下载 HTML 及 JS/CSS 资源 -> 解析 JS 执行 -> JS 请求数据 -> 客户端解析 DOM 并渲染 -> 下载渲染图片-> 完成渲整体染。
白屏时间 = 页面开始展示时间点 - 开始请求时间点。
App下的白屏时间
初始化 WebView -> 客户端发起请求 -> 下载 HTML 及 JS/CSS 资源 -> 解析 JS 执行 -> JS 请求数据 -> 服务端处理并返回数据 -> 客户端解析 DOM 并渲染 -> 下载渲染图片 -> 完成整体渲染。
多了启动浏览器内核,也就是 Webview 初始化的时间。这个时间必须通过手动采集的方式来获得,而且因为线上线下时间差别不大,线下采集即可。具体来说,在 App 测试版本中,程序在 App 创建 WebView 时打一个点,然后在开始建立网络连接打一个点,这两个点的时间差就是 Webview 初始化的时间。
场景四、卡顿指标采集
FPS(Frames Per Second,每秒显示帧数) 连续 3 帧不低于 20 FPS,且保持恒定。