应使用 memory 面板+堆快照+分离 dom 分析定位 script 标签导致的内存污染;script 执行后变量是否驻留取决于可达引用链,如挂 window、闭包捕获、未销毁库实例、未解绑事件等;需通过对比快照分析 closure、detached dom 等增长项,并遵循封装、生命周期管理、定时器/事件配对、第三方库 destroy 四条实践。

直接看控制台的 App State 并不能监控 script 标签变量对内存的污染——因为 App State 不是浏览器原生功能,Chrome DevTools 里也没有叫“App State”的独立面板。你真正需要的是 Memory 面板 + 堆快照(Heap Snapshot)+ 分离 DOM 分析 这套组合,来定位 script 标签引入后遗留的变量如何长期驻留堆中。
script 标签执行后,变量为何“常驻”不释放?
script 标签一旦执行完成,其内部声明的变量是否留在内存中,和标签本身是否存在完全无关。关键取决于这些变量是否还被“可达引用链”持有:
- 挂到
window上的全局变量(如window.apiClient = new Api())会一直存活,直到显式赋值为null或页面刷新 - 闭包捕获的大对象(如函数内定义了
const bigList = Array(100000),又被定时器或事件监听器持续引用),GC 无法回收 - 第三方库实例(如
Chart.js图表、wangEditor编辑器)未调用.destroy(),其内部 DOM 引用、事件监听、数据缓存全部滞留 - 匿名回调绑定事件但未解绑(
el.addEventListener('click', () => {...})),导致整个作用域闭包无法释放
用 Memory 面板实操定位污染源
打开 Chrome DevTools → Memory 面板,按以下顺序操作:
- 点击左上角 垃圾回收图标(?️),强制触发一次 GC,清掉瞬时对象
- 执行目标操作(例如:动态加载一个 script,渲染组件,再切换路由卸载)
- 再次点?️,等几秒后拍下第一张堆快照(Snapshot 1)
- 重复操作 2–3 次(模拟用户多次进入/退出),再点?️,拍 Snapshot 2
- 在 Snapshot 2 中点击 “Compare to” → 选 Snapshot 1,按 “# New” 或 “Size” 排序
重点关注这几类增长明显的条目:
-
Closure:展开后看闭包里是否持有了 DOM 元素、大数组、未销毁的库实例 -
Detached HTMLDivElement等:说明 DOM 已从树中移除,却被 JS 变量意外引用(常见于 GTM、统计脚本、轮播图插件) -
system / Context下的长命名函数:可能是未清理的定时器回调或 Promise 微任务残留 - 搜索
window.开头的变量名(如window.chartInst),确认是否手动挂载且未清理
避免 script 标签引发的内存污染,4 条硬性实践
-
禁止裸写全局变量:所有 script 必须用 IIFE、ES Module 封装,或启用
'use strict'阻止隐式挂载 -
导出明确生命周期方法:动态加载的脚本应提供
init()和destroy(),并在组件卸载时主动调用(Vue 的onBeforeUnmount、React 的useEffect清理函数) -
定时器与事件必须配对管理:保存
setInterval返回值;事件监听用具名函数或AbortController.signal,确保能精准解绑 -
第三方库务必查文档调 destroy:比如
tippy.destroy()、editor.destroy()、map.remove(),不是删 DOM 节点就能释放内存
script 标签只是入口,真正的污染来自它执行后留下的运行时痕迹。控制台不提供“App State”视图,但堆快照能如实暴露每一个不该存在的引用。盯住 Closure 和 Detached DOM,比任何日志打印都更接近真相。











