前端工程师须要知道的性能知识

咱们都知道网站性能很重要。那在说到网站性能、网站速度很快的时候,咱们具体指的是什么呢?git

首先咱们要知道,网站打开速度是一个相对的概念。同一个网站,在高端机上或者网络优良的状况下,能够是很快的,在低端机,网络差的状况却很慢。github

下图是一个网站的打开时间分布图,X 轴是耗时区间,Y 轴是用户量。首先从这个图中,咱们能够知道,网站打开速度并非一个固定的数值,并且一个区分分布。虽然大部分用户打开时间在 2s 内,但其实仍是存在少许用户,他们的打开时间大于 10 秒。web

FMP区间分布图

还有一些其余的概念:canvas

  • 两个网站加载时间相同,渐进式渲染的网站比加载完渲染的网站,感受上会更快。
  • 网站加载速度很快,交互响应很慢,咱们也会以为网站性能差。

定义指标

那么咱们该若是衡量网站性能呢,站在用户的角度上想,咱们须要回答一下几个问题?浏览器

是否发生? 导航发生了吗?服务器响应了吗?
是否有用? 页面是否渲染出足够可用内容
是否可用? 用户能够与该页面进行交互,仍是仍在加载中?
是否使人愉快? 交互是否顺畅天然,没有滞后和卡顿?

如何度量

先用一个直观的图片来展现,页面显示的各个阶段。缓存

Page Painting

是否发生 FP 、 FCP

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

FMP (First Meaningful Paint) 首次有效绘制,是页面主要内容绘制的时间点。

如何计算 FMP 指标

在打开一个网页的时候,随着网页的加载与解析,浏览器会将布局对象(Layout Object)逐步添加到布局树(Layout Tree)上进行布局。

example1

example2

  • 图 1 展现了当加载谷歌搜索结果页面时,被逐步加载到布局树中的布局对象的数量。
  • 图 2 展现了加载「谷歌搜索结果页」在加载和渲染过程当中的可视化过程

将两张图结合起来解读

  1. 1.577s,页面头部渲染,”布局对象“总数是 60 个。
  2. 1.76s,页面头部渲染完成,“布局对象”总数是 103 个。
  3. 1.907s, 搜索结果数据返回并渲染,“布局对象”总数是 261。而此时页面主体内容已经绘制完成,从用户体验的角度看,此时的时间点就是是 FMP。
  4. 2.425s, 其余搜索结果和页面底部的布局对象继续被添加到布局树中并进行绘制,页面完成最终加载和渲染。

从以上对于「谷歌搜索结果页」加载过程的例子中能够发现,布局对象的数量与页面完成度高度相关。咱们得出如下结论:

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 值,可是在一些其余场景下,仍是存在误差。

example3

example4

图 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

TTI (Time to Interactive) 可交互时间,表明网页第一次达到可交互的时间点。首页是页面的 ui 是可交互的状态,而且无场长任务运行,即页面是流畅的。

下图演示了如何查找 TTI。

TTI

  • 从 FCP 开始,向前搜索一个 5s 以上的静默窗口。静默窗口:无场任务,且网络请求不超过 2 个。
  • 从静默窗口日后搜索,最后一个长任务的结束时间,若是搜索不到,则在 FCP 处中止。
  • TTI 是静默窗口以前的最后一个长任务的结束时间(若是找不到长任务,则使用 FCP 的值做为 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 解析等的时间点。

Navigation Timing Level 1

Navigation Timing Level 2

指标解读

指标 说明
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

相关文章

User-centric performance metrics

First Contentful Paint

Time to First Meaningful Paint

Time to Interactive (TTI)

渲染页面:浏览器的工做原理

相关文章
相关标签/搜索