iframe内存泄漏

场景:公司最近发现在网页随着时间越用越久,越来越卡。通过chrome的任务管理器,发现内存增长的很厉害,疑似内存泄漏。通过调试后发现,多次开关tab页(使用iframe实现的)内存不会下降。下面做了一个demo还原内存泄漏。
父窗口html(命名为leak.html):

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
<input type="button" value="创建iframe页面" onclick="createIframe()" />
<input type="button" value="关闭iframe页面" onclick="closeIframe()" />
<div id="content" style="width:500px;height:300px;border:1px solid black"></div>

<script type="text/javascript">
    var arr = new Array();
    
    function createIframe(){
        const iframe = document.createElement("iframe");
        iframe.src = "iframe.html";
        iframe.id = "ifr";
        document.getElementById("content").appendChild(iframe);
        console.log("创建iframe完成");
    }
    
    function closeIframe(){
        let iframe = document.getElementById("ifr");
        iframe.src = "about:blank";
        iframe.contentWindow.document.write('')
        iframe.contentWindow.document.clear();
        iframe.contentWindow.close();
        iframe.parentNode.removeChild(iframe);
        iframe = null;
    }
</script>
</body>
</html>

iframe页(命名为:iframe.html,在该页面定义了一个大内存变量):

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
iframe page
<input type="button" value="父窗口赋值" onclick="assignment()" >
<script>
    //创建一个大对象
    var a = new Array(1024*1024*10).join('0').split('');
    function assignment(){
        //将iframe中的变量赋值给父窗口变量
        const obj = {name:"test"};//变量为json对象时不释放内存
        //const str = "test";//变量为字符串或者其他常量,会释放内存
        /* const fun = function(){
            console.log("test");//变量为函数时不会释放内存
        } */
        //const obj2 = new Array();//变量为对象不会释放内存
        
        window.parent.arr.push(obj);
        console.log("父窗口赋值完成");
    }
</script>
</body>
</html>

将上面两段代码拷贝到demo项目中,按名称命名后运行。打开leak.html页面如下:


leak.html

多次执行创建iframe页面,父窗口赋值,关闭iframe页面。观察浏览器内存变化。
观察方式:打开chrome控制台观察Performance monitor中的JS heap size的值


Performance monitor

或者打开chrome任务管理器观察:
chrome任务管理器

可以每次在关闭iframe页后执行垃圾回收,再观察内存,确保结果准确性。


点击垃圾回收按钮手动执行gc

经过观察发现,多次开关iframe页内存持续增长不会下降

结论:
如果父窗口(外部窗口)保留了iframe中的引用变量、方法或者父窗口将内部方法绑定到父窗口,即使删除iframe也不会释放iframe中所有的js堆内存和dom所占用的内存

1、如果在iframe中使用window.top给父窗口赋值
window.top.innerObject = someInsideObject
2、如果在父窗口使用iframeEl.contentWindow将iframe中的变量赋值给父窗口的变量
innerObject = iframeEl.contentWindow.someInsideObject
3、如果将iframe中的方法绑定到父窗口上
window.top.document.addEventLister('click',function(){...});
以上这些操作都将导致删除iframe不释放内存

解决方法:
断开所有父页面对iframe中的变量的引用
上述demo中新建一个断开引用的方法如下:

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
<input type="button" value="创建iframe页面" onclick="createIframe()" />
<input type="button" value="关闭iframe页面" onclick="closeIframe()" />
<input type="button" value="断开ifame变量的引用" onclick="shutIframe()" />
<div id="content" style="width:500px;height:300px;border:1px solid black"></div>

<script type="text/javascript">
    var arr = new Array();
    
    function createIframe(){
        const iframe = document.createElement("iframe");
        iframe.src = "iframe.html";
        iframe.id = "ifr";
        document.getElementById("content").appendChild(iframe);
        console.log("创建iframe完成");
    }
    
    function closeIframe(){
        let iframe = document.getElementById("ifr");
        iframe.src = "about:blank";
        iframe.contentWindow.document.write('')
        iframe.contentWindow.document.clear();
        iframe.contentWindow.close();
        iframe.parentNode.removeChild(iframe);
        iframe = null;
    }
    //断开iframe中变量的引用
    function shutIframe(){
        for(let i = 0;i < arr.length;i++){
            arr[i] = null;
        }
        console.log("断开引用成功");
    }
</script>
</body>
</html>

将上述代码替换demo中的leak.html代码。打开页面如下:


断开引用

在多次开关ifame后,再点击断开ifame变量引用的按钮,手动执行垃圾回收后内存恢复正常。注意:不要在chrome控制台操作变量将引用断开,如果这么做内存不会释放。个人理解原因是控制台又保留了一份变量。

第二种方式:使用postMessage在父子页面进行传值。尽量不要使用window.top或iframeEl.contentWindow直接操作父子窗口中对变量或方法具体用法参考:
https://blog.csdn.net/tang_yi_/article/details/79401280
这个方法传值,不能传递方法。操作机制是将窗口中对变量进行克隆后再传输。

第三种方式:解除父窗口对iframe中的事件绑定

对这个问题对个人理解:
变量或方法在定义时就确定了全局上下文,所以将值传递给父窗口时,即使删除iframe,因为变量或方法的引用不会释放ifame中的上下文。又因为基本数据类型是值传递,如果父窗口引用的是子窗口的基本数据类型的变量,不会导致内存泄漏(纯属个人理解,如有不正确的地方请多指教)。

tips:
判断引用是否为当前上下文创建。将以下代码直接拷贝到控制台执行

function scan(o) {
        try{
            Object.keys(o).forEach(function (key) {
                var val = o[key];
                if (typeof val !== 'string' && typeof val !== 'number' && typeof val !== 'boolean' && !(val instanceof Object) && val !== null && val !== undefined) {
                    //debugger;
                    console.log("obj--"+o+"objkey--"+key);
                 }else{
                 }
            })
        }catch(err){
        }
    }
    scan(window)

如果非当前上下文的引用将被输出(只是window中的引用)。原理是使用instanceof Object判断来自子窗口的引用返回false。如果将Object替换为iframeEl.contentWindow.Object则返回true。

js其他内存泄漏方式参考如下链接:
https://segmentfault.com/a/1190000011842980
https://www.tutorialspoint.com/how-can-detached-dom-elements-cause-memory-leak-in-javascript
https://community.pega.com/knowledgebase/articles/common-javascript-memory-leak-patterns
https://auth0.com/blog/four-types-of-leaks-in-your-javascript-code-and-how-to-get-rid-of-them/

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,122评论 6 505
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,070评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,491评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,636评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,676评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,541评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,292评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,211评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,655评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,846评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,965评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,684评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,295评论 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,894评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,012评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,126评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,914评论 2 355

推荐阅读更多精彩内容