Skip to content

13 性能优化

约 3173 字大约 11 分钟

2025-09-07

  • DCL(DOM Content Loaded):dom解析完毕,不包括css、图像等资源的加载 监听:
document.addeventListener('DOMContentLoaded', function() {}, false)

计算方式:

const dclTime = performance.timing.domContentLoadedEventEnd - performance.timing.domContentLoadedEventStart
  • load (Onload Event):页面中依赖的所有资源加载完的事件 监听:
window.onload

计算方式

const loadTime = performance.timing.loadEventEnd - performance.timing.loadEventStart;
  • FP (First Paint):渲染出第一个像素点。(一般在html解析完成或者解析一部分的时候触发)
const fp = performance.getEntriesByType('paint').filter(entry => entry.name == 'first-paint')[0].startTime;
  • FCP(First Contentful Paint):渲染出第一个内容 计算方式
const fcp = performance.getEntriesByType('paint').filter(entry => entry.name == 'first-contentful-paint')[0].startTime;
  • FMP(First Meaningful Paint):首次渲染出有意义的内容的事件。没有标准的定义 由于没有标准的定义,需要自己使用MutationObserver计算
  • LCP(Largest Contentful Paint):最大内容渲染时间 计算方式
new PerformanceObserver((entryList) => {
  for (const entry of entryList.getEntries()) {
    console.log('LCP candidate:', entry.startTime, entry);
  }
}).observe({type: 'largest-contentful-paint', buffered: true});
  • 白屏时间:地址栏输入网址后回车 - 浏览器出现第一个元素 白屏结束时间 = FP事件触发时间
  • 首屏时间:地址栏输入网址后回车 - 浏览器第一屏渲染完成 首屏结束时间 = FCP事件触发时间 或 FMP、LCP

可交互时间

  • TTI(Time to Interactive):首次可交互时间。计算较为复杂,需要满足以下条件:
    1. 从 FCP 指标后开始计算;
    2. 持续 5 秒内无长任务(执行时间超过 50 ms)且无两个以上正在进行中的 GET 请求;
    3. 往前回溯至 5 秒前的最后一个长任务结束的时间。 计算方式
const timeToInteractive = performance.timing.domInteractive - performance.timing.fetchStart
  • FID(First Input Delay):首次输入延迟时间,记录在FCP和TTI之间,用户首次与页面交互时响应的延迟 计算方式
const observer = new PerformanceObserver((list) => {
  for (const entry of list.getEntries()) {
    console.log('fid', entry.processingStart - entry.startTime);
  }
});
observer.observe({type: 'first-input', buffer: true});

稳定性指标

  • CLS(Cumulative Layout Shift):在页面的整个生命周期中发生的每一次意外布局变化的最大布局变化得分,得分越小页面越稳定 不稳定元素:一个非用户操作但发生较大偏移的可见元素称为不稳定元素。 布局变化得分:元素从原始位置偏移到当前位置影响的页面比例 * 元素偏移距离比例 计算方式
import {getCLS} from 'web-vitals';
getCLS(console.log);

流畅性指标

FPS

  • Chrome DevTool 中有一栏 Rendering 中包含 FPS 指标,但目前浏览器标准中暂时没有提供相应 API ,只能手动实现。这里需要借助 requestAnimationFrame 方法模拟实现,浏览器会在下一次重绘之前执行 rAF 的回调,因此可以通过计算每秒内 rAF 的执行次数来计算当前页面的 FPS。
  • FPS过低会让用户感觉卡顿,因此这个计算可以用来监控页面卡顿情况。

什么情况会造成操作卡顿和渲染慢

  1. 一次性操作大量dom
  2. 进行复杂度很高的运算

vue的优化

  1. 正确使用v-show和v-if
  2. v-for使用合理的key值
  3. keep-alive缓存
  4. 区分请求粒度,减少请求范围
  5. 不变数据、定期失效可以缓存再cookies或者localstorage中
  6. 对于要页面要缓存的数据存于内存中(全局对象、pinia),能够保证更新、

亮点难点

移动端

  1. 多屏幕适配rem,px转vw,媒体查询,js解决
  2. 移动端、c端项目通常具有非常高的还原度要求
  3. uniapp条件编译
  4. 使用兼容性高的api

B端

文件上传

  1. 大文件上传、断点续传
  2. webworker计算
  3. excel、pdf、word预览、导出,在线excel
  4. 富文本编辑器

可视化

  1. 难度比较高的图表,如:高度定制的地图、流程图、关系图
  2. 按照公司ui设计自定义图表风格和样式,形成公司风格的ui组件库
  3. 3d可视化绘图

其他

  1. 即时通讯webscoket,在线协作,消息提醒
  2. webrtc
  3. webgl
  4. canvas功能
  5. 图片滤镜,剪裁,压缩

工作亮点

  1. 规范定制,并落实到工程化工具中
  2. 组件封装

行为埋点

埋点

埋点主要用于收集用户行为数据。通过在前端代码中插入代码或脚本的方式来实现埋点功能。 埋点的主要作用就是:捕获特定用户行为(如点击、浏览、提交表单、页面跳转等)以及关键业务数据(如下单金额、商品类别等)

埋点的实现方案大致可以分为以下三大类:

  • 手动埋点:在代码中手动加入记录代码来捕获特定事件。
  • 自动埋点:利用DOM事件代理等技术来捕获页面上所有事件,从而减少手动配置。
  • 可视化埋点:通过工具界面标记需要采集的元素和事件,可以不用手写代码。

手动埋点

手动埋点就是在用户操作的行为添加监听,并将该操作记录,比如:按钮点击埋点就是监听目标按钮的点击事件然后将这些信息记录下来

监控

监控主要关注系统的性能和稳定性。在日常开发中,我们会通过采集页面加载时间、资源请求、错误日志等数据的方式来实现前端监控。 监控的主要作用就是:及时发现并定位页面性能瓶颈或代码异常,目的是为了保障系统不出bug

监控一般需要完成以下三大部分:

  • 性能监控:如:首屏加载时间、页面交互耗时、资源加载耗时等。
  • 错误监控:捕获JEvaScript错误、网络请求失败、资源加载异常等。
  • 用户体验监控:收集白屏、卡顿等影响用户体验的问题等。

区别

维度埋点监控
目标捕获用户行为数据监控系统性能、错误、稳定性
数据类型用户点击、表单提交,页面跳转等页面加载事件、错误日志、卡顿情况等
实现方式手动埋点、自动埋点、可视化埋点错误捕获、性能指标采集
核心关注点用户行为、业务数据系统bug、性能优化

性能监控

如果要监控某个操作的性能可以该操作开始前与结束后分别记录要监控的性能指标,比如:首屏渲染耗时,就需要在加载前记录一下当前的时间戳,等加载完成后再记录下时间戳,用加载完成后的时间减去开始时的时间就能得到耗时 性能监控一般使用preference全局对象中提供的函数等接口获取数据的

错误监控

监听js-error事件并根据不同的错误类型进行判断来监控

上报

对于用户行为的埋点和错误监控,一般都是需要上报的,上报给服务器服务器接收后进行处理将数据展示出来以便分析并优化程序

上报也分为统一上报和实时上报

ECharts性能优化

1. 分段加载

使用dataZoom配置只显示一部分区域内的数据

优点 可以很好的解决 ECharts 首次进行大数据量渲染造成的卡顿体验问题,不需要额外的数据处理,只需要通过简单的配置 dataZoom 缩放组件就可以实现

缺点

  • 无法进行全局概览数据,只能分段查看数据
  • 可能需要根据数据量动态的配置属性值,start、end、minSpan 和 maxSpan

2. 降采样

通过sampling属性配置过滤数据点的策略,被过滤的数据将不会被渲染 可选值有以下几种:

  • lttb: 采用 Largest-Triangle-Three-Bucket 算法,可以最大程度保证采样后线条的趋势,形状和极值。
  • average: 取过滤点的平均值
  • min: 取过滤点的最小值
  • max: 取过滤点的最大值
  • minmax: 取过滤点绝对值的最大极值 (从 v5.5.0 开始支持)
  • sum: 取过滤点的和

优点

  • 使用简单,ECharts 内部降采样算法,效果显著
  • 可以完整的将曲线趋势展示出来,和原曲线基本一致

缺点

  • 并不是展示的所有点,会删除一些无用的点,保证渲染性能
  • 最大程度保证采样后线条的趋势,形状和极值,但是某些情况下,极值有偏差

3. 使用后台线程进行数据处理

使用webworker在后台处理数据,不阻塞主线程

4. 过滤不必要的数据

5. 使用canvas

相比于svg,canvas没有真实dom也没有事件,所以在没有复杂事件需求的图表中使用canvas可以提升性能

6. 使用appendData异步分片加载数据

appendData是v5版本提出的,只有少部分图表支持,它可以分片异步加载数据和增量渲染 允许向指定的系列(series)追加数据,而无需重新设置整个 option

echartsInstance. appendData
(opts: {
    // 要增加数据的系列序号。
    seriesIndex?: string,
    // 增加的数据。
    data?: Array|TypedArray
}) => string

示例:

myChart.appendData({
    seriesIndex: 0, // 指定要追加数据的系列索引
    data: [
        // 新的数据块
        [timestamp1, value1],
        [timestamp2, value2],
        // ...
    ]
});

vue中的性能优化

  • 数据层级不宜过深,合理设置响应式数据
  • 通过Object.freeze()方法冻结属性
  • 使用数据时缓存值的结果,不频繁取值。
  • 合理设置Key属性
  • v-show和v-if的选取
  • 控制组件粒度->Vue采用组件级更新
  • 采用函数式组件->函数式组件开销低
  • 采用异步组件->借助webpack分包的能力
  • 使用keep-alive缓存组件v-once
  • 分页、虚拟滚动、时间分片等策略..
  • 对于只有内容且变化较少的内容手动判断而不使用v-if
	<div>{{ isShow ? '显示' : '隐藏' }}</div>
	<div v-if='isShow'>显示</div>
	<div v-else>隐藏</div>

手动控制的话只会更改其中的内容,如果使用v-if整个dom都会重新创建

时间分片

对于大量数据渲染的时候,JS运算往往不是性能的瓶颈,性能的瓶颈主要在于渲染阶段

时间分片的核心思想就是将计算量大的任务分批执行

  1. settimeout
  2. promise
  3. requestAnimationFrame
  4. DocumentFragment

内存泄漏

程序中一个变量已经没用了,而垃圾回收机制认为这段内存还在使用,从而无法释放。(程序对一段内存失去控制权)

首屏加载优化

一、网络层优化

  1. 启动http2/3:多路复用请求,资源并行下载
  2. gzip/brotli压缩:代码文件压缩减小体积
  3. cdn分发:从最近的节点拿资源
  4. 合并请求:数据小的请求合并到业务相同的请求中
  5. dns预解析、预连接

二、资源优化

  1. 代码分块:第三方库和业务代码剥离可以缓存库代码
  2. 图片优化:雪碧图、小图使用base64编码,图标使用svg可以合并请求内嵌到代码中减少请求
  3. 树摇优化:删除未使用的代码、切换新版本的支持树摇优化的库、简单的工具自己实现
  4. 图片懒加载、预加载

三、渲染层

  1. ssr/ssg:服务端生成页面可以让用户马上看到页面
  2. 骨架屏、占位符、进度条、加载动画
  3. 关键路径优化:只加载首屏关键资源,其他的延迟加载

四、js执行优化

  1. 异步或延迟加载脚本
  2. 拆分首屏逻辑,非首屏逻辑延迟加载

五、缓存策略

  1. 合适的缓存策略优化非首次加载

大数据量优化

一、渲染层

  1. 虚拟列表
  2. 分片渲染
  3. 避免重复渲染:选择合理的key、使用v-memo

二、计算层

  1. webworker多线程计算
  2. 数据懒加载、分页加载
  3. 缓存

三、绘制层

  1. 分层
  2. 降采样

四、传输层

  1. 压缩数据传输
  2. 只传输必要字段
  3. 结构化数据:扁平化大对象、减少深层响应式代理

五、vue优化

  1. shallowRef、shallowReactive
  2. 使用watchEffect、computed代理深层代理

贡献者

PinkDopeyBug

公告

本博客内容原本使用obsidian编写,由于没有仔细配置,以至图片引用使用obsidian风格。

且图片存储路径频繁变更导致部分文章图片无法正常显示。

为您带来不便请谅解。

ps:贡献者一直都只有wynnsimon一人,显示Pink的贡献者是因为我没好好配置git。后面因为懒就没一个个修改。如果被提及的人不希望被显示可以联系我我会立即删除。