SPA路由切换卡顿主因是内存未释放,需在beforeUnmount或useEffect清理函数中清除全局事件监听、定时器及DOM引用,并用Chrome Memory面板验证Detached节点与Retained Size是否递增。

单页应用(SPA)频繁切换路由时卡顿,往往不是因为渲染慢,而是内存没及时释放——旧页面残留的对象持续占用主线程资源,GC压力增大,页面越用越卡。核心思路是:让不该存在的引用彻底消失,让垃圾回收器能真正清掉它们。
清理事件监听和定时器
路由卸载时,最容易被忽略的是绑定在全局或 DOM 上的监听器和定时器。它们会隐式持有对组件作用域、DOM 节点甚至整个状态树的引用。
- Vue 中在 beforeUnmount 或 onBeforeUnmount 钩子中调用
removeEventListener,尤其注意scroll、resize、keydown等全局监听 - React 中在 useEffect 的清理函数里清除
setInterval和addEventListener,避免闭包捕获过期的 state 或 ref - 手动绑定的监听器建议使用具名函数,便于精准移除;匿名函数无法被 removeEventListener 正确识别
解除 DOM 引用与缓存节点
组件内部若缓存了 DOM 元素(比如通过 ref 或 querySelector),又未在卸载时置空,就会形成 Detached DOM —— 节点已从文档树移除,却仍被 JS 变量强引用。
在 Java 中初始化和管理阿里云 SDK客户端。包括单例模式、线程安全、endpoint 与 region 配置、VPC 终端节点、同步与异步等。
- 卸载前将缓存的 DOM 引用设为
null,例如myRef.current = null - 避免在闭包中长期持有大 DOM 对象,如
const el = document.getElementById('xxx')后直接用于定时器回调 - 使用 Chrome Memory 面板的 Heap Snapshot 对比:切换两次路由后拍快照,筛选 Detached HTMLDivElement 类型,查看 Retained Size 是否递增
控制模板与计算结果缓存
像 art-template、Handlebars 等模板引擎默认开启缓存,若模板路径含动态参数(如 /user/123),每次都会生成新缓存项,导致 caches.__data 持续膨胀。
立即学习“Java免费学习笔记(深入)”;
- 为模板缓存加 TTL(生存时间),超过时限自动失效,避免无限增长
- 对高频渲染但内容固定的模板,用
cacheKey归一化路径(如统一为/user/detail,不带 ID) - 大数据计算结果(如分页列表的聚合统计)不要挂到全局或组件外层变量上,改用局部变量 + memoization 工具(如
lodash.memoize)并设定最大缓存数
监控与验证是否真释放了
光写清理逻辑不够,得验证它是否生效。靠肉眼或 console.log 很难判断对象是否被 GC。
- 用 Chrome DevTools 的 Memory 面板 → Take heap snapshot,连续操作三次路由切换,对比各次快照中相同构造函数实例数是否稳定
- 关注 Retained Size 列:如果某个闭包或监听器的 Retained Size 在多次快照中持续上升,说明它还在“拖着”一堆不该存在的对象
- 配合 Performance 面板录制,观察内存曲线是否呈阶梯式上涨,而非周期性回落

















