跪拜 Guibai
← All articles
Frontend

How to Diagnose Frontend Apps That Get Slower the Longer They Run

By IT橘子皮 ·
Read original on juejin.cn ↗ Google Translate ↗ Alt translation

Progressive slowdowns are harder to catch than initial-load problems because they require sustained interaction to surface. A repeatable, tool-driven method for isolating long tasks, memory leaks, and forced reflows lets developers fix the root cause instead of treating symptoms.

Summary

Pages that degrade over time typically suffer from long tasks, memory leaks, or layout thrashing. The Performance panel's flame graph exposes these by color-coding scripting, rendering, and painting work on the main thread. A macro-to-micro approach starts by scanning for tasks exceeding 50ms, then drills into the Call Tree or Bottom-Up view to identify the specific functions consuming the most CPU time.

For slowdowns that appear only after extended use, the Memory panel becomes essential: a stepwise, non-recovering memory curve points to leaked listeners, timers, or closures. Zone.js callbacks from frameworks like Angular can amplify the problem by queuing heavy asynchronous work on the main thread. High-frequency events such as scroll or resize demand debouncing or throttling, while oversized synchronous tasks need splitting via `setTimeout` or `requestIdleCallback`.

The guiding principle is measure first, then optimize, then verify. Flame graphs and call trees turn guesswork into precise, data-driven fixes.

Takeaways
Long tasks — any scripting, rendering, or painting block exceeding 50ms — are the most direct cause of main-thread jank and appear as wide blocks in the flame graph.
Use the Call Tree to trace a long task's full invocation chain; switch to the Bottom-Up view sorted by Self Time to immediately see which function burns the most CPU.
A memory curve that rises in steps and never drops after navigation signals a leak from undestroyed listeners, lingering timers, or closure references.
Zone.js captures asynchronous callbacks and queues them on the main thread; heavy work inside setInterval or Promise.then callbacks can accumulate and degrade performance over time.
Scroll, resize, and mousemove handlers that fire hundreds of times should be wrapped with debounce or throttle, and DOM mutations deferred to requestAnimationFrame.
Alternating between reading layout properties (offsetHeight, getBoundingClientRect) and writing styles forces repeated synchronous layouts; batch reads before writes to avoid layout thrashing.
Split monolithic initialization tasks with setTimeout or requestIdleCallback so the main thread yields control and stays responsive.
Conclusions

The 50ms long-task threshold is a practical, actionable line in the sand: any flame-graph block wider than that is a direct candidate for splitting or deferring.

Bottom-Up view is underused but often more efficient than the Call Tree for performance work because it immediately surfaces the highest-cost leaf functions regardless of call depth.

Memory leaks in SPAs are especially insidious because users rarely refresh; a leak that looks small per operation compounds into a crash over hours of use.

Zone.js is a double-edged abstraction — it standardizes async context but can silently serialize heavy work onto the main thread, making Angular and similar apps vulnerable to cumulative slowdowns.

Layout thrashing is a classic 'death by a thousand cuts' problem: no single forced reflow is catastrophic, but hundreds interleaved with script execution destroy frame budgets.

Concepts & terms
Long Task
Any task on the browser's main thread that takes longer than 50ms to execute, blocking user interaction and causing visible jank. Chrome flags these with red triangles in the Performance panel.
Flame Graph (Flame Chart)
A visualization in the Performance panel where the horizontal axis is time and the vertical axis is the call stack. Colors indicate work type: yellow for scripting, purple for rendering, green for painting.
Call Tree / Bottom-Up View
Two analysis views in DevTools. The Call Tree shows the full hierarchical call chain for a selected time range. The Bottom-Up view merges identical functions and sorts them by Self Time — the time spent inside the function body, excluding its children — to quickly identify the most expensive operations.
Layout Thrashing (Forced Synchronous Layout)
A performance anti-pattern where JavaScript interleaves reads of layout properties (e.g., offsetHeight) with writes to the DOM, forcing the browser to recalculate layout synchronously on every iteration and destroying frame rates.
Zone.js
A library that patches browser APIs to provide execution context for asynchronous operations. Used by Angular and other frameworks, it can cause main-thread congestion when many async callbacks queue heavy work.
Source: juejin.cn ↗ Google Translate ↗ Backup ↗