描述
我们遇到的情况是有 3 层嵌套的 iframe。第三个 iframe 初始化了一些大尺寸对象,但每当用户在应用程序中导航时,该 iframe 就会被丢弃并在 DOM 中重建。
每当丢弃并构造第三层的新 iframe 时,应用程序的堆大小就会不断增加,从而会出现内存泄漏。
测试用例链接
在 Chrome 中转到https://vamsikrishnach90.github.io/。打开开发人员工具(按 F12)并导航到“内存”选项卡。注意 JS 堆大小,arppox 5 MB。
- 单击“加载子页面”按钮。子页面加载到父页面的 iframe 内。
- 点击“轰炸页面!” 子页面上的按钮。这将使用一个巨大的 json 初始化 1 个自定义类型的全局变量。打开 devtools 并检查堆现在为 75 MB。
- 单击“删除子页面”按钮。子页面 (iframe) 将从 DOM 中删除。
重复步骤 1 - 3 约 2 次。步骤 2 后,记下堆大小。您会注意到,每次我们添加子 iframe 并对其进行轰炸时,堆都会不断增加。即使在步骤 3 中,我们丢弃了该帧,垃圾收集器也不会声明该堆。 用例演示帮助(图)
观察结果
重复上述1-3步骤3次后,拍摄堆快照。请注意,每次添加子页面并对其进行轰炸时创建的自定义类“Vamsi”的 3 个实例仍然存在于堆中,即使相应的 iframe 已从 DOM 中删除。
请注意,在对象图中,unloadHandler() 已绑定到属于废弃 iframe 的 Window 对象。堆内存快照
根本原因
子页面在父页面中有一条jquery查找语句:
$('div.msg-banner', window.parent.document).show();
这一行语句导致了内存泄漏问题。
$('div.msg-banner', window.parent.document)
上面的部分语句,调用了JQuery find api。这个查找 API 被Sizzle覆盖,Sizzle 是 JQuery 采用的纯 JavaScript CSS 选择器引擎。来自 JQuery 的 find api 调用 Sizzle 的 setDocument 函数(参见图片),该函数向所请求文档的 (window.parent.document) 窗口对象注册 unloadHandler()。这导致父页面的卸载事件绑定到子页面中的 unloadHandler() 回调函数。因此,即使子页面 iframe 从 DOM 中删除,窗口对象也永远没有资格被垃圾收集,这就是内存泄漏的原因。
链接到Stackoverflow线程:https://stackoverflow.com/questions/76579826/iframes-memory-leak-with-jquery-and-sizzle