长任务必然导致输入延迟,因主线程被占满使事件回调无法入栈;performanceobserver是线上实时捕获唯一方式,需早注册、类型为'longtask'、妥善处理空attribution。

长任务直接导致输入延迟——不是“可能”,而是必然。主线程被占满时,input、click、scroll 事件回调根本进不了调用栈,用户操作就悬在那儿,直到任务结束。
用 PerformanceObserver 捕获运行中的 long task
这是唯一能在线上环境实时感知长任务的方式,但必须早注册、写对类型、处理空 attribution。
-
PerformanceObserver必须在里或document.write前执行,否则首屏关键 long task 会漏掉 - 监听类型是
'longtask',拼错成'long-task'或'longTask'都不会触发回调 -
entry.attribution在跨域 iframe、动态import()、或 script 标签没带crossorigin时为空,此时只能靠entry.name(如'self'、'unknown')粗略定位来源 - 示例中只筛
duration >= 1000是合理起点:≥50ms 是 API 上报阈值,但 ≥1000ms 才真正对应用户可感知的卡顿(比如按住空格键不放,光标停顿超 1 秒)
为什么 getEntriesByType('longtask') 查不到刚发生的卡顿
它只返回已结束且未被浏览器丢弃的条目,不是实时流。页面刚卡住时,long task 还在跑,performance.getEntriesByType('longtask') 返回空数组完全正常。
- 这个方法适合 DevTools 里手动调试:等卡顿发生后,切到 Console 粘贴执行,看历史积压了哪些任务
- 它不保证返回全部——浏览器内存策略会主动丢弃旧条目,尤其在低端设备上
-
startTime升序排列,但中间可能有断层;别假设连续,也别用它做线上埋点 - 如果想验证某段代码是否触发 long task,得先让它执行完,再立刻调用该方法;放在
setTimeout(() => {}, 0)里反而可能错过 microtask 后立即开始的任务
输入延迟 ≠ long task duration,得结合事件队列看
一个 80ms 的 long task 不一定导致输入延迟,但如果它紧挨着 input 事件回调,而该回调又触发了 DOM 写操作(如 el.style.left = '10px'),那后续的 layout 就会被拖到下一帧甚至更晚。
- 在 Performance 面板录制时,打开
Events轨道,找input或keydown事件,看它的 handler 是否被 long task 排队压在后面 - 注意
Dispatch Mouse Event和Run Microtasks之间的 gap:如果中间插了一个 >50ms 的任务,输入响应就已受损 - 滚动卡顿更隐蔽:
scroll事件本身不耗时,但绑定的回调里调offsetHeight+ 改style组合,会强制同步 layout,极易形成连环 long task - 别只盯着 JS 执行时间——CSSOM 更新、样式计算、布局(Layout)、绘制(Paint)都发生在主线程,同样算进 long task 总耗时
拆分任务时最容易踩的坑
让出控制权不等于解决问题。切片粒度、上下文保存、DOM 引用生命周期,任何一个没处理好,都会让卡顿从“一次长停顿”变成“多次短抖动”。
- 用
setTimeout(fn, 0)拆分,但没控制单次处理量,结果每轮只处理 1 个节点,注册了几千个定时器,反而把任务队列撑爆 - 基于时间片切片时,用
performance.now()但没减去起始时间戳,导致每次判断基准漂移,切片越来越碎 - 在闭包里缓存了
document.querySelectorAll('li')结果,但后续 DOM 变化后,这些 NodeList 仍指向旧节点,逻辑错乱且内存泄漏 - 用
requestIdleCallback时没检查deadline.timeRemaining() > 0,强行执行到超时,失去让出意义;Safari 不支持,又没降级到setTimeout
真正难的不是发现 long task,而是确认它和用户抱怨的“点不动”“输不进”之间那条因果链——这条链往往横跨 JS 执行、样式计算、布局、绘制,还掺着事件调度优先级,得一帧一帧往回推。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











