咱们都知道网站性能很重要。那在说到网站性能、网站速度很快的时候,咱们具体指的是什么呢?git
首先咱们要知道,网站打开速度是一个相对的概念。同一个网站,在高端机上或者网络优良的状况下,能够是很快的,在低端机,网络差的状况却很慢。github
下图是一个网站的打开时间分布图,X 轴是耗时区间,Y 轴是用户量。首先从这个图中,咱们能够知道,网站打开速度并非一个固定的数值,并且一个区分分布。虽然大部分用户打开时间在 2s 内,但其实仍是存在少许用户,他们的打开时间大于 10 秒。web
还有一些其余的概念:canvas
那么咱们该若是衡量网站性能呢,站在用户的角度上想,咱们须要回答一下几个问题?浏览器
是否发生? | 导航发生了吗?服务器响应了吗? |
是否有用? | 页面是否渲染出足够可用内容 |
是否可用? | 用户能够与该页面进行交互,仍是仍在加载中? |
是否使人愉快? | 交互是否顺畅天然,没有滞后和卡顿? |
先用一个直观的图片来展现,页面显示的各个阶段。缓存
FP (First Paint) 首次绘制,表明浏览器第一次像屏幕传输像素的时间,也就是页面在屏幕上首次发生视觉变化的时间。安全
FCP (First Contentful Paint) 首次内容绘制,表明浏览器第一次绘制内容的时间。服务器
如何计算markdown
function showPaintTimings() {
if (window.performance) {
let performance = window.performance;
let performanceEntries = performance.getEntriesByType('paint');
performanceEntries.forEach((performanceEntry, i, entries) => {
console.log(
'The time to ' +
performanceEntry.name +
' was ' +
performanceEntry.startTime +
' milliseconds.'
);
});
} else {
console.log("Performance timing isn't supported.");
}
}
showPaintTimings();
// The time to first-paint was 949.5900000038091 milliseconds.
// The time to first-contentful-paint was 949.5900000038091 milliseconds.
复制代码
FP 不包含默认背景绘制,可是包含非默认背景绘制。网络
只有绘制文本、图片(包含背景图)、非白色的 svg 或者 canvas 是才被算做 FCP
FMP (First Meaningful Paint) 首次有效绘制,是页面主要内容绘制的时间点。
在打开一个网页的时候,随着网页的加载与解析,浏览器会将布局对象(Layout Object)逐步添加到布局树(Layout Tree)上进行布局。
将两张图结合起来解读
从以上对于「谷歌搜索结果页」加载过程的例子中能够发现,布局对象的数量与页面完成度高度相关。咱们得出如下结论:
FMP = 页面在加载和渲染过程当中最大布局变更以后的那个绘制时间
复制代码
基于刚刚得出的结论:FMP 的时间点为 DOM 结构变化最剧烈的时间点。DOM 结构变化的时间点能够经过 MutationObserver API 来得到。
// 用于存放每次 dom 变话时,时间和 dom 数量
const list = [];
// 起止时间
const startTime = Date.now();
// 建立监听
const observer = new MutationObserver(callback);
// 监听 dom 变动
observer.observe(document, {
childList: true,
subtree: true,
});
// dom 变动回调
function callback() {
const duration = Date.now() - startTime;
const body = document.querySelector('body');
list.push({
number: body ? count(body) : 0,
duration,
});
}
// 计算 dom 数量
function count(element) {
let number = 0;
const childrenLength = element.children ? element.children.length : 0;
if (childrenLength > 0) {
const children = element.children;
for (let length = childrenLength - 1; length >= 0; length--) {
number += count(children[length]);
}
}
number += 1;
return number;
}
// 找出变化作大的时间点, 计作 FMP
function getFmp() {
let result;
for (let i = 1; i < list.length; i++) {
const diff = list[i].number - list[i - 1].number;
if (!result.diff || diff > result.diff) {
result = {
duration: list[i].duration,
diff,
};
}
}
return result?.duration || 0;
}
复制代码
当前方法能够在大部分状况下得出 FMP 值,可是在一些其余场景下,仍是存在误差。
图 3 是微博页面在加载和渲染的可视化过程
图 4 展现了当加载微博页面时,被逐步加载到布局树中的布局对象的数量。
经过图 3 图 4 能够看出,主要元素加载时间在 6.047 秒,可是布局对象发生变化最大的时候,是 24.25 秒。在 24.25 秒的时候,页面底部到可见区域外,大量元素被添加到布局树上。
为了优化上述现象,咱们引入布局意义概念。
布局意义 = 添加的布局对象数量 / max(1, 页面高度 / 屏幕高度)
复制代码
// 计算页面比率
function getRatio() {
const clinetH = document.body.clientHeight;
const screenH = window.screen.availHeight;
return Math.max(clinetH / screenH, 1);
}
// 更新 callback 方法
function callback() {
const duration = Date.now() - startTime;
const body = document.querySelector('body');
list.push({
number: body ? count(body) / getRatio(): 0,
duration,
});
}
复制代码
图 5 展现了当加载微博页面时,布局意义变化状况。布局意义最大变化发生在 5.89 秒,在 FMP(6.047)秒以前,符合预期。
TTI (Time to Interactive) 可交互时间,表明网页第一次达到可交互的时间点。首页是页面的 ui 是可交互的状态,而且无场长任务运行,即页面是流畅的。
下图演示了如何查找 TTI。
如何计算
谷歌团队开发了一个polyfill,用于检测 TTI,适用于全部支持 Long Tasks API 的浏览器。
import ttiPolyfill from './path/to/tti-polyfill.js';
ttiPolyfill.getFirstConsistentlyInteractive(opts).then((tti) => {
// Use `tti` value in some way.
});
复制代码
长任务(Long Tasks API):任何连续不间断的且主 UI 线程繁忙 50 毫秒及以上的时间区间。
页面”是否使人愉快“,主要有几个角度,动画是否流畅,用户交互是否能够快速影响。而卡顿、交互响应慢的状况一般由长任务致使的,了解长任务的发生频率,能够帮助咱们判断页面是否流畅。
如何计算
var observer = new PerformanceObserver(function(list) {
var perfEntries = list.getEntries();
for (var i = 0; i < perfEntries.length; i++) {
// Process long task notifications:
// report back for analytics and monitoring
// ...
}
});
// register observer for long task notifications
observer.observe({ entryTypes: ['longtask'] });
// Long script execution after this will result in queueing
// and receiving "longtask" entries in the observer.
复制代码
下图1、图二是 Navigation Timing API 初版和第二版,该 API 提供了可用于衡量一个网站性能的数据。记录了页面重定向、DNS 查询、TCP、SSL 链接、内容请求、DOM 解析等的时间点。
指标 | 说明 |
---|---|
startTime | 0 |
unloadEventStart | 上一个页面 unload 事件的触发时间,只有同源跳转能够记录,非同源返回 0 |
unloadEventEnd | 上一个页面 unload 事件的结束时间 |
redirectStart | 第一个 http 重定向请求发起时间,只有同源跳转能够记录,非同源返回 0 |
redirectEnd | 最后一个 http 重定向请求发起时间 |
fetchStart | 请求开始时间,发生在检查本地缓存以前 |
domainLookupStart | 域名解析(DNS)开始时间,若是存在本地缓存,则该值等同于 fetchStart |
domainLookupEnd | 域名解析(DNS)结束时间,若是存在本地缓存,则该值等同于 fetchStart |
connectStart | 请求链接被发送到网络的时间 |
secureConnectionStart | 安全链接握手开始的时间 |
connectEnd | 网络连接创建的时间 |
requestStart | 浏览器发送文档请求的开始时间 |
responseStart | 浏览器接收到响应的第一个字节的时间 |
responseEnd | 浏览器接收到响应的最后一个字节的时间 |
domInteractive | 主文档的解析器结束工做,Document.readyState 改变为 interactive 的时间,至关于 readystatechange 时间触发的时间 |
domContentLoadedEventStart | 全部的须要被运行的脚本已经被解析,即 DOMContentLoaded 事件触发时间 |
domContentLoadedEventEnd | 全部的须要被运行的脚本已经执行完毕 |
domComplete | 主文档的解析器结束工做,Document.readyState 变为 complete |
loadEventStart | load 事件触发时间 |
loadEventEnd | load 时间完成时间 |
阶段 | 计算方式 |
---|---|
DNS 查询 | domainLookupEnd - domainLookupStart |
TCP 链接 | connectEnd - connectStart |
SSL 建连 | connectEnd - secureConnectionStart |
首字节网络请求(TTFB) | responseStart - requestStart |
内容传输 | responseEnd - responseStart |
DOM 解析 | domInteractive - responseEnd |
白屏时间 | domInteractive - t.fetchStart |
资源加载 | loadEventStart - domContentLoadedEventEnd |
DOM Ready | domContentLoadedEventEnd - fetchStart |
相关文章