前端性能优化做到一定阶段,绕不开一个话题:主线程被长任务阻塞。所谓长任务(Long Task),指的是主线程上连续执行超过50毫秒的任务,它会导致输入响应延迟、动画掉帧,用户直观感受就是页面卡一下。要捕捉这类任务,靠console.time这种手工埋点显然不现实,而浏览器原生的Performance Timeline API提供了PerformanceObserver接口,可以被动接收longtask类型的条目。这篇文章就来聊聊怎么在jQuery项目中把它封装成一套可落地的监控方案。

一、先弄懂PerformanceObserver是怎么工作的
PerformanceObserver是Performance Timeline API Level 2引入的观察器,它的工作方式和MutationObserver类似:你先注册一个回调,告诉浏览器你关心哪些类型的性能条目,之后每当有新的条目产生,浏览器就会在合适的时机批量回调你,而不是让你频繁地轮询performance.getEntries()。
对于长任务监控,我们关心的是longtask类型的条目。每个条目包含几个关键字段:name通常是"Task",startTime表示任务开始的时间点(相对页面导航的毫秒数),duration是任务持续时长,attribution数组则标注了任务可能的来源,比如script、layout等。需要特别说明的是,attribution在目前的浏览器实现里能提供的信息比较有限,通常只能告诉你任务归属的大类,想要精确定位到某段代码,还得结合埋点和耗时统计来交叉分析。
还有一个容易被忽视的点:buffered选项。如果在注册观察器时传入{buffered: true},浏览器会把注册之前已经产生、还留在缓冲区里的条目也一并推送给你。对于长任务监控来说这个选项很实用,因为脚本加载本身可能就是页面早期卡顿的元凶,等你的监控代码跑起来时,那些长任务早就结束了,不加buffered就漏采了。
二、用jQuery封装一个监控模块
之所以选择jQuery来做封装,一方面是老项目中jQuery几乎必装,没必要再引一个监控库;另一方面$.ajax、$.extend这些工具函数拿来处理配置和数据上报非常顺手。下面是一个完整的实现,支持配置任务时长阈值、采样率和上报地址。
(function ($) {
'use strict';
// 默认配置
var defaults = {
threshold: 100, // 只记录超过100ms的任务,过滤噪声
sampleRate: 1, // 采样率,1表示全量采集
reportUrl: '/api/perf/longtask',
bufferSize: 20, // 本地缓存多少条后统一上报
onTask: null // 每捕获一个任务的回调,便于调试
};
function LongTaskMonitor(options) {
this.config = $.extend({}, defaults, options);
this.queue = [];
this.observer = null;
this._init();
}
LongTaskMonitor.prototype._init = function () {
var self = this;
// 浏览器不支持时静默降级,不影响业务
if (typeof PerformanceObserver === 'undefined') {
return;
}
this.observer = new PerformanceObserver(function (list) {
var entries = list.getEntries();
for (var i = 0; i < entries.length; i++) {
self._handle(entries[i]);
}
});
this.observer.observe({ entryTypes: ['longtask'] });
};
LongTaskMonitor.prototype._handle = function (entry) {
// 低于阈值或未命中采样则丢弃
if (entry.duration < this.config.threshold) {
return;
}
if (Math.random() > this.config.sampleRate) {
return;
}
var item = {
name: entry.name,
startTime: Math.round(entry.startTime),
duration: Math.round(entry.duration),
attribution: (entry.attribution || []).map(function (a) {
return a.name;
}).join(','),
url: location.pathname,
ts: Date.now()
};
this.queue.push(item);
if ($.isFunction(this.config.onTask)) {
this.config.onTask(item);
}
if (this.queue.length >= this.config.bufferSize) {
this.flush();
}
};
LongTaskMonitor.prototype.flush = function () {
if (!this.queue.length) {
return;
}
var data = this.queue.splice(0, this.queue.length);
$.ajax({
url: this.config.reportUrl,
method: 'POST',
contentType: 'application/json',
data: JSON.stringify(data),
// 监控上报失败不应弹错,静默处理
global: false
});
};
// 挂到jQuery上,页面卸载前把剩余数据冲出去
$.longTaskMonitor = function (options) {
return new LongTaskMonitor(options);
};
$(window).on('beforeunload', function () {
// 这里简单处理,严格场景可用navigator.sendBeacon
});
})(jQuery);
使用起来也很简单,在页面初始化时调用一次即可:
$.longTaskMonitor({
threshold: 150,
reportUrl: '/api/perf/longtask',
bufferSize: 10,
onTask: function (item) {
console.warn('捕获长任务:', item.duration + 'ms', item);
}
});
这里有几个设计细节值得展开。首先是阈值过滤,浏览器判定长任务的门槛是50毫秒,但实践中50到80毫秒的任务非常密集且大多无害,全量记录会稀释真正有问题的数据,所以默认阈值设成了100毫秒,你可以根据自己页面的情况调整。其次是批量上报,每捕获一个任务就发一次请求既浪费带宽也可能反过来制造网络压力,攒够一批再发更稳妥。
三、上报与数据分析的注意事项
监控数据到了服务端才有价值。上报环节建议优先考虑navigator.sendBeacon,它专门为页面卸载期的数据发送设计,不会阻塞 unload 流程,而且jQuery的$.ajax在页面关闭时请求可能被浏览器直接取消。可以在flush方法里做个能力检测,不支持sendBeacon时再降级到$.ajax。
LongTaskMonitor.prototype.flush = function () {
if (!this.queue.length) {
return;
}
var payload = JSON.stringify(this.queue.splice(0, this.queue.length));
if (navigator.sendBeacon) {
navigator.sendBeacon(this.config.reportUrl, new Blob([payload], {
type: 'application/json'
}));
} else {
$.ajax({
url: this.config.reportUrl,
method: 'POST',
data: payload,
global: false
});
}
};
数据分析层面,单看某一次长任务的duration意义不大,更有价值的是聚合视角。常见的做法是按页面路径(URL)分组,统计P95时长的长任务耗时、每页平均出现次数,再结合startTime绘制时间分布,你会很快发现卡顿是集中在首屏加载阶段还是用户交互之后。前者多半和脚本体积、同步初始化有关,后者则要怀疑事件处理函数里的重计算或意外的强制回流。
最后提醒两点生产环境的实践。第一,监控本身也有开销,虽然PerformanceObserver的回调是浏览器调度的、代价很小,但回调里如果做了复杂计算或频繁触发网络请求,就可能自己制造出长任务,所以回调逻辑务必保持轻量,重活留给服务端。第二,采样策略要和流量规模匹配,日PV上百万的站点全量采集既是浪费也不必要,把sampleRate降到0.1甚至0.01,统计结论通常不会有明显偏差,还能省下一大笔日志存储成本。另外longtask条目类型目前Safari还不支持,监控方案要接受这部分流量的数据缺失,不要把它当成页面健康度的唯一指标。
把这套封装接入项目后,配合服务端的聚合报表,主线程卡顿从玄学问题变成了可以量化追踪的指标,后续无论是拆分大脚本、把同步逻辑改成异步,还是给昂贵计算加Web Worker,都有了明确的数据依据。
jQueryPerformance Timeline API长任务监控修改时间:2026-09-04 22:06:43