引言
在进行性能优化之前,做好性能数据的采集很关键,它不仅可以指明获取优化成果最显著的方向 ,还能量化成果。
经过调研,我发现衡量页面性能的参数分为两种,一种是通过 Performance API 采集,它是 window 对象自带的属性,浏览器兼容性好,采集到的数据更加基础,所以可操作就更强,我们可以在此基础上做定制化的分析、计算、上报;另一种方法是通过 lighthouse 采集,它会根据采集到的数据给出一份报告,这个报告包含了很多我们熟悉的性能衡量名词,如FCP、LCP,还给出了一些针对性的优化建议。
所以,根据我初步的判断,如果是要进行性能优化实操,用 lighthouse 比较好,如果要封装性能监测 sdk ,用来收集页面性能、监测性能异常等,用Performance API比较好
Performance
性能参数的含义(参数解读引自 网页性能检测-performance)
| 属性名 | 含义 |
|---|---|
| navigationStart | 浏览器窗口的前一个网页关闭时发生unload事件时的Unix时间戳,属于最前的测量时间点 |
| unloadEventStart | 前网页与当前网页同属一个域名时,返回前一个网页的unload事件发生时的Unix时间戳 |
| unloadEventEnd | 前网页与当前网页同属一个域名时,返回前一个网页unload事件的回调函数结束时的Unix时间戳 |
| redirectStart | 返回第一个HTTP跳转开始时的Unix时间戳 |
| redirectEnd | 返回最后一个HTTP跳转结束时的Unix时间戳 |
| fetchStart | 返回浏览器准备使用HTTP请求读取文档等资源时的Unix时间戳,在网页查询本地缓存之前发生 |
| domainLookupStart | 返回域名查询开始时的Unix时间戳。如果使用持久连接,或者信息是从本地缓存获取的,则返回值等同于fetchStart属性的值 |
| domainLookupEnd | 返回域名查询结束时的Unix毫秒时间戳。如果使用持久连接,或者信息是从本地缓存获取的,则返回值等同于fetchStart属性的值 |
| connectStart | 返回HTTP请求开始向服务器发送时的Unix毫秒时间戳。如果使用持久连接(persistent connection),则返回值等同于fetchStart属性的值 |
| connectEnd | 返回浏览器与服务器之间的连接建立时的Unix毫秒时间戳。如果建立的是持久连接,则返回值等同于fetchStart属性的值。连接建立指的是所有握手和认证过程全部结束 |
| secureConnectionStart | 返回浏览器与服务器开始安全链接的握手时的Unix毫秒时间戳。如果当前网页不要求安全连接,则返回0 |
| requestStart | 返回浏览器向服务器发出HTTP请求时(或开始读取本地缓存时)的Unix毫秒时间戳 |
| responseStart | 返回浏览器从服务器收到(或从本地缓存读取)第一个字节时的Unix毫秒时间戳 |
| responseEnd | 返回浏览器从服务器收到(或从本地缓存读取)最后一个字节时(如果在此之前HTTP连接已经关闭,则返回关闭时)的Unix毫秒时间戳 |
| domLoading | 返回当前网页DOM结构开始解析时(即Document.readyState属性变为“loading”、相应的readystatechange事件触发时)的Unix毫秒时间戳 |
| domInteractive | 返回当前网页DOM结构结束解析、开始加载内嵌资源时(即Document.readyState属性变为“interactive”、相应的readystatechange事件触发时)的Unix毫秒时间戳 |
| domContentLoadedEventStart | 返回当前网页DOMContentLoaded事件发生时(即DOM结构解析完毕、所有脚本开始运行时)的Unix毫秒时间戳 |
| domContentLoadedEventEnd | 返回当前网页所有需要执行的脚本执行完成时的Unix毫秒时间戳 |
| domComplete | 返回当前网页DOM结构生成时(即Document.readyState属性变为“complete”,以及相应的readystatechange事件发生时)的Unix毫秒时间戳 |
| loadEventStart | 返回当前网页load事件的回调函数开始时的Unix毫秒时间戳。如果该事件还没有发生,返回0 |
| loadEventEnd | 返回当前网页load事件的回调函数运行结束时的Unix毫秒时间戳。如果该事件还没有发生,返回0 |
发展历程
1. 通过window.performance.timing获取
这个api可以获取大部分性能数据,但是已经废弃。现在常用window.performance.getEntries来替代,后者的精度可以从前者的微秒提到了纳秒,除此之外还提供了更多功能。
//可以在控制台打印查看
const performanceTiming = window.performance.timing;
const {
connectEnd,
connectStart,
domComplete,
domContentLoadedEventEnd,
domContentLoadedEventStart,
domInteractive,
domLoading,
domainLookupEnd,
domainLookupStart,
fetchStart,
loadEventEnd,
loadEventStart,
navigationStart,
redirectEnd,
redirectStart,
requestStart,
responseEnd,
responseStart,
secureConnectionStart,
unloadEventEnd,
unloadEventStart,
} = performanceTiming;
console.log(performanceTiming);
console.log(
`🎨 Prompt for unload: ${navigationStart}`,
`\n🎐 redirect: ${redirectEnd - redirectStart}`,
`\n🎄 unload: ${unloadEventEnd - unloadEventStart}`,
`\n⌛️ App cache: ${domainLookupStart - fetchStart}`,
`\n🧩 DNS: ${domainLookupEnd - domainLookupStart}`,
`\n⚽ TCP: ${connectEnd - connectStart}`,
`\n👖 Request: ${responseStart - requestStart}`,
`\n🩱 Response: ${responseEnd - responseStart}`,
`\n👚 Processing: ${domComplete - domLoading}`,
`\n🛍️ onload: ${loadEventEnd - loadEventStart}`
);
2. 通过window.performance.getEntries获取
const entries = window.performance.getEntries();
console.log(entries[0]);
const {
connectEnd,
connectStart,
domComplete,
domContentLoadedEventEnd,
domContentLoadedEventStart,
domInteractive,
domLoading,
domainLookupEnd,
domainLookupStart,
fetchStart,
loadEventEnd,
loadEventStart,
navigationStart,
redirectEnd,
redirectStart,
requestStart,
responseEnd,
responseStart,
secureConnectionStart,
unloadEventEnd,
unloadEventStart,
} = entries[0];
console.log(
`🎨 Prompt for unload: ${navigationStart}`,
`\n🎐 redirect: ${redirectEnd - redirectStart}`,
`\n🎄 unload: ${unloadEventEnd - unloadEventStart}`,
`\n⌛️ App cache: ${domainLookupStart - fetchStart}`,
`\n🧩 DNS: ${domainLookupEnd - domainLookupStart}`,
`\n⚽ TCP: ${connectEnd - connectStart}`,
`\n👖 Request: ${responseStart - requestStart}`,
`\n🩱 Response: ${responseEnd - responseStart}`,
`\n👚 Processing: ${domComplete - domLoading}`,
`\n🛍️ onload: ${loadEventEnd - loadEventStart}`
);
3. 通过window.performance.getEntries + PerformanceObserver获取
由于window.performance.getEntries一般在window.onload事件中调用,只能获取到页面加载时的数据,无法跟进后续的性能,这里引入的PerformanceObserver可以自动监听性能的变化并上报。
const config = {
entryTypes: ['navigation', 'resource', 'paint'],
};
const observer = new PerformanceObserver((list) => {
list.getEntries().forEach((entry) => {
console.log(
entry,
`Name: ${entry.name}`,
`Type: ${entry.entryType}`,
`Start: ${entry.startTime}`,
`Duration: ${entry.duration}`
);
});
});
// 开始观察
observer.observe(config);
lighthouse
性能参数含义
1. FCP(15%)
FCP 是指浏览器从 DOM 中渲染第一个内容所需的确切时间,这个时间可以是任何重要的图片、文本,甚至是页面底部的小 SVG。
1.TTI(15%)
在此时间点,页面布局已经稳定,主要的网络字体已经可见,主线程已可以响应用户输入 — 基本上意味着只是用户可以与 UI 进行交互。
- 页面显示有用的内容,由First Contentful Paint衡量(FCP时间)
- 为大多数可见页面元素注册了事件处理程序(onLoad完成时间)
- 页面在 50 毫秒内响应用户交互。
3.SI(15%)
衡量视觉上页面被内容充满的速度,数值越低越好。
4.TBT(25%)
是一个重要的、以用户为中心的指标,用于衡量加载响应性,因为它有助于量化一个页面在成为可靠的交互性之前的非交互性的严重程度 -- 低 TBT 有助于确保页面的可用性。
5.LCP(25%)
是一个重要的、以用户为中心的衡量标准,用于衡量感知加载速度,因为它标记了页面加载时间轴中页面的主要内容可能加载的时间点 -- 快速的 LCP 有助于向用户保证页面是有用的。
6.CLS(5%)
是衡量视觉稳定性的一个重要的、以用户为中心的指标,因为它有助于量化用户经历意外布局变化的频率,低 CLS 有助于确保页面令人愉悦。
使用方式
node环境下使用puppeteer和lighthouse获取性能报告
const lighthouse = require('lighthouse');
const puppeteer = require('puppeteer');
const chromeLauncher = require('chrome-launcher');
const reportGenerator = require('lighthouse/lighthouse-core/report/report-generator');
const request = require('request');
const util = require('util');
const options = {
locale: 'zh-CH', //输出中文报告
chromeFlags: ['--headless'], //无头模式,不打开浏览器页面执行
};
/**
*
* Perform a Lighthouse run
* @param {String} url - url The URL to test
* @param {Object} options - Optional settings for the Lighthouse run
* @param {Object} [config=null] - Configuration for the Lighthouse run. If
* not present, the default config is used.
*/
async function lighthouseFromPuppeteer(url, options, config) {
// Launch chrome using chrome-launcher
const chrome = await chromeLauncher.launch(options);
options.port = chrome.port;
console.log(options);
// Connect chrome-launcher to puppeteer
const resp = await util.promisify(request)(
`http://localhost:${options.port}/json/version`
);
const { webSocketDebuggerUrl } = JSON.parse(resp.body);
const browser = await puppeteer.connect({
browserWSEndpoint: webSocketDebuggerUrl,
});
const page = await browser.newPage();
//这里塞入cookie可以避免登录重定向
await page.setCookie({
name: 'token',
value: 'xxx',
url,
});
await page.goto(url);
// Run Lighthouse
const { lhr } = await lighthouse(url, options, config);
await browser.disconnect();
await chrome.kill();
const json = reportGenerator.generateReport(lhr, 'json');
const audits = JSON.parse(json).audits; // Lighthouse audits
const performance = JSON.parse(json).categories['performance'].score; // Lighthouse audits
const firstContentfulPaint = audits['first-contentful-paint'].displayValue;
const totalBlockingTime = audits['total-blocking-time'].displayValue;
const timeToInteractive = audits['interactive'].displayValue;
console.log(`\n
Lighthouse metrics:
🎨 Performance Score: ${performance},
🎨 First Contentful Paint: ${firstContentfulPaint},
⌛️ Total Blocking Time: ${totalBlockingTime},
👆 Time To Interactive: ${timeToInteractive}`);
}
lighthouseFromPuppeteer('https://www.baidu.com/', options);