前言

经常遇到性能优化,零零散散看过一些技术文章,今天来系统整理一下。

数据上传-“GIF 像素追踪技术“

使用 1x1 像素的透明 GIF 图片进行数据上传,通常被称为“GIF 像素追踪技术“

也可以是一些埋点信息,等等。讲到数据上传,大家一般都会想到利用接口 API 上传,但行业内比较推荐的就是使用 Gif,为什么不直接用接口呢?

原因

  1. 轻量化:GIF 是一种无损压缩格式,文件大小相对较小,能够快速加载和传输。这对于网络传输速度较慢或者带宽有限的情况下非常有用。
  2. 透明背景:GIF 格式支持透明背景,可以将埋点信息图像叠加在任何背景上,不会影响页面的显示效果。
  3. 兼容性:几乎所有的浏览器和设备都会支持 GIF 图片,无需担心兼容性问题。
  4. 避开跨域问题:如果直接使用 AJAX 请求 API,可能会遇到跨域问题。而请求 GIF 图片则可以绕过这个问题。
  5. 适用于各种事件追踪:例如页面的加载、按钮的点击、鼠标的移动等。

具体方法

  1. 部署一个 nginx 服务,且在可访问的路径中放入一个 1x1 像素的透明 GIF 图片
  2. 使用页面的 Image 对象加载图片时,将页面的性能参数作为这个图片的请求参数,传给后台
  3. 成功访问到这个 gif 时,nginx 的 assets 日志会自动保存,URL 的全部信息
  4. 使用 node 或者 kafka 来读取日志,做清洗并入库

注意点

首先,因为它是通过 GET 请求来发送数据的,所以数据量有限

其次,服务器需要正确设置响应头,以确保图片不会被缓存,否则可能会影响数据的实时性。

上传时机

onload

load 用于检测一个完全加载的页面,页面的 html、css、js、图片等资源都已经加载完之后才会触发 load 事件。完全加载后便可以上传指标数据

不用 DOMContentLoaded,因为初始的 HTML 文档被完全加载和解析完成之后,DOMContentLoaded 事件就会被触发,而无需等待样式表、图像和子框架的完成加载。此时指标数据还没完善

PerformanceObserver

一个构造函数,用于创建一个新的PerformanceObserver对象,它可以监听**performance**时间线上的特定事件,比如资源加载时间(resource timing)、浏览器绘制帧率(frame timing)、用户交互事件(User Timing)等,并在这些事件发生时运行回调函数。这使得我们能够异步地收集和分析某些类型的性能事件。在性能条目添加到时间线的那一刻立即获取和处理这些条目,而无需等待所有条目都可用。

性能数据获取

Performance

Performance 是一个 JavaScript 的全局对象,它提供了对 web 性能的访问。这个对象可以让你获取到网页的性能相关信息,如导航时间、资源加载时间等。这个对象是 Performance API 的入口点,包含了一系列的方法和属性,用以收集、处理和查询性能数据。

以下是一些常用的 Performance 对象的方法和属性:

eventCount

一个 EventCounts 映射,其中包含每个事件类型已调度的事件数。

// Report all exposed events
for (entry of performance.eventCounts.entries()) {
const type = entry[0];
const count = entry[1];
// sendToEventAnalytics(type, count);
}

// Report a specific event
const clickCount = performance.eventCounts.get("click");
// sendToEventAnalytics("click", clickCount);

// Check if an event count is exposed for a type
const isExposed = performance.eventCounts.has("mousemove"); // false

image-20230822105201196

memory(已废弃)

当前页面的内存使用情况对象,包含:

jsHeapSizeLimit :表示当前页面最多可以获得的 JavaScript 堆大小)

totalJSHeapSize :表示当前页面已经分配的 JavaScript 堆大小

usedJSHeapSize :表示当前页面 JavaScript 已经使用的堆大小

totalJSHeapSize大于jsHeapSizeLimit会触发页面崩溃,存在内存泄露的风险。

image-20230822104909234

performance.navigation(已废弃)

PerformanceNavigation 是一个包含页面导航信息的对象,它是 Performance API 的一部分。这个接口提供了有关如何导航到当前网页的信息,包括是否是重定向、是否是通过书签进入、是否是通过链接进入等。使用 PerformanceNavigationTiming 代替

以下是 PerformanceNavigation 的一些主要属性:

  • type::这个属性返回一个整数,表示导航的类型。可能的值为:
    • 0:正常的导航。例如,通过在地址栏输入 URL、通过书签、通过链接等。
    • 1:通过刷新按钮或者 location.reload() 重新加载的页面。
    • 2:通过前进或后退按钮导航的页面。
  • redirectCount:这个属性返回一个整数,表示在导航到当前页面的过程中发生的重定向的数量。
  • image-20230822104932822

performance.now()

返回一个以毫秒为单位的时间戳,表示从页面加载开始到该方法被调用的时间。

performance.timing(已废弃)

返回一个 PerformanceTiming` 对象,包含了各种与导航和页面加载相关的时间戳。

image-20230822104956535

使用 PerformanceNavigationTiming 代替

performance.getEntries()

返回一个 PerformanceEntry 对象的数组,包含了所有性能条目。

performance.getEntriesByType(type)

返回一个 PerformanceEntry 对象的数组,只包含给定类型的性能条目。

performance.getEntriesByName(name, type)

返回一个 PerformanceEntry 对象的数组,只包含给定名称和类型的性能条目。

通过 Performance 对象,你可以获取到许多有关网页性能的详细信息,这对于性能分析和优化是非常有价值的。

entry

在 Performance API 中,“entry”是一个术语,指的是对一种性能事件的记录。性能事件可以是各种类型,包括资源加载、测量、标记等。

每个性能条目(entry)都是一个对象,包含了一些基本的属性,如:

  • name:条目的名称。对于资源加载条目,这是请求的 URL;对于用户定义的性能标记和测量,这是用户定义的名称。
  • entryType:条目的类型,例如”navigation”、”resource”、”mark”、”measure”等。
  • startTime:条目开始的时间,以毫秒为单位。
  • duration:条目的持续时间,以毫秒为单位。

不同类型的性能条目可能会有额外的属性。例如,资源加载条目还有fetchStartresponseEnd等属性,分别表示开始获取资源的时间和资源获取结束的时间。

你可以使用performance.getEntries()performance.getEntriesByName()performance.getEntriesByType()等方法获取性能条目。例如:

const entries = performance.getEntries();
for (let i = 0; i < entries.length; i++) {
const entry = entries[i];
console.log(
"Name: " +
entry.name +
", Type: " +
entry.entryType +
", Start time: " +
entry.startTime +
", Duration: " +
entry.duration
);
}

以上代码将输出所有性能条目的名称、类型、开始时间和持续时间。

PerformanceNavigationTiming

请注意,PerformanceNavigationTiming 接口是在 Navigation Timing API Level 2 中引入的,可能并不是所有的浏览器都支持。在使用这个接口之前,你应该检查它是否可用。

proformance.timing,proformance.navigation 已经被废弃了,官方推荐使用 PerformanceNavigationTiming 替代,不过它是一个对象类型,通过performance.getEntriesByType('navigation')返回。

其中包括了以下属性:

  • connectStart:HTTP(TCP)开始或重新建立链接的时间,如果是持久链接,则和 fetchStart 一致

  • connectEnd:HTTP(TCP)完成建立链接的时间(完成握手),如果是持久链接,则和 fetchStart 一致。

  • domComplete:DOM 渲染完成的时间戳

  • domContentLoadedEventEnd:DOMContentLoaded 事件结束的时间戳

  • domContentLoadedEventStart:DOMContentLoaded 事件开始的时间戳

  • domInteractive:DOM 解析完成的时间戳

  • domainLookupEnd:DNS 查询结束的时间戳

  • domainLookupStart:DNS 查询开始的时间戳

  • duration:页面加载总耗时

  • encodedBodySize:页面内容压缩后的大小

  • entryType:性能条目类型,此处为 navigation

  • fetchStart:第一个网络请求发起的时间戳

  • initiatorType:发起性能条目生成的类型,此处为 navigation

  • loadEventEnd:load 事件结束的时间戳

  • loadEventStart:load 事件开始的时间戳

  • name:性能条目名称,此处为 document

  • nextHopProtocol:下一跳协议,如 h2、h3 等

  • redirectCount重定向次数 (代替 performance.navigation)

  • redirectEnd:最后一个重定向结束的时间戳

  • redirectStart:第一个重定向开始的时间戳

  • requestStart:第一个网络请求发起的时间戳

  • responseEnd:最后一个网络响应结束的时间戳

  • responseStart:第一个网络响应开始的时间戳

  • secureConnectionStart:安全连接建立开始的时间戳(HTTPS)

  • serverTiming:服务器计时信息,如 TTFB 等

    serverTiming 是一个性能条目的属性,属于 Performance API 的一部分,它返回一个数组,每个元素都是一个 PerformanceServerTiming 对象。这些对象提供了服务器端的性能计时信息。

    每个 PerformanceServerTiming 对象包含以下几个属性:

    • name:服务器计时度量的名称。
    • duration:度量的持续时间(毫秒)。如果服务器没有返回持续时间,此值为 0。
    • description:服务器提供的关于度量的额外描述性信息。如果服务器没有返回此类信息,此值为空字符串。
  • startTime:性能条目开始的时间戳

  • type:**获取导航类型 **(代替 performance.navigation)

    navigate:通过点击链接、地址栏输入、表单提交、脚本操作等进行的导航

    reload:通过浏览器的刷新按钮或 location.reload()进行的导航

    backforward:通过浏览器的前进/后退按钮进行的导航_

    prerender:页面在后台预先渲染,然后切换到该页面的导航

    image-20230822163357563

!!! 基于上述数据内容,我们可以得到如下的执行顺序模块。 !!!

  1. 页面卸载部分
  • 卸载:navigationStart、unloadEventStart、unloadEventEnd
  1. 网络部分
  • 跳转:redirectStart、redirectEnd
  • 请求文档:fetchStart
  • DNS 查询:domainLookupStart、domainLookupEnd
  • 连接建立:connectStart、connectEnd
  • 请求:requestStart、responseStart、responseEnd
  1. 页面解析部分
  • 解析 dom:domLoading、domInteractive
  • 资源加载,dom 渲染:domContentLoadedEventStart、domContentLoadedEventEnd、domComplete

作者:闭目入神
链接:https://juejin.cn/post/7223280402475089978
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

性能指标

TTFB

Time To First Byte,它指的是从浏览器发起请求到从服务器接收到第一个字节的数据所花费的时间。包含重定向时延,DNS 查询时延,链接建立延迟,请求响应时延

// 1 使用PerformanceNavigationTiming
window.onload = () => {
const [pageNav] = performance.getEntriesByType("navigation")[0];
const TTFB = pageNav.responseStart - pageNav.navigationStart;
};

// 2 使用PerformanceObserver
new PerformanceObserver((entryList) => {
const [pageNav] = entryList.getEntriesByType("navigation");
const TTFB = pageNav.responseStart - pageNav.navigationStart;
}).observe({
type: "navigation",
buffered: true,
});

这两种方法都可以用来获取浏览器的导航性能数据,但它们的工作方式和使用场景略有不同。

  1. 使用 new PerformanceObserver() 创建的观察器对象,是一个异步监听的方式。当你创建这个观察器时,你需要提供一个回调函数,这个回调函数会在每次有新的性能条目添加到性能时间线时被调用。在你的代码示例中,这个回调函数用于获取类型为 ‘navigation’ 的性能条目,并从中获取 ‘responseStart’ 时间,即 TTFB。这种方式的好处是,它可以在性能条目添加到时间线的那一刻立即获取和处理这些条目,而无需等待所有条目都可用。
  2. performance.getEntriesByType('navigation') 是一个同步的方式,它会立即返回一个包含所有类型为 ‘navigation’ 的性能条目的数组。这种方式的好处是,它可以在任何时间点获取所有已经添加到性能时间线的 ‘navigation’ 类型的性能条目,而不仅仅是在这些条目被添加到时间线的那一刻。

总的来说,new PerformanceObserver() 更适合于实时处理性能数据的场景,而 performance.getEntriesByType('navigation') 更适合于在特定时间点获取和分析性能数据的场景。

FP

First Paint,指代用户发起请求到浏览器开始渲染页面内第一个像素点所经过的时间。一般 html 解析结束之后就会开始触发.

获取 first-paint Entry

performance
.getEntries("paint")
.filter((entry) => entry.name == "first-paint")[0].startTime;

FCP

First Contentful Paint

首次内容绘制时间,即浏览器首次绘制任何文本、图像、非空白 canvas 或 SVG 的时间。

performance
.getEntries("paint")
.filter((entry) => entry.name == "first-contentful-paint")[0].startTime;
new PerformanceObserver((entryList) => {
for (const entry of entryList.getEntriesByName("first-contentful-paint")) {
// entry.startTime的值即为FCP
}
}).observe({ type: "paint", buffered: true });

SI

Speed Index,用于测量页面内容的可视部分在多长时间内被充分显示出来。

LCP

Largest Contentful Paint。最大的可见元素(如图片或文本块)何时在视口中完成渲染。

new PerformanceObserver((entryList) => {
for (const entry of entryList.getEntries()) {
// entry.startTime 的值即为LCP
}
}).observe({ type: "largest-contentful-paint", buffered: true });

SI 和 LCP 区别

当我们打开一个网页时,举个例子说是一篇新闻文章,网页中会有标题、文章内容、图片、评论等等元素。

  1. Speed Index:这个指标关注的是整个页面加载的速度。例如,从打开这个网页开始,标题出现了,图片出现了,文章内容出现了,评论加载出来了,这整个过程的速度就是 Speed Index 关注的。它通过记录整个页面加载过程的快慢来反映用户的体验。如果 Speed Index 得分低,就说明这个网页从开始加载到全部内容呈现给用户的速度比较快。
  2. Largest Contentful Paint (LCP):这个指标则关注的是最大的页面元素加载完成的时间。对于这篇新闻文章来说,假设最大的元素是一个大图片,那么 LCP 就是这个图片从开始加载到完全显示在用户眼前的时间。如果 LCP 得分低,就说明这个最大的元素加载完成的速度快,用户可以更早地看到页面的主要内容。

TBT

Total Blocking Time,总阻塞时间,是指在 FCP(首次内容绘制)和 TTI(可交互时间)之间,主线程被阻塞的总时间。在这段时间里,主线程被长任务(超过 50 毫秒的任务)占用,导致无法立即响应用户的交互。T

CLS

Cumulative Layout Shift,用于量化网页的视觉稳定性的指标。它帮助衡量在页面加载过程中,视觉元素移动的频率。如果一个页面的元素在加载过程中频繁地移动,那么这个页面的 CLS 得分就会较高,表示这个页面的视觉稳定性较差。

TTI

Time to Interactive,可交互时间,是指页面内容已经足够完整,能够让用户进行主要操作的时间点。换句话说,就是页面开始快速响应用户输入的时间点。TTI 的计算需要考虑两个条件:一是页面的主要内容已经渲染完成,二是网页已经可以快速、可靠地响应用户的交互。

如需根据网页的性能跟踪计算 TTI,请执行以下步骤:

  1. 先进行首次内容绘制 (FCP)。
  2. 沿时间轴正向搜索时长至少为 5 秒的安静窗口,其中,安静窗口的定义为:没有长任务且不超过两个正在处理的网络 GET 请求。
  3. 沿时间轴反向搜索安静窗口之前的最后一个长任务,如果没有找到长任务,则在 FCP 步骤停止执行。
  4. TTI 是安静窗口之前最后一个长任务的结束时间(如果没有找到长任务,则与 FCP 值相同)。
new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
TTI = entry.startTime;
}
}).observe({ entryTypes: ["longtask"] });

FID

First-input-Delay,首次输入延迟时间

new PerformanceObserver((entryList) => {
for (const entry of entryList.getEntries()) {
const delay = entry.processingStart - entry.startTime;
console.log("FID candidate:", delay, entry);
}
}).observe({ type: "first-input", buffered: true });

DCL

DOMContentLoaded,当 HTML 文档被完全加载和解析完成之后,DOMContentLoaded 事件被触发,无需等待样式,图像和子框架的完成加载的时间。

web-vitals

Web Vitals 是 Google 的一项重大举措,旨在为质量信号提供统一的指导.也推出了集成 Web Vitals 的 js 库

  • Largest Contentful Paint (LCP): 衡量加载性能。为了提供一个好的用户体验,LCP 应该在 2.5 秒内。
  • First Input Delay (FID): 衡量可交互性。为了提供一个好的用户体验,FID 应该在 100 毫秒内。
  • Cumulative Layout Shift (CLS): 衡量视觉稳定性。为了提供一个好的用户体验,CLS 应该小于 0.1。
import { getCLS, getFID, getLCP } from "web-vitals";

getCLS(console.log);
getFID(console.log);
getLCP(console.log);

测试工具

开发者工具-performance

前端性能优化——Performance 的使用攻略 - 掘金 (juejin.cn)

开发者工具-livehouse

使用 Lighthouse 分析前端性能 - 知乎 (zhihu.com)

测评网站-pagespeed

PageSpeed Insights (web.dev)

优化

体积:gzip 压缩、懒加载、分包、externals

网络:http2、缓存、cdn

针对不同客户端网络加载不同的资源

获取用户设备或网络信息,并根据这些信息调整资源加载策略或内容展示方式。

可以使用 Client Hints 或 Network Information API 等技术,获取用户设备或网络信息,并根据这些信息调整资源加载策略或内容展示方式。

Client Hints 是一种 HTTP 请求头,可以让服务器根据客户端的设备特性,如屏幕尺寸、分辨率、网络类型等,来优化响应内容。

首先,你需要在服务器端发送一个 Accept-CH 响应头,来指定你想要接收的客户端提示。例如:

Accept-CH: DPR, Width, Viewport-Width

或者,你也可以在 HTML 中使用一个 元素来启用 Client Hints。例如:

<meta http-equiv="Accept-CH" content="DPR, Width, Viewport-Width">

这样,当客户端发起子资源请求时,就会携带相应的请求头,如 DPR (设备像素比)、Width (图片宽度)、Viewport-Width (视口宽度) 等。例如:

Accept: image/webp,image/*,*/*;q=0.8` `DPR: 2` `Width: 320` `Viewport-Width: 640

然后,你可以根据这些信息来优化你的响应内容,比如根据设备像素比和图片宽度来选择合适的图片格式和尺寸。例如:

Content-Type: image/webp` `Content-Length: 12345` `Vary: DPR, Width

注意,你应该在响应头中使用 Vary 字段来指定影响资源选择的客户端提示,以便正确地缓存响应内容。

Network Information API 是一种 Web API,可以让应用程序获取到系统的网络连接信息,比如说连接方式是 WiFi 还是蜂窝。应用程序可以根据此信息为用户展现不同清晰度的内容。

首先,你需要获取一个 NetworkInformation 对象,它是 Navigator 接口上的一个属性。例如:

var connection =
navigator.connection || navigator.mozConnection || navigator.webkitConnection;

然后,你可以通过这个对象的一些属性来获取用户的网络连接信息,如 type (连接类型)、downlink (下行速度)、effectiveType (有效类型) 等。例如:

js复制代码var type = connection.type; // wifi, cellular, etc.
var downlink = connection.downlink; // in megabits per second
var effectiveType = connection.effectiveType; // slow-2g, 2g, 3g, 4g

你也可以给这个对象添加一个 change 事件监听器,来检测用户的网络连接状态变化。例如:

js复制代码function updateConnectionStatus() {
console.log("设备的网络连接从" + type + "变成了" + connection.type);
type = connection.type;
}

connection.addEventListener('change', updateConnectionStatus);

你可以根据这些信息来优化你的应用程序逻辑,比如根据下行速度或有效类型来选择合适的资源加载策略或内容展示方式。

参考

来来来,前端性能监控,带你拿到正确的性能指标 - 掘金 (juejin.cn)

使用 chatGPT,设计一套可落地的前端性能优化方案(上) - 掘金 (juejin.cn)