1. navigationStart

前一个文档卸载时间戳 => 无上一层页面时,从 fetchStart 开始

  1. unloadEventStart / unloadEventEnd

与前一个网页 unload 时的时间戳 => 无上一个网页,默认 0
执行完毕的时间戳

  1. redirectStart / redirectEnd

通常用来计算 redirect 完成的时间

  1. worker

worker 初始化时间

  1. fetchStart

整个全新开始页面的 start
浏览器网络层 ready 的时间

  1. domainInLookupStart / domainInLookupEnd

DNS 连接的开始结束

  1. connectStart / connectEnd

TCP 连接的开始结束

  • connectEnd - domainInLookupStart 为网络的建立连接过程
  • secureConnectionStart: 若使用 https 会有此过程,建立安全链接
  1. requestStart / responseEnd

整个请求的发起开始到完成接收耗时

Processing

纯前端可做的性能优化,主要集中在 dom 渲染过程中

  1. domLoading

开始解析渲染 dom 树的事件 => readyStateChange

  1. domInterActive

完成了 dom 树的解析 => readyStateChange => 只是完成了 dom 树的解析,并没有开始加载网页资源 (async/defer)

  • domInterActive - domLoading 为解析 dom 树的时间
  1. domContentLoaddedEventStart / domContentLoaddedEventEnd

Dom 树解析完成后,资源的加载

  1. domComplete

Dom 树 ready

  • domComplete - domLoading 为整个 dom 树初始化所需要的时间
  1. loadEventStart / loadEventEnd

执行脚本开始与结束

1
2
3
4
5
6
7
8
9
<script>
javascript:(() => {
const performance = window.performance.timing;
const pageLoadTime = performance.loadEventEnd - performance.navigationStart;

// 打印、上报、存储
console.log('当前页面加载耗时: ', pageLoadTime, 'ms');
})()
</script>

W3C Paint Timing

新时代下的指标

  1. DCL(DOMContentLoad): HTML 加载完成时间
  2. L(onLoad): 页面所有资源加载完成时间

performance.getEntriesByType(‘paint’) 获取以下两个时间点的值

  1. FP(First Paint): 页面在导航后首次呈现出不同于导航前内容的时间点
  2. FCP(Fisrt Contentful Paint): 首次绘制任何文本、图像、非空白 canvas 或 svg 的时间点
  3. FMP(First Meaningful Paint): 首次绘制页面“主要内容”的时间点
  4. LCP(Largest Contentful Paint): 可视区域“内容”最大的可见元素开始出现在页面上的时间点
  5. CLS(Cumulative Layout Shift): 表示用户经历的意外 layout 的频率
  6. TBT(Total Blocking Time): 表示从 FCP 到 TTI(可交互时间 Time to Interactive) 之间所有 long task 的阻塞时间之和

Core Web Vitals

CWV 网页核心性能指标
加载、交互、视觉稳定 —— 代表不同方向的衡量标准 + 可衡量且接近真是体验的数据化参数

Largest Contentful Paint(LCP)

衡量装载的性能
页面加载前 2.5s 内,必须要进行最大内容的渲染

  1. 什么是最大内容
  • 图片 <image><img><svg>
  • 视频 <video>
  • 通过 url 加载内容的模块 (例如 background 为 url)
  • 主要文本模块以及其内联模块
  1. LCP 值低下的原因及解决方案
  • 资源慢: 使用缓存 => 强缓存 + 协商缓存
  • 渲染被阻断: 简化结构与逻辑 + 内联、合并方式简化整体解析逻辑
  • 资源交互: 图片分类 => 1. 图片上传 2. 云资源管理
  • 静态资源: CDN

First Input Delay(FID)

衡量交互体验
页面首次输入延迟小于 100ms

  1. 减少 JS 的执行时间
  • 优化算法
  • 尽量减少 JS 的占用: 空间 + 时间
  • 首屏加载: 1. 服务端渲染 2. 预加载、懒加载
  1. 解决长任务问题
  • 阻塞渲染 50ms 以上: 尽量拆分,或者交互弥补
  1. 提升性能来帮助提速
  • JS workers: Web worker | service worker | worklet
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/* main.js */
// 新增一个worker
const worker = new Worker("worker.js");

// main thread 通信
worker.postMessage("Come on & work~");
worker.onmessage = function (e) {
console.log(e.data);
};

/* worker.js */
self.onmessage = function (e) {
console.log(e.data);
// 回调主人喊话 => 业务逻辑

// 完成后发给main
self.postMessage(/* 业务逻辑 */);
};
1
2
3
4
5
6
7
8
9
10
11
12
// service worker - 网络 + 内存
/* main.js */
navigator.seviceWorker.register('service-worker.js');

/* service-worker.js */
self.addEventLister("install", function() {});
self.addEventLister("activate", function() {});
self.addEventLister("fetch", function(e) {
e.respondwith(
caches.match(event.request);
)
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 浏览器概念流程:  JS => style => layout => paint => composite

/* main.js */
CSS.paintWorklet.addModule('worklet.js');

/* worklet.js */
registerPaint('myGradient', class {
paint(ctx, size, prop) {
var gradient = ctx.createLinearGradient(0, 0, 0, size.height - 5);

gradient.addColorStop(0, "black");
gradient.addColorStop(1, "white");

ctx.fillStyle = gradient;
ctx.fillRect(0, 0, size.width, size.height);
}
})

/* app */
.content {
background-image: paint(myGradient);
}

Cumulative Layout Shift(CLS)

衡量视觉稳定性
页面要保持 CLS 小于 0.1: 可见元素从前一帧到后一帧改变位置的动作

  1. 不使用无尺寸元素

srcset & sizes: 根据屏幕自适应,多用于移动端,效果如 web 端的 media

1
2
3
4
<img
srcset="width-320w.jpg 320w, width-480w.jpg 480w, width-800w.jpg 800w"
sizes="(max-width: 320px) 300px, (max-width: 480px) 440px, 800px"
/>
  1. 减少内容内部的插入

影响到整体的布局

  1. 字体控制

CWV 工具

Core Web Vitals Annotations

性能评估指标 - performance

页面性能评估参考指标:

  1. FPS、CPU、网络请求
  2. 网络任务队列
  3. JS 消耗时间、性能占用
  4. 浏览器绘制页面的帧布局

工程化监控体系

  1. 上报 => 信息采集 => 数据回收 => 获取场景数据
  2. 数据分析 => timing 节点计算 => 阈值设置 + 数据分类 + 数据重组
  3. 可视化展示

    一些埋点工具流程参考: growing.io (采集上报) => fineBi / powerBi (数据分析) => grafana (数据展示看板) => 钉钉、企业微信、飞书 webhook (监控信息告警)

性能优化的另一种可能: bigpipe —— 页面分解成若干的 pagelet

  1. 服务前端接收客户端请求
  2. node 生成 HTML * n => 若干个 pagelet
  3. 浏览器获取到 pagelet 后开始加载资源做 layout paint
  4. 客户端整合形成页面

MVVM 优化

  1. 自身特性优化
    vue - template
    react - dom diff
  2. 技巧上的优化
1
2
3
4
5
6
7
8
9
10
11
12
13
14
setNewState = e => {
this.setSate({
/* */
})
}
render() {
return (
// 每点击一次新生成一个方法示例
<div onClick={e => { this.setState(/* ^ */) }}></div>

// 优化
<div onClick={this.setNewState}></div>
)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
	// computed代替watch
computed: {
preData(data) {
return '123' + data;
}
},

data: function() {
return {
preData: '123'
}
}
watch: {
preData(data) {
this.preData = '123' + data;
}
}

首屏方案选择

几个方案的实现都依赖于 MutationObserver API 用于监听 DOM 变动

  • 在页面加载中 初始化 MutationObserver, 当每一次页面发生变化时,会去判断页面是不是一次有意义的变化,例如变化的标签是不是一些有意义需要统计的标签,例如 meta link script 这类标签就认为这一次变动是无意义的,无需后面的步骤
  • 然后会记录下发生变化的时间,以及通过 setTag 给此次变化的 DOM 打上标记
  • 一个简单的递归 DOM 树,为每一个在屏幕范围内具有大小的可见元素进行标记,一旦发现父级不满足要求就不会向下递归

  1. 首屏内加载最慢的图片: 监听首屏内所有图片的 onload 事件,获取图片 onload 时间的最大值,即可获得近似的首屏时间。
  2. 页面的 DOM 树第一次稳定: 这个方案想要比较好的实现比较有难度,并且精确度值得商榷。
  3. 不够规范
  4. 最大变化: 在之前只通过大小一个维度来描述首屏的基础上,又衍生出了,计算最大的一次变化,通过层级、标签权重、元素面积等因素,通过计分的方式,记录下最大的一次变化。

首屏优化手段

  1. 路由懒加载
  2. 非首屏使用异步组件
  3. 首屏不要使用组件延迟加载
  4. 静态资源 CDN
  5. 避免不必要的重定向
  6. 减少页面唯一域名,从而减少 DNS 查询次数/使用 dns prefetch 做预解析
  7. 减小首屏上 JS、CSS 等资源文件大小
  8. 服务端渲染
  9. 减少 DOM 的数量和层级
  10. 精灵图请求
  11. loading 效果
  12. gzip
  13. 图片懒加载
  14. 组件按帧率来分别加载: 结合 requestAnimationFrame 使用

requestAnimationFrame、setTimeout、setInterval

工作原理

  1. setTimeout: 这个函数会将要执行的代码或函数放入事件循环队列中,等待当前代码执行完毕后,再等待指定的时间后执行一次。如果设置了定时器,那么每隔一定时间就会执行一次代码,直到 clearTimeout 被调用或窗口被关闭。
  2. setInterval: 与 setTimeout 类似,setInterval 也会将要执行的代码或函数放入事件循环队列中,但它在指定的时间间隔后会一直重复执行,直到 clearInterval 被调用或窗口被关闭。也就是说,setInterval 会不断地调用函数,直到被取消。
  3. requestAnimationFrame: 这个函数的工作原理与 setInterval 和 setTimeout 略有不同。它会将回调函数加入到浏览器下一次重绘之前要执行的队列中。这样做的目的是为了确保动画的流畅度,因为浏览器会自动优化这个 API,只在浏览器处于激活状态并且页面处于可见状态时才会执行回调函数。此外,requestAnimationFrame 会根据系统的刷新率来自动匹配时间间隔,从而确保每帧动画的间隔时间尽可能地准确。

区别

  • 执行时机: requestAnimationFrame 是由浏览器提供的 API,它会在浏览器下一次重绘之前执行回调函数。这意味着它能够确保动画的流畅度,并且能够自动匹配系统的刷新率。相比之下,setInterval 和 setTimeout 会在指定的时间间隔后执行回调函数,无论浏览器是否处于激活状态或正在进行其他操作。
  • 性能优化: requestAnimationFrame 由浏览器自动优化,只在浏览器处于激活状态并且页面处于可见状态时才会执行回调函数。这可以节省 CPU、GPU 和内存的使用,特别是在移动设备上。相比之下,setInterval 和 setTimeout 不会自动优化,如果页面处于隐藏或不可见状态,它们会继续执行回调函数,这可能会导致资源的浪费。
  • 回调函数执行时间: requestAnimationFrame 的回调函数会在浏览器下一次重绘之前执行,因此它能够确保回调函数的执行时间相对准确。相比之下,setInterval 和 setTimeout 的回调函数执行时间取决于浏览器事件循环中的队列和执行时间,因此可能会有一定的延迟
  • 停止操作: requestAnimationFrame 的回调函数只会在浏览器下一次重绘之前执行一次,因此可以通过清除队列中的回调函数来停止操作。相比之下,setInterval 和 setTimeout 会不断地执行回调函数,直到 clearInterval 或 clearTimeout 被调用或关闭页面为止
  • 函数节流: 在高频率事件(resize,scroll 等)中,为了防止在一个刷新间隔内发生多次函数执行,使用 requestAnimationFrame 可保证每个刷新间隔内,函数只被执行一次,这样既能保证流畅性,也能更好的节省函数执行的开销

应用场景

  • setTimeout: 可用于在网页加载后延迟执行某些操作,例如加载页面内容、初始化组件等。也可用于定时触发某些操作,例如定时发送数据、定时检查任务等。
  • setInterval: 常用于需要周期性执行的操作,例如定时更新数据、定时触发事件等。在 web 端,如果列表需要定时更新,可以使用 setInterval 来定时获取列表的请求。另外,如果需要在某一特定情况下清除定时任务,可以使用 clearInterval 来停止定时器。
  • requestAnimationFrame: 主要用于实现流畅的动画效果。它会在浏览器下一次重绘之前执行指定的函数,避免了频繁的重绘导致的性能问题。requestAnimationFrame 会自动匹配系统的刷新率,从而确保每帧动画的间隔时间尽可能地准确。在需要反复触发的情况下,使用 requestAnimationFrame 可以避免连续调用导致的相互干扰。