Skip to content

判定性能指标

RAIL

Google Chrome 在 2015 年出的标准

  • Response ( 响应): 100ms 响应用户的操作
  • Animaton: 动画应该在 16ms 内完成
  • Idle: JS 任务尽量在空闲线程 中完成 50ms
  • Load:页面在 5s 内加载完成

基于用户体验的指标

  1. FP (First Paint )白屏

    从页面开始加载到浏览器中检测到渲染(任何渲染)时被触发(例如背景改变,样式应用等)

  2. FCP ( first content paint) :第一个内容出现

  3. LCP (largest content paint)

    LCP 指标代表的是视窗最大内容区可见(图片或者文本块)的所需渲染时间

  4. FID ( first input delay) 首次输入延迟

    从用户第一次与页面交互(点击,输入,按键)到浏览器实际能够开始处理事件的时间

  5. TTI (time to interactive): 完全达到可交互阶段

    从页面开始到它的主要子资源加载到能够快速地响应用户输入的时间。(没有耗时长任务)

  6. CLS ( cumulative layout shift) : 累计内容偏移,视觉稳定性

    是所有布局偏移分数的汇总,凡是在页面完整生命周期内预料之外的布局偏移都包括。布局偏移发生在任意时间,当一个可见元素改变了它的位置,从一个渲染帧到下一个

Web Vitals

Google 在 2020 年 05 月 05 号 ,提出的新的标准, 只关注 3 个点:

  • LCP : 加载性能
  • FID : 交互时间
  • CLS: 交互稳定性

性能测试工具

lighthouse

chrome 自带的

webPageTest

官网地址

前端性能测试--Performance API

W3C processing-model

js
// 页面加载完成后执行
window.addEventListener('load', function () {
  // 获取性能数据
  var perfData = window.performance.getEntriesByType('paint')

  // 提取 FP、FCP、LCP 数据
  var fp = perfData.find(entry => entry.name === 'first-paint').startTime
  var fcp = perfData.find(entry => entry.name === 'first-contentful-paint').startTime
  var lcp = perfData.find(entry => entry.name === 'largest-contentful-paint').startTime

  // 打印 FP、FCP、LCP 数据
  console.log('First Paint (FP): ' + fp + ' ms')
  console.log('First Contentful Paint (FCP): ' + fcp + ' ms')
  console.log('Largest Contentful Paint (LCP): ' + lcp + ' ms')

  // 获取 FID 数据
  var fidData = window.performance.getEntriesByType('first-input')
  var fid = fidData.length > 0 ? fidData[0].processingStart - fidData[0].startTime : 'N/A'

  // 打印 FID 数据
  console.log('First Input Delay (FID): ' + fid + ' ms')

  // 获取 CLS 数据
  var clsData = window.performance.getEntriesByType('layout-shift')
  var cls = 0
  if (clsData.length > 0) {
    cls = clsData.reduce((acc, entry) => acc + entry.value, 0)
  }

  // 打印 CLS 数据
  console.log('Cumulative Layout Shift (CLS): ' + cls)
})

Puppeteer – 性能评估

js
const puppeteer = require('puppeteer')

const start = async () => {
  const browser = await puppeteer.launch()
  const page = await browser.newPage()

  await page.goto('https://jiatingyu.top')
  await page.waitForSelector('title')

  // 在页面上下文中执行导航API
  const metrics = await page.evaluate(() => JSON.stringify(window.performance))

  // 将结果解析为JSON
  console.info(JSON.parse(metrics))

  await browser.close()
}

start()

从 url 渲染出页面经历些什么过程

  1. DNS 查询:浏览器首先会执行 DNS 查询,将 URL 解析为对应的 IP 地址。如果 DNS 缓存中有相应的记录,可以直接使用缓存,否则需要进行 DNS 查询。

  2. 建立 TCP 连接:一旦浏览器获得了服务器的 IP 地址,它就会尝试与服务器建立 TCP 连接。这是一个三次握手的过程,用于确保浏览器和服务器之间的可靠通信。

  3. 发起 HTTP 请求:浏览器通过已建立的 TCP 连接向服务器发起 HTTP 请求,请求页面及其相关资源,如 HTML 文件、CSS 文件、JavaScript 文件、图像等。

  4. 服务器响应:服务器收到浏览器的请求后,会进行相应的处理并返回响应。响应通常包括状态码、响应头和响应体。在这个阶段,服务器可能会进行一些处理,如动态生成页面内容。

  5. 接收响应:浏览器接收到服务器的响应后,开始接收响应体的内容。对于 HTML 文件,浏览器会逐步解析内容并开始构建 DOM 树。

  6. 解析 HTML:浏览器解析 HTML 文件,构建 DOM 树。同时,浏览器会识别文档中的 CSS 和 JavaScript 资源,并发起进一步的请求来获取这些资源。

  7. 渲染页面:随着 DOM 树的构建,浏览器开始将页面内容呈现给用户。这个过程包括样式计算、布局和绘制,最终形成用户可见的页面。

  8. 解析和执行 JavaScript:如果 HTML 文件中包含 JavaScript 代码,浏览器会在解析 HTML 的同时解析和执行 JavaScript。JavaScript 的执行可能会修改 DOM 树和样式信息,进而触发重新渲染。

  9. 加载并执行其他资源:页面中可能包含外部的 CSS 文件、JavaScript 文件、图像等资源。浏览器会根据需要逐一加载这些资源,并在加载完成后执行相应的操作,如样式应用、脚本执行等。

  10. 页面完成加载:当所有资源都加载完成,页面呈现完毕后,浏览器会触发 load 事件,表示页面加载完成。

优化方向

请求响应优化

  1. DNS 解析

a. 增减域名(减少域名查询,分配适量域名) b. DNS 预取

html
<link href="https://gw.alipayobjects.com" rel="dns-prefetch" />
  1. CDN 缓存 (域名设置)
  • CDN 也是具有存储功能的,而且就近返回,所以快
  • 资源域名与主站域名不一致(js\css\img...)
    • 不必要的 cookie 携带(cookie 携带,需要同源)
    • 浏览器同一域名下的请求并发限制
  1. 长链接

建立 http 链接前,需要先进行 TCP/IP 链接,这个时间比较耗时, http1.1 默认开启 a. Connection : keep-alive b. 管道机制: 可以并行发送多个请求,不用等上一个返回后才发送

  1. http2.0

  2. 避免过多的重定向 a. 重定向也叫 URL 转化,状态码是 30x

  3. http 传输资源压缩 a. 头部信息 b. 响应体 gzip 压缩资源(js、css、图片) c. 请求头(h2 本身就有)、请求体(需要后端支持解压,前端再压缩)

  4. HTTP 缓存

    a. 强缓存 b. 弱缓存(协商缓存)

  5. Service Worker 缓存

关键渲染路径的优化:

HTML:

  • 使节点少、深度小、节点大小变小
  • minify、compress、 http cache

CSS :

  • 避免使用 @import 会让请求变成同步
  • 指定css 的用途,不会阻塞渲染

JS:

  • defer (异步、顺序执行,并且要等待 DOMContentLoaded 之前运行)
  • async(异步、加载完执行、常用于第三方库)
  • 防抖节流
  • 定时器防抖(简单的几个按钮)
  • 网络防抖(针对所有资源)

资源加载优化(3 类)

常见资源预取方式

html
<!-- 预加载: -->
<link rel="preload" as="style" href="..." />
<!-- 预提取:(预提取某些资源,这里主要是在当前页面加载完成后,由浏览器发起一些比较Low 的资源请求,为用户的下一步加速 -->
<link rel="prefetch" href=".." />
<!-- 预解析DNS:  -->
<link rel="dns-prefetch" href="..." />
<!-- 预连接: 有些链接在建立链接时比较耗时 -->
<link rel="preconnect" href="https://example.com" />

打包优化

  • 路由懒加载:分包
  • 减小 webpack 中的第三方包的数量和体积

文件资源(图片、视频)

  • 压缩:

    • 图像类型:位图、矢量图、svg、base64
    • 图像格式:png、jpg、jpeg、gif、webp
    • 压缩方式: 有损压缩、无损压缩
  • 延迟加载

    • insertsection observer (监听元素离视口的位置来加载需要的资源)
    • 常规的根据滚动条 scorllTop 的位置,getBoundingClientReact()

其他资源:

浏览器加载资源的优先级是通过自身的启发式算法来的,network 中 priority 属性(LOW、Medium、High、Highest)

浏览器优化

方向

  • 缓存:http 缓存、Storage、ServiceWork

  • 并行:预取、预加载、Worker

  • 更快的执行速度:WebAssembly

Service Worker

作用

  • 缓存静态资源、离线化、预加载
  • 默认使用 caches (cacheStorage)来缓存
  • 拦截浏览器所有请求

限制

  1. 为 JavaScript 创造多线程环境,运行复杂运算、资源,导致页面卡顿
  2. 不能直接操作 DOM、也无法使用 document、window、parent 这些对象。但是,Worker 线程可以 navigator 对象和 location 对象。
  3. 同源限制
  4. 上线需要 HTTPS,本地可以 localhost 来使用
  5. 浏览器进程,可以多页面共享数据,且不会因页面关闭而关闭
  6. navigator 对象下的属性 navigator.serviceWorker
  7. 可以使用 XMLHttpRequest 对象发出 AJAX 请求。
  8. 一旦被安装则永久存活,除非手动卸载;

Worker

comlink

主线程:

  • worker.onerror:指定 error 事件的监听函数。
  • worker.onmessage:指定 message 事件的监听函数,发送过来的数据在 Event.data 属性中。
  • worker.onmessageerror:指定 messageerror 事件的监听函数。发送的数据无法序列化成字符串时,会触发这个事件。
  • worker.postMessage():向 Worker 线程发送消息。
  • worker.terminate():立即终止 Worker 线程。

worker 线程:

  • self.name: Worker 的名字。该属性只读,由构造函数指定。
  • self.onmessage:指定 message 事件的监听函数。
  • self.onmessageerror:指定 messageerror 事件的监听函数。发送的数据无法序列化成字符串时,会触发这个事件。
  • self.close():关闭 Worker 线程。
  • self.postMessage():向产生这个 Worker 线程发送消息。
  • self.importScripts():加载 JS 脚本。
js
// 主线程
var myWorker = new Worker('worker.js', { name: 'myWorker' })
var first = document.querySelector('#number1')
var second = document.querySelector('#number2')

first.onchange = function () {
  // 像内部发送数据
  myWorker.postMessage([first.value, second.value])
  console.log('Message posted to worker')
}
// worker.js 线程
self.name // myWorker
let data = fetch('htp://......')
self.postMessage(data)