场景:用户在某个页面操作时,在监控平台收集到的数据表现为内存占用有逐步提高的趋势,最先想到的是 DOM 元素卸载后其 JavaScript 对象未能被垃圾回收这类内存泄漏问题。同时,如果在 DOM 元素删除时没有完全清理其对应的 JavaScript 引用,那么内存占用就会只增不减,最终影响用户体验。
Performance Monitor 能够在较小的性能代价下展示出网站应用的若干个影响性能和体验的关键参数随着时间变化(用户操作)的趋势,其关键指标如下所示。
CPU usage 网页使用的CPU百分比。默认显示。
JS heap size JavaScript程序在页面上使用的内存量。默认显示。
DOM Nodes 浏览器中DOM节点的数量(跨选项卡)。默认显示。
JS event listeners 浏览器中JavaScript事件监听器的数量(跨选项卡)。
Documents 浏览器中文档对象的数量(跨选项卡)。
Document Frames 浏览器中文档框架的数量(跨选项卡)。
Layouts / sec 浏览器引擎每秒构建页面布局的次数。
Style recalcs / sec 浏览器引擎每秒计算页面CSS样式的次数。
针对内存泄漏问题,可以重点关注 JavaScript 堆大小和 DOM 节点数的变化趋势,并根据以下原则对内存泄漏进行初步的定性判断:
当二者的变化趋势满足同比同频,基本可以确定是对 DOM 元素的引用没有清理导致的内存泄露问题。
官方文档:https://learn.microsoft.com/en-us/microsoft-edge/devtools-guide-chromium/memory-problems/dom-leaks
Detached Elements 的功能非常明确,即找到所有没有挂载在 DOM 树上,同时还没有被浏览器引擎垃圾回收的 DOM 元素。因为浏览器的垃圾回收本身就是周期性的行为,所以在进行问题排查前,必须手动触发一次垃圾回收行为,保证剩下的就是要排查分析的目标元素。(记住一个要点:每次录制前需要手动回收,以保证数据准确)
DOM对象是占用内存最高的一类对象之一,因此如果在应用程序中频繁地创建和销毁DOM对象,就容易导致内存泄漏。游离的DOM引用指的是已经不在文档中的DOM节点的引用,但是这些引用仍然被保存在JavaScript的变量、数组和对象中,因此这些DOM节点无法被垃圾回收器回收,从而导致内存泄漏。
官方文档:https://learn.microsoft.com/en-us/microsoft-edge/devtools-guide-chromium/memory-problems/
Memory 能够建立当前应用的 JavaScript 堆快照,用于进一步分析页面的 JavaScript 对象以及相互之间的引用关系。在已经定位了泄漏源的基础上,可以借助该工具查明目标 DOM 被什么 JavaScript 对象持有了引用导致无法被垃圾回收。
基于这里的快照,我们可以发现发生泄漏的 DOM 元素的 distance属性 是 7,点击之后可以反向追溯其到 Root(浏览器环境下为 window 对象)的完整路径。当然,持有该 DOM 元素的路径通常不止一条,我们只需要关注最短的那条即可。基于此,我们可以构建出其对象持有路径…
[外链图片转存中…(img-Xi1zHZIP-1719410253318)]
在分析了多个发生泄漏的 DOM 元素之后,我们最终定位到XXXX属性持有了已经被卸载的 DOM 的引用,导致用户只要停留在页面,DOM增删情况越多越多内存泄漏得越多。对相关代码进行处理,及时删除引用即可。
在 React 框架中,为了能够方便地建立 DOM 元素与 FiberNode 之间的关联,由框架生成的 DOM 元素会持有其 FiberNode 对象的引用,FiberNode 中同样持有了相关 DOM 元素的引用。因此,无论是浏览器的 DOM 树还是 React 的 Fiber 树,只要有任意一个节点没有被正确释放引用,其自身以及所有子孙元素在两棵树上的对象都无法被垃圾回收。
因此在React项目中,不仅要关心真实Dom的引用,也要关心虚拟Dom及其Fiber节点的引用情况…