搜尋文章

页面交互卡顿、掉帧严重怎么排查?从帧预算出发,用 Chrome Performance 定位脚本、布局、绘制与合成瓶颈,再选择任务拆分、虚拟列表和绘制优化,并通过同场景录制验证效果。Vue 面试完整笔记:响应式、组件、路由、Pinia 与渲染机制覆盖《Vue 面试》合集全部 28 集:前言与 27 节专题。逐集整理核心概念、代码思路、面试答法与易错边界,并校正响应式、Pinia、diff 和 nextTick 中的常见误解。Vue 响应式底层原理:Proxy 与视图更新(P6)从手动更新 DOM 出发,理解 Proxy 的 get/set 拦截、与 Vue 2 属性拦截的区别,以及对象、数组和原始对象访问的边界,厘清拦截变化与依赖追踪的关系。前端面试 P081:StrictMode 的检查与版本边界JavaScript 函数传参:值传递与对象引用前端面试 P018:手写柯里化与参数个数边界前端面试 P010:普通函数、箭头函数与 this前端面试 P088:Immer 草稿与结构共享前端面试 P006:对象遍历与属性键的选择前端面试 P009:执行上下文、调用栈与词法作用域前端面试 P094:SSE 分片解码与事件边界前端面试 P097:请求并发池、任务启动与失败释放前端面试 P076:Fiber 的协作调度与不可中断边界前端面试 P013:节流、防抖与取消待执行任务前端面试 P033:Object.create、描述符与共享属性前端面试 P057:Vue Router 配置、历史模式与导航前端面试 P030:构造函数与 new 的返回规则前端面试 P077:key、组件身份与状态重置前端面试 P039:同源策略、CORS 与请求是否发出前端面试 P032:instanceof、原型链与自定义判断前端面试 P047:Proxy 与 Vue 2 属性拦截的区别前端面试 P019:函数组合与同步数据管道前端面试 P004:手写深拷贝、循环引用与实现边界前端面试 P059:导航守卫、顺序与权限边界前端面试 P083:函数式更新、队列与纯度前端面试 P014:call、apply、bind 与模拟实现边界前端面试 P012:高阶函数、柯里化与部分应用前端面试 P041:事件委托、目标匹配与 React 事件前端面试 P048:track、trigger、动态依赖与清理前端面试 P066:自定义指令与 DOM 资源清理前端面试 P075:props 只读、状态归属与派生数据前端面试 P062:Pinia 创建、实例范围与使用前端面试 P073:JSX 编译、React 元素与表达式前端面试 P026:Promise 延迟与顺序任务队列前端面试 P016:纯函数、副作用与参数共享前端面试 P094:流式文本的 SSE 与 WebSocket 取舍前端面试 P064:storeToRefs 与 action 解构纠错前端面试 P044:Composition API 与组合式函数前端面试 P051:副作用清理与过期请求前端面试 P021:事件循环、微任务与渲染机会前端面试 P003:原始值、对象共享与赋值行为前端面试 P098:用户偏好存储、TTL 与命名空间前端面试 P056:插槽作用域与 slot props前端面试 P011:闭包、变量绑定与循环回调前端面试 P027:async 错误处理与 return await前端面试 P036:CSP、nonce 与防护边界前端面试 P007:隐式转换、ToPrimitive 与空数组比较前端面试 P035:继承方式对照与寄生组合式继承前端面试 P025:手写 Promise 的最小状态机前端面试 P079:受控表单与非受控输入前端面试 P072:虚拟 DOM 的价值与性能边界前端面试 P005:数组方法、可变性与回调副作用前端面试 P100:Proxy 沙箱为何不是安全边界前端面试 P040:发布订阅、事件总线与退订前端面试 P053:props、组件事件与 v-model 约定前端面试 P008:相等比较、NaN 与 Object.is前端面试 P065:Pinia 最小变更与对象替换的取舍前端面试 P087:Redux 单向数据流与状态边界前端面试 P002:数据类型与 typeof 的陷阱前端面试 P085:useReducer 与 useState 的实际取舍前端面试 P078:React 合成事件与旧事件池纠错前端面试 P025:Promise 解析过程与实现验收前端面试 P043:Vue 渐进式与声明式 UI前端面试 P091:SWR、TanStack Query 与服务端缓存前端面试 P074:函数组件快照与类实例的输出纠错前端面试 P071:UI 是状态映射与渲染纯度前端面试 P054:provide、inject 与跨层级依赖前端面试 P061:何时引入全局状态管理前端面试 P097:请求去重、缓存键与过期策略前端面试 P086:Context 拆分与订阅更新边界前端面试 P024:Promise 并发组合与空输入边界前端面试 P089:Redux middleware、next 与异步返回值前端面试 P022:Promise 状态、链式调用与错误传播前端面试 P084:状态提升、单一来源与范围选择前端面试 P034:class、super 与语法糖的边界前端面试 P058:路由参数、query 与组件 props前端面试 P020:定时器延迟、轮询重叠与清理前端面试 P029:对象原型、prototype 与属性查找前端面试 P095:AI SDK、编排框架与生成 UI 工具前端面试 P092:Signals、细粒度依赖与 React 集成边界前端面试 P046:Vue 响应式陷阱与 ref 自动解包纠错前端面试 P090:Zustand、Jotai 与状态作用域前端面试 P100:微前端 JS 隔离方案与资源生命周期前端面试课程:学习目录与纠错记录前端面试 P023:async、await、异步恢复与并发前端面试 P068:Vue 2 与 Vue 3 的列表 diff 区别前端面试 P015:IIFE、作用域隔离与现代模块前端面试 P063:Pinia 状态、getter 与 action前端面试 P052:Vue 条件、列表与表单指令前端面试 P050:Vue 生命周期、DOM 时机与 SSR前端面试 P060:路由 meta、合并与界面配置前端面试 P045:ref、reactive、computed 与 watch前端面试 P096:令牌无感刷新、重试与安全边界前端面试 P093:MCP 架构与前端集成场景前端面试 P080:组合、children 与自定义 Hook前端面试 P082:useState 更新请求、快照与批处理前端面试 P067:Vue 模板编译与运行时边界前端面试 P031:手写 new 与 Reflect.construct 的区别前端面试 P069:nextTick 等待 DOM 刷新与绘制的区别前端面试 P055:默认插槽、具名插槽与内容接口前端面试 P038:嵌套定时器的最小延迟与调度选择前端面试 P099:倒计时截止时间、漂移与完成事件前端面试 P019:记忆化缓存与递归调用的纠错前端面试 P049:Vue 与 React 的更新范围和批处理前端面试 P004:浅拷贝、深拷贝与 structuredClone前端面试 P042:Vue 面试导学与学习顺序前端面试 P096:共享刷新 Promise 与并发失败恢复前端面试 P070:React 面试导学与复习主线前端面试 P028:异步输出顺序的逐步推演前端面试 P001:var、let、const 与暂时性死区前端面试 P037:内存泄漏的快照与引用链排查前端面试 P017:手写 bind、重复绑定与构造调用你好,這裡是 Henry 的自留地給想法找一個落腳的地方,也給那些解決過的問題留下一點記錄。
Esc 關閉
全部文章

页面交互卡顿、掉帧严重怎么排查?

目前顯示中文(中國)原文

视频笔记:《前端面试官:页面交互卡顿、掉帧严重怎么排查?》,UP 主「Web前端面试题」,时长约 4 分 16 秒。依据视频画面与内嵌字幕整理;下文另行标明技术校正和实操补充。

核心结论

卡顿排查的重点,是拿到异常交互的性能记录,确定时间消耗在什么阶段,找到具体调用和触发原因,再用同一场景验证修复效果。防抖、节流、虚拟列表、Web Worker 各有适用条件,应该由性能证据决定使用哪一种。

视频用四层展开这条主线:理解一帧的工作、正确录制和阅读 Performance、按耗时类型定位瓶颈、针对瓶颈修复。

第一层:先理解一帧的预算

浏览器更新画面时,可能涉及 JavaScript、样式计算、布局、绘制、栅格化和合成。不同变更触发的阶段不同,也不是所有阶段都在主线程上执行。

  • 60 Hz 屏幕的刷新间隔约为 16.7 ms,120 Hz 约为 8.3 ms。这是一帧的总时间尺度,业务 JavaScript 能使用的时间还要给其他工作留余量。
  • 主线程被长时间占用,或者布局、绘制、栅格化等阶段未能及时完成,都可能让画面错过刷新时机。
  • Long Task 的阈值是任务持续超过 50 ms,和帧预算不是同一个概念。 即使没有 Long Task,多个较短任务或昂贵的渲染工作也可能造成掉帧。

因此,“JS 只执行了 2 ms,但页面仍掉帧”完全可能。下一步应检查布局、绘制、栅格化、合成及线程调度,而不是直接认定 DOM 太多。

第二层:用 Performance 把问题缩小到一个时间段

视频强调先找异常帧,再看 Main 主线程和耗时构成。实操时可以按下面的顺序进行:

  1. 固定复现条件。 明确是输入、点击、滚动、拖拽还是动画卡顿,记录设备、浏览器和数据量。使用接近线上环境的构建;需要 CPU 降速时,前后使用相同设置。
  2. 录制一次短而完整的交互。 打开 Chrome DevTools 的 Performance,开始录制,执行问题操作,结束录制。避免把大量无关空闲时间混在一起。
  3. 找异常区间。 结合 Frames、交互轨道和截图检查停顿位置;查看版本中可用的丢帧标记与长任务警告,再缩小时间范围。
  4. 分解这段时间。 查看 Main 线程中的脚本、样式计算、Layout、Paint 等事件;需要时继续查看 Raster、合成和 GPU 相关轨道。选中范围后的 Summary 能帮助判断主要时间花在哪里。
  5. 落到调用与代码。 用 Call tree 看调用关系,用 Bottom-up 找累计耗时较高的函数,区分自身耗时和包含子调用的总耗时,再定位源码或触发更新的位置。

火焰图横向宽度表示持续时间,但父函数可能只是包含了昂贵的子调用。找到最长的条只是线索,还需要向下追踪真正消耗时间的工作。

第三层:按证据区分瓶颈

主要耗时或现象 重点检查 常见修复方向
Scripting 较高 长循环、大 JSON 解析、复杂正则、重复计算、过量框架更新 减少工作量、缓存合适的结果、拆分任务、将合适的计算移到 Worker
样式计算或 Layout 较高 DOM 规模、反复布局、修改样式后读取几何信息、过大更新范围 虚拟列表、批量处理 DOM 读写、缩小更新和布局影响范围
Paint 或栅格化较高 重绘区域、昂贵阴影和滤镜、滚动时反复重绘大块内容 减少重绘面积与效果成本,检查图片尺寸和栅格工作
合成相关工作较高 图层数量、巨大纹理、频繁图层变化、GPU 和显存压力 精简图层,控制纹理与动画成本,核查图层提升是否有收益
System/其他耗时较高 具体事件、线程、垃圾回收和环境负载 先明确来源,区分页面造成的开销和系统竞争,再决定修复位置

强制同步布局要看具体读写顺序

例如,循环中每次修改元素宽度,随后立即读取 offsetWidth,可能迫使浏览器提前完成布局;反复交错这种读写,会形成布局抖动。

改进方向是先批量读取所需几何信息,再批量写入样式,并去掉不必要的同步测量。读取 offsetTop 本身不等于一定发生重排,关键在于读取时布局是否已经失效,以及浏览器是否必须立即计算结果。

框架更新也要回到实际工作量

如果 trace 指向组件更新,再结合 React 或 Vue 的性能工具判断哪些组件反复更新、更新范围是否过大。只有发现相应开销后,才考虑状态下沉、稳定属性、记忆化等措施。

第四层:修复方案要匹配瓶颈

脚本阻塞: 优先减少计算和重复更新。需要分片时应让主线程有机会处理输入和渲染;把同样的重计算塞进 Promise 微任务,并不能保证让出渲染机会。Web Worker 适合可移出主线程的计算,不能直接操作 DOM,还要考虑消息传递和数据复制成本。requestIdleCallback 适合可延后的后台工作,不能保证关键交互及时完成。

布局开销: 大列表可以考虑虚拟滚动,同时保留合理的滚动、焦点和无障碍体验;减少 DOM 读写交错,缩小更新范围。动画优先评估 transform、opacity,并用录制结果确认是否避免了不必要的布局或绘制。

绘制和合成开销: 减少大范围重绘和昂贵视觉效果。will-change 应只用于少量确有收益的元素,并在不需要时移除;大量提升图层会增加内存和合成成本。contain 可帮助隔离影响范围,但不同取值会改变布局、尺寸或裁剪行为,使用前要确认页面仍然正确。

对视频中几处表述的校正

  • 颜色和时间阈值: 视频提到“超过 50 ms 标黄、超过 90 ms 标红”。这不能作为通用的 DevTools 判定规则;轨道颜色、警告和界面会随版本与事件类型变化,应以具体事件名称、持续时间和警告说明为准。50 ms 的 Long Task 阈值也不代表 50 ms 以内就流畅。
  • System 归因: System 占比高,不能直接推导为“与代码无关”。需要继续展开事件和相关线程,检查页面分配、垃圾回收、浏览器内部工作及系统资源竞争。
  • 合成优化: 使用 transform 不保证任何场景都“只走合成”,也不保证必然更快。图层、栅格化、纹理和运行环境都会影响结果;优化结论应由实际 trace 支持。

实操补充:用同场景验证修复

保持设备、数据、构建和录制条件一致,重新执行同一个交互,比较输入响应、异常帧、长任务次数与时长,以及目标阶段的耗时。重复几次确认收益稳定,同时检查滚动、动画、内存和操作行为是否出现退化。

掉帧和交互延迟有关,但指标并不等价。INP 主要衡量点击、轻触、键盘等交互的响应,滚动和持续动画还需要结合帧时间观察;也不能用一次本地录制直接声称线上 INP 已改善。

面试回答示例

我会先明确是哪种交互卡顿,在固定设备和数据条件下,用 Performance 录制可复现的过程。先找到异常帧或响应延迟区间,再拆解 Main 线程和相关渲染线程的耗时。脚本高就沿调用树定位长任务和重复计算;布局高就查 DOM 规模和强制同步布局;绘制或合成高就看重绘区域、滤镜、图层与栅格化。然后选择任务拆分、Worker、虚拟列表或绘制优化,并用相同条件重新录制验证。60 Hz 下帧间隔约 16.7 ms,Long Task 的 50 ms 阈值是另一个概念,没有长任务也可能掉帧。

关联笔记

参考资料

看看其他文章 →