IE 下由于 iframe 造成的内存泄漏原因分析及方案

@GuoYongfeng 2018-04-03 09:48:59发表于 iuap-design/blog

0、背景

目前政务一个财政类中后台业务系统在 IE8 下频繁出现崩溃问题,久查无果。系统在 Web 上采用的传统多页面的方式构成,通过内嵌 iframe 加上多页签的方式实现的类似 SPA 的业务形式。

1、问题分析

1、IEiframe 元素的回收方面存在着 bug,在通常情况下应该将该元素的src属性值修改为”abort:blank”,并手工将其从 DOM 树上移除,然后把脚本中引用它的变量置空并调用 CollectGarbage() 就可以避免 iframe 不能正常回收所造成的内存泄露。
2、在IE8中,生成特定 DOM 节点所占用的内存是不会被释放的,即使这些节点被删除内存也不会被释放。内存泄露的节点类型包括:form、button、input、select、textarea、a、img等。

2、Javascript 的垃圾回收机制

Javascript具有自动垃圾回收机制(GC:Garbage Collecation),也就是说,执行环境会负责管理代码执行过程中使用的内存。

原理:垃圾收集器会定期(周期性)找出那些不在继续使用的变量,然后释放其内存。

JavaScript垃圾回收的机制很简单:找出不再使用的变量,然后释放掉其占用的内存,但是这个过程不是实时的,因为其开销比较大,所以垃圾回收器会按照固定的时间间隔周期性的执行。

不再使用的变量也就是生命周期结束的变量,当然只可能是局部变量,全局变量的生命周期直至浏览器卸载页面才会结束。局部变量只在函数的执行过程中存在,而在这个过程中会为局部变量在栈或堆上分配相应的空间,以存储它们的值,然后在函数中使用这些变量,直至函数结束,而闭包中由于内部函数的原因,外部函数并不能算是结束。

3、iframe 内存解决方案

3.1、思路

创建 iframe并挂载到指定容器,创建的时候设置宽高。销毁iframe的时候需要设置iframe 的 src 属性将其指向空白页面,不然会造成内存泄漏。

这种方式虽然不能对 iframe 所占用的内存完全释放,但是iframe内存占用量不会一直增长,整个应用内存使用量控制在150M左右。

3.2、代码实现


/**
 * 动态创建iframe 
 * @param {*} container DOM 节点 
 * @param {*} src URL String
 * @param {*} onload onload 事件处理函数
 */
function createIframe(container, src, onload){ 
    var iframe = document.createElement("iframe"); 
     
    iframe.style.width = '100%'; 
    iframe.style.height = '100%'; 
    iframe.style.margin = '0'; 
    iframe.style.padding = '0'; 
    iframe.style.overflow = 'hidden'; 
    iframe.style.border = 'none'; 
     
    if(onload && Object.prototype.toString.call(onload) === '[object Function]'){ 
        if(iframe.attachEvent){ 
            iframe.attachEvent('onload', onload); 
        }else if(iframe.addEventListener){ 
            iframe.addEventListener('load', onload); 
        }else{ 
            iframe.onload = onload; 
        } 
    } 
     
    iframe.src = src; 
    container.appendChild(iframe); 

    return iframe; 
} 
     
/**
 * 销毁iframe时通过将iframe指向空白页面的方式释放iframe所占用的内存。 
 * @param {*} iframe DOM 节点
 */
function destroyIframeDOMNode(iframe){ 
    iframe.src = 'about:blank'; 
    
    try{ 
        ifame.contentWindow.document.write( '');//清空frame的内容
        ifame.contentWindow.document.clear();
        ifame.contentWindow.close(); //避免frame内存泄漏
    
    }catch(e){
        throw(e)
    } 

    if (navigator.userAgent.indexOf('MSIE') >= 0) {
        if (CollectGarbage) {
            CollectGarbage(); //IE 特有 释放内存
            //把iframe从页面移除 
            iframe.parentNode.removeChild(iframe); 
        }
    }
}

/**
 * 每过 5 秒主动释放内存 
 */
function clearRAMInterval(){
    setInterval( function() {
        if (navigator.userAgent.indexOf('MSIE') >= 0) {
            if (CollectGarbage) CollectGarbage(); //IE 特有 释放内存
        }
    }, 5000) 
}

3.3 ifame.contentWindow.document 和 ifame. contentDocument 的区别

  • contentWindow属性是iframe对象或frame对象的专属属性。兼容各种浏览器,并返回window对象。作用是:以 HTML 对象来返回 iframe 或frame中的文档。
  • contentDocument 属性可用于火狐或IE8+,返回一个documnet对象。作用是:以 HTML 对象返回框架容纳的文档。

4、JavaScript 内存解决方案

4.1 意外的全局变量引起的内存泄露

  • 原因: 全局变量不会被回收
  • 解决:使用严格模式避免

4.2 闭包引起的内存泄漏

  • 原因: 活动对象被引用,使闭包内的变量不会被释放
  • 解决: 将活动对象赋值为null

4.3 被清理的DOM元素引起的内存泄漏

  • 原因: 虽然DOM被删掉了,但对象中还存在对DOM的引用
  • 解决: 将对象赋值为null

4.4 未清空的定时器或回调函数引起的内存泄漏

  • 原因: 定时器内部实现闭包,回调也是闭包
  • 解决: 清理定时器clearInterval、null