ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

腾讯前端面试:如何排查页面卡顿及链路追踪?

2026/8/23 20:04:31 拓冰建站 浏览量
腾讯前端面试:如何排查页面卡顿及链路追踪? 这个问题来源于一次腾讯前端实习岗的面试。这个问题是这样的面试官问问题一这里现在有 A、B、C 三个组件用户反馈很卡。卡顿的原因可能是首屏加载卡也可能是分页切换卡还可能是加载数据卡顿。我们应该怎么排查是哪个原因问题二如果 A 组件是一个筛选组件当 A 组件改变了筛选项导致 B 列表、C 统计图组件的更新突然变卡请从 Vue 执行的链路来分析整个过程。问题一解答问题一这里现在有 A、B、C 三个组件用户反馈很卡。卡顿的原因可能是首屏加载卡也可能是分页切换卡还可能是加载数据卡顿。我们应该怎么排查是哪个原因首屏卡​可能原因​​所有组件同步加载未做代码分割初始数据请求过多或串行大型第三方库如 ECharts、Monaco Editor未异步加载大量图片未懒加载或图片未压缩组件内setup中有同步耗时操作如遍历大数组 这个原因在真实业务开发中遇到的比较少一般存在于库或者工具的开发​排查工具​Chrome Performance录制首屏查看 Long Task 分布哪个函数占用时间长Network查看瀑布图是否存在串行请求、大体积 JS bundleVue Devtools → Timeline查看组件挂载顺序和耗时​解决方案​路由懒加载() import(./B.vue)异步组件SuspensedefineAsyncComponent对非首屏关键模块做 code splitting 或者 async component首屏关键路径上的资源反而要尽可能减少异步加载否则可能会让首屏更慢图片懒加载v-lazy/loadinglazy将初始化请求改为并行Promise.all使用shallowRef避免深层响应式初始化开销 主要用于库或者工具开发的优化真正业务开发中使用极少这么多条目记不住怎么办所以我更希望从一条完整的请求渲染链路来回答。从发送网络请求到 JS 执行然后 Vue 初始化并渲染页面紧接着浏览器执行布局、绘制以及合成等操作最终呈现给用户并可以交互。这里面的每一个细节其实刚好对应着我们排查的流程点。我详细说一些关键部分比如页面出来得晚。举个例子我们的 JS 下载需要 2 秒钟接口请求 1 秒钟Vue 初始化 500ms这种就属于经典的 FCP 或 LCP 页面初始化慢的情况。当然还有另一种情况页面出来了但是一打开就很卡。虽然页面开始显示但这种情况其实是主线程被长任务占用了这就是经典的运行时 Performance。所以我们需要区分是“页面出现得晚”还是“页面已经出现但主线程的长任务导致了交互卡顿”如果是页面出现得晚我们重点要看网络、资源和初始化链路。如果是长任务导致的交互卡顿我们需要重点看主线程、长任务以及 JS 的执行。分页切换卡​可能原因​​切换分页时整个列表组件销毁重建而非复用每次切换都重新请求全量数据或前端做全量数据的分页数据量过大切换动画导致强制回流如修改height、top等几何属性分页组件本身渲染复杂如带大量按钮、下拉​排查工具​Performance录制切换动作观察 Layout / Recalc Style 耗时Vue Devtools → Component Tree查看切换时组件是否被卸载再挂载Console打印mounted和unmounted日志确认组件生命周期​解决方案​使用虚拟滚动如vue-virtual-scroller只渲染可视区域分页数据只请求当前页不做全量加载动画使用transform/opacity避开 Layout分页控件用shallowRef或v-memo减少不必要的子组件更新 只是作为一种优化思路真实开发中能不用就不用具体原因下面有写到这里特别强调不建议直接说 keep-alive 。因为我们需要先确认分页切换时到底发生的是一个组件的更新还是组件发生了卸载和挂载。千万不能在没有确定生命周期行为之前就直接使用 keep-alive。加载数据卡​可能原因​​请求返回后前端对大量数据做了复杂转换排序、分组、深拷贝且放在watch或computed中同步执行数据更新触发了大量 watcher 和 computed 重新求值没有 loading 状态用户在等待时界面僵死因为主线程被占请求未做防抖用户连续操作导致并发请求堆积​排查工具​Performance查看数据返回后的 Long Task定位是哪段代码占用了主线程Sources → Call Stack在数据更新的地方打断点查看调用栈Vue Devtools → Components观察数据更新后哪些组件重新渲染当然这里面试官可能会追问通过 performance 看到两秒的长任务怎么确定是 Vue此时我们主要可以这样回答通过 performance 定位到主线程的长任务。通过 call tree 和 bottom up 来看具体的 JS function。如果 function 落到 render reactive effect 或者 patch 等 API 上再结合 Vue 的 dev tools就可以定位到具体的组件。​解决方案​数据处理移到 Web Worker 中主线程只负责赋值使用computed缓存而非methods在模板中调用对大量数据使用Object.freeze冻结静态数据跳过响应式包装 平时的业务开发用到的少一般用于库的开发加入 loading 骨架屏避免用户感知空白 这里很多同学有一个误区骨架屏只是优化用户体验而不是优化性能的。如果我们主线程卡顿好几秒其实放个骨架屏也没有用因为主线程被 JS 占用连骨架屏自己都可能无法及时绘制使用debounce或AbortController取消过期请求避免过期结果回写前端减少不必要的客户端处理上面对于数据请求响应后做监听的行为非常建议把详细的链条说出来当 HTTP 响应之后我们一步解析得到响应数据并且修改响应式的 state进而触发 trigger 变更。通过 Vue 的 scheduler 调度然后组件 update job 进行入队之后 nextTick 可批量执行。页面通过 render 进行渲染进而生成或者复用虚拟节点。然后执行 patch 操作 DOM 更新。浏览器最终进行布局、绘制或者合成。因此如果面试官想要更深入地回答的话其实性能问题还可能存在于这些场景数据处理响应式触发Computed 和 watcher组件的 render虚拟 DOM 的 diff 和 patch浏览器的 layout 和 paint问题二解答问题二如果 A 组件是一个筛选组件当 A 组件改变了筛选项导致 B 列表、C 统计图组件的更新突然变卡请从 Vue 执行的链路来分析整个过程。我把这个问题的执行链路拆分一下便于思考filter.category book 1. filter.category 被修改 2. Proxy setter / reactive trigger 3. 找到依赖 category 的 effects 4. Vue scheduler 对更新任务进行调度 5. 相关 component update job 被执行 6. B/C 如果确实依赖该状态则重新执行 render 7. render 产生新的 VNode 8. Vue patch 新旧 VNode 9. DOM 更新 10. 浏览器执行 Style/Layout/Paint/Composite现象本质A 的筛选项通常是reactive对象或 Pinia store 里的 state发生变化后B 和 C 组件重新渲染但渲染耗时明显过长导致界面卡顿。Vue 链路中的卡顿根源如果面试官并没有强调具体的场景我们可以根据这几种原因依次分析。着重强调一下关于列表中的 key当列表只是尾部追加没有重排序时Index 作为 key 未必会造成明显的问题只有在列表发生插入、删除或者排序的时候Index 作为 key 并不能稳定地表示业务实体的身份这时候才容易出现错误的复用。当然其实如果面试官要追问的话我们也不能直接说父组件更新就一定会导致整棵子树的重新渲染因为 Vue 3 内部其实已经做了非常多的优化。父组件更新之后Vue 会对其子组件进行必要的更新检查。是否导致子组件真正执行更新其实还要看子组件依赖的 props 变化以及编译优化后的 patch 信息等而不能简单地说整个子组件全部会重新渲染。排查方法Vue Devtools → Performance 面板录制筛选项变化过程查看 B/C 组件的render耗时、setup调用次数、computed 重新求值次数。console.time 打点在 B/C 的beforeUpdate和updated钩子中打印耗时定位哪个组件最慢。检查 props 引用确认 A 传给 B/C 的筛选项是否每次都生成新对象如...state展开导致子组件认为 props 变了。这些只是排查的方法那我们应该如何快速定位呢我这里把定位做了一些分层可以供大家参考​第一层是数据层。​通过筛选改变进而触发接口请求返回 10 万条数据这样就可以去检查我们的数据量以及序列化、反序列化和数据转换等操作。​第二层是响应式层​。比如说我们的 state 发生了改变进而触发了 trigger那么其他的响应式 API比如 computed、watch 或者组件的副作用都会执行。在这种情况下我们需要思考是否监听了过大的对象是否存在无关的依赖computed 是否会频繁地失效watch 是否触发了大量的副作用​第三层是 Vue 渲染层​。当组件更新时触发 render 渲染并且进行虚拟 DOM 对比更新以及 patch。这种情况下我们需要思考render 是否执行了太多次列表是不是有成千上万个节点或者存在大量无意义的更新props 是否因为引用的变化导致了更新key 是否合理​最后一个层面是浏览器的渲染层面​。这一点其实主要回答的是“浏览器渲染原理”这道面试题。我们根据渲染原理比如说 DOM 更新触发 style 的变更进而触发重排、重绘以及合成层。其中主要涉及以下情况大量的 DOM 强制同步布局复杂的 CSS 动画大面积的绘制。Chrome DevTools 的 Performance / Rendering / Layers 等工具可以进一步确认是 JS 执行、布局绘制还是合成阶段占用时间。解决方案// 1. 精细化响应式只监听具体字段 const filter reactive({ category: , priceRange: [] }) // B 组件只关心 category const filteredItems computed(() items.filter(i i.category filter.category)) // 2. 用 computed 缓存昂贵计算 const sortedList computed(() [...rawList].sort(/* ... */)) // 3. 子组件包裹 memo (Vue 3.3)了解即可开发中很少用 script setup defineOptions({ inheritAttrs: false }) /script // 或用 shallowRef triggerRef 手动控制更新了解即可开发中很少用 // 4. 列表加稳定 key且不要用 index li v-foritem in list :keyitem.id // 5. 使用 Pinia getter 缓存派生数据 export const useStore defineStore(main, () { const expensiveList computed(() heavyComputation(items.value)) })同时这里我们也需要明确computed 虽然能够缓存高昂计算但是不是用了 computed 就一定快。computed 的依赖是否稳定以及依赖变化频率是否合理才是关键。还有官方很明确地强调过v-memo 应该尽量少使用。只有明确确定存在大量无意义的子树更新时才会考虑使用 v-memo 跳过特定子树的更新。这是一种非常偏底层的微优化我们面试时候可以说但在工作中一定要慎重再慎重地去使用。话术总结面对“页面卡顿”我不会先猜是哪个组件有问题而是先用 Performance 把卡顿定位到网络、JS 执行、Vue 更新还是浏览器渲染阶段。如果是 Vue 更新导致的卡顿我会从响应式数据变更开始追A 修改筛选条件 → 触发对应响应式依赖 → Vue scheduler 调度更新任务 → 相关组件执行 render → 生成 VNode → patch → DOM 更新 → 浏览器进行 Layout/Paint。然后分别检查四类问题第一是否有过大的响应式依赖和无意义的 computed/watch第二是否因为 props 引用变化等原因导致无意义组件更新第三render、diff、列表渲染是否过重第四DOM 更新后是否产生大量 Layout/Paint。最后再根据定位结果选择 computed、v-memo、虚拟列表、拆分组件、Web Worker 等具体优化而不是一上来就堆优化手段。