`
wyzxzws
  • 浏览: 378801 次
  • 性别: Icon_minigender_1
  • 来自: dazhou
社区版块
存档分类
最新评论

js前端之---内存泄漏

 
阅读更多

转自:http://www.blogjava.net/JAVA-HE/archive/2009/10/27/299856.html

问题:js前端之---内存泄漏

解决方案:

                                                          JavaScript 内存泄露
     今天下午同事让帮忙看web内存泄露问题。当时定位到创建ActiveX 对象的时候产生的,于是我对这个奇怪的问题进行了一些深入探索。
很多时候我都依赖javascript的垃圾回收机制,所以对C 以及C++ 操作内存语言常发生的内存泄露是很陌生的。当时创建回调函数用了闭包,当然最终的解决方法是也避免闭包调用。
   
    随着这个问题的浮出水面,我回忆起以前的一个项目中也应该存在这个内存泄露问题。于是查阅了相关资料把类似的问题总结下来,希望对大家也有帮助。
    原因:对于一门具有垃圾收回机制的语言存在内存泄露,其原因不外乎就是javascript脚本引擎存在bug。
   很多时候,我们要做的不是去修正那样的bug,而是想办法去规避。
目前发现的可能导致内存泄露的代码有三种:

  • 循环引用
  • 自动类型装箱转换
  • 某些DOM操作

下面具体的来说说内存是如何泄露的
循环引用:这种方式存在于IE6和FF2中(FF3未做测试),当出现了一个含有DOM对象的循环引用时,就会发生内存泄露。
什么是循环引用?首先搞清楚什么是引用,一个对象A的属性被赋值为另一个对象B时,则可以称A引用了B。假如B也引用了A,那么A和B之间构成了循环引用。同样道理 如果能找到A引用B B引用C C又引用A这样一组饮用关系,那么这三个对象构成了循环引用。当一个对象引用自己时,它自己形成了循环引用。注意,在js中变量永远是对象的属性,它可以指向对象,但决不是对象本身。
循环引用很常见,而且通常是无害的,但如果循环引用中包含DOM对象或者ActiveX对象,那么就会发生内存泄露。例子:

var a=document.createElement("div");
var b=new Object();
a.b=b;
b.a=a; 

很多情况下循环引用不是这样的明显,下面就是著名的闭包(closure)造成内存泄露的例子,每执行一次函数A()都会产生内存泄露。试试看,根据前面讲的scope对象的知识,能不能找出循环引用?

function A()...{
    var a=document.createElement("div");
    a.onclick=function()...{
        alert("hi");
    }
}
A(); 

OK, 让我们来看看。假设A()执行时创建的作用域对象叫做ScopeA 找到以下引用关系

ScopeA引用DOM对象document.createElement("div");
DOM对象document.createElement("div");引用函数function(){alert("hi")}
函数function(){alert("hi")}引用ScopeA
这样就很清楚了,所谓closure泄露,只不过是几个js特殊对象的循环引用而已。
自动类型装箱转换:这种泄露存在于ie6 ie7中。这是极其匪夷所思的一个bug,看下面代码

var s="lalalalala";
alert(s.length); 

这段代码怎么了?看看吧,"lalalalala"已经泄露了。关键问题出在s.length上,我们知道js的类型中,string并非对象,但可以对它使用.运算符,为什么呢?因为js的默认类型转换机制,允许js在遇到.运算符时自动将string转换为object型中对应的String对象。而这个转换成的临时对象100%会泄露(汗一下)。

某些DOM操作也可能导致泄露 这些恶心的bug只存在于ie系列中。在ie7中 因为试图fix循环引用bug而让情况变得更糟,以至于我对写这一段种满了恐惧。
从ie6谈起,下面是微软的例子,

<html>
    <head>
        <script language="JScript">...
        function LeakMemory()
        ...{
            var hostElement = document.getElementById("hostElement");
            // Do it a lot, look at Task Manager for memory response
            for(i = 0; i < 5000; i++)
            ...{
                var parentDiv =
                    document.createElement("<div onClick='foo()'>");
                var childDiv =
                    document.createElement("<div onClick='foo()'>");
                // This will leak a temporary object
                parentDiv.appendChild(childDiv);
                hostElement.appendChild(parentDiv);
                hostElement.removeChild(parentDiv);
                parentDiv.removeChild(childDiv);
                parentDiv = null;
                childDiv = null;
            }
            hostElement = null;
        }

        function CleanMemory()
        ...{
            var hostElement = document.getElementById("hostElement");
            // Do it a lot, look at Task Manager for memory response
            for(i = 0; i < 5000; i++)
            ...{
                var parentDiv =
                    document.createElement("<div onClick='foo()'>");
                var childDiv =
                    document.createElement("<div onClick='foo()'>");
                // Changing the order is important, this won't leak
                hostElement.appendChild(parentDiv);
                parentDiv.appendChild(childDiv);
                hostElement.removeChild(parentDiv);
                parentDiv.removeChild(childDiv);
                parentDiv = null;
                childDiv = null;
            }
            hostElement = null;
        }
        </script>
    </head>
    <body>
        <button onclick="LeakMemory()">Memory Leaking Insert</button>
        <button onclick="CleanMemory()">Clean Insert</button>
        <div id="hostElement"></div>
    </body>
</html>

 

看看结果吧,LeakMemory造成了内存泄露,而CleanMemory没有,循环引用了么?仔细看看没有。那么是什么问题呢?MS的解释是"插入顺序不对",必须先将父级元素appendChild。这听起来有些模糊,这里给出一个比较恰当的等价描述:永远不要使用DOM节点树之外元素的appendChild方法。
我曾经看到过这样的说法,创建dom的时候,先创建子节点,当子节点完善后一次性添加到页面中,不要一点点朝页面上加东西,尽量减少document刷新次数,这样效率会高点。(打个比方就是应该像 LeakMemory )可见这里我还是被某些书籍误导了。至少他没有告诉我内存泄露的问题。
接下来是ie7和ie8 beta 1中运行这段程序,看到什么?没看错吧,2个都泄露了!别急,刷新一下页面就好了。为什么呢?ie7改变了DOM元素的回收方式:在离开页面时回收DOM树上的所有元素,所以ie7下的内存管理非常简单:在所有的页面中只要挂在DOM树上的元素,就不会泄露,没挂在DOM树上,肯定泄露。所以,ie7中记住一条原则:在离开页面之前把所有创建的DOM元素挂到DOM树上。
接下来谈谈ie7的这个设计吧,坦白的说,这种做法纯粹是偷懒的垃圾做法。动态垃圾回收不是保证所有内存都在离开页面时收回,而是要保证内存的充分利用,运行时不回收,等到离开时回收有什么用?这只是名义上的避免泄露,其实是完全的泄露。况且还没有回收DOM节点树之外的元素。
 4.内存泄露的解决方案
内存泄露怎么办?真的以后不用闭包了么?没法封装控件了?这样做还不如要了js程序员的命,嘿嘿。
事实上,通过一些很简单的小技巧,可以巧妙的绕开这些危险的bug。
to be continued......
coming soon:

  • 显式类型转换
  • 避免事件导致的循环引用
  • 不影响返回值地打破循环引用
  • 延迟appendChild
  • 代理DOM对象
  • 显式类型转换

首先说说最容易处理的情况 对于类型转换造成的错误,我们可以通过显式类型转换来避免:

var s=newString("lalalalala");//此处将string转换成object
alert(s.length); 

这个太容易了,算不上正经方案。不过类型转换泄露也就这一种处理方法了。
避免事件导致的循环引用
在比较成熟的js程序员里,把事件函数写成闭包是再正常不过了:

function A(){
    var a=document.createElement("div");
    a.onclick=function(){
        alert("hi");
    }
} 
 

这将导致内存泄露。按照IBM那两位老大的说法,当然是把函数放外面或者a=null就没问题了,不过还要访问A()里面的变量呢?假如有下面的代码:

function A(){
    var a=document.createElement("div");
    var b=document.createElement("div");
    a.onclick=function(){
        alert(b.outerHTML);
    }
    return a;
} 
 

 如何将它的逻辑表达出来 还避免内存泄露? 分析一下这个内存泄露的形式:只要onclick的外部环境中不包含a那么,就不会泄露。那么办法有2个一是将环境到a的引用断开 另一个是将function到环境的引用断开,但是,如果要在函数中访问b就不能将Function放到外面,如果要返回a的值,就不能a=null,怎么办呢?
解决方案1:
构造一个不含a的新环境

function A(){
    var a=document.createElement("div");
    var b=document.createElement("div");
    a.onclick=BuildEvent(b);
    return a;
}
 

 

function BuildEvent(b)
{
    return function(){
        alert(b.outerHTML);
    }
} 
 


a本身可以通过this访问,将其它需要访问的外层函数变量传递给BuildEvent就可以了。保持BuildEvent定义和调用的参数名一致,会带来方便。
解决方案2:
在return 之后a=null,不可能? 看看下面:

function A(){
    try{
        var a=document.createElement("div");
        var b=document.createElement("div");
        a.onclick= function(){
            alert(b.outerHTML);
        }
        return a;
    } finally {
        a=null;
    }
} 
 

finally在try之后执行,如果finall块不返回值,才会返回try块的返回值。
· 延迟appendChild
还记得函数的lazy initalize吧,对于ie恶心至极的DOM操作泄露,我们需要用类似的方法去处理。在一个函数中构造一个复杂对象,在需要的时候将之appendChild到DOM树上,这是很常见的做法,但在IE6中,这样做将导致所谓的"插入顺序内存泄露",没有别的办法,我们只能用一个数组parts保存子节点,编写一个appendTo方法先序遍历节点树,去把它挂在某个DOM节点上。

function appendTo(Element)
...{
    Element.appendChild(this);
    if(!this.parts)return;
    for(var i=0;i<this.parts.length;i++)
        parts.appendTo(this);
} 
 

· 垃圾箱
对于ie7,我比较无可奈何,因为DOM对象不会被CG程序回收,只有离开页面时会被回收,所以我的建议是:使用DOM要有节制,尽量多用innerHTML吧...... good luck.
一旦你使用了DOM对象,千万不要试图o=null,你可以设置一个叫做Garbage的div并且将其display设置为none,将不用的DOM对象存入其中(就是appendChild上去)就好了
· 代理对象
这是Ext的做法,这里只是顺带提一下。将每个元素用一个"代理对象"操作,不论appendChild还是其他操作都不是对DOM对象本身的操作,而是通过这个代理对象操作。这是一个很不错的Proxy模式,不过要想避免泄露还是需要一点功夫的,并非用了Proxy之后就不会泄露,有时反而更容易泄露。

5 .FAQ
1 内存泄露是内存占用很大么? 不是,即使1byte内存也叫做内存泄露。
2 程序中提示,内存不足,是内存泄露么?不是,这一般是无限递归函数调用导致栈内存溢出。
3 内存泄露是哪个区域泄露?堆区,栈区是不会泄露的。
4 window对象是DOM对象么?不是,window对象参与的循环引用不会内存泄露。
5 内存泄露后果是什么?大多数时候后果不很严重,但过多DOM操作会导致网页执行变慢。
6 跳转页面后,内存泄露仍然存在么?仍然存在,直到关闭浏览器。
7 FireFox也会内存泄露么?FF2仍然有内存泄露

分享到:
评论
2 楼 wyzxzws 2014-12-12  
yoven 写道
那如何检测程序中是否存在内存泄漏呢?

http://www.php100.com/html/webkaifa/javascript/2012/0504/10356.html
1 楼 yoven 2014-12-10  
那如何检测程序中是否存在内存泄漏呢?

相关推荐

    01-JS内存泄漏.md

    本章将通过多个面试题,讲解前端面试常考的底层原理问题,涉及 JS Vue React Nodejs 等。 ## 为何要考察 深挖你的技术“天花板”,看未来潜力和可培养性 —— 特别是对于刚毕业不就的新人。 如果面试通过了,大...

    Js内存泄漏及解决方案.pdf

    Js内存泄漏及解决方案.pdf

    前端开源库-leakage

    前端开源库-leakage节点的泄漏、内存泄漏测试。使用您最喜欢的测试运行程序进行javascript内存占用。

    中高级前端必须了解的JS中的内存管理(推荐)

    因为自动垃圾回收机制的存在,让大多Javascript开发者感觉他们可以不关心内存管理,所以会在一些情况下导致内存泄漏。 内存生命周期 JS 环境中分配的内存有如下声明周期: 1.内存分配:当我们申明变量、函数、对象...

    插件:检测javascript的内存泄漏

    Javascript的内存泄漏,不是太可怕。它只会悄悄的,慢慢的把你的浏览器拖的巨慢无比,让你愤怒的拍案而起,大骂微软出品的破烂浏览器危害社会。这一切有可能并不是浏览器的错,可能只是因为网页上有些javascript的...

    fast-cache:短小精悍的前端暂存工具,防止内存“泄漏”

    短小精悍的前端暂存工具,防止内存“泄漏” 简介 特色:一次开发、多次使用,[更多...][1] 安装下载 下载地址 npm i fast-cache-andy CDN ##快速使用 import FastCache form 'fast-cache-andy' var cache = new Fast...

    2022前端企业高频问答题

    JavaScript: 数据类型、面向对象、继承、闭包、插件、作用域、跨域、原型链、模块化、自定义事件、内存泄漏、事件机制、异步装载回调、模板引擎、Nodejs、JSON、ajax等。 其他: HTTP、安全、正则、优化、重构、...

    vue单页应用的内存泄露定位和修复问题小结

    在前端项目(PC端)中,内存泄露的定位往往比修复更加困难,即使google浏览器有提供Memory工具,但是面对成千上万的元素和错综复杂的引用关系,开发则依然很难快速定位到问题代码块。 一、什么是内存泄漏? 系统进程...

    基于JavaScript的三维地图程序(仿e都市gis)

    1.已修正8.15 ie内存泄漏问题,chrome下可能还存在内存泄漏 2.增加图标定位功能 3.增加鼠标滚轮事件(http://yongzhi.blog.hexun.com/5057947_d.html) 4.通过jquery加载json数据文件(图标显示层数据) 2009-8-17 ...

    前端开发面试题.html

    内存泄漏、跨域、异步请求、模板引擎、模块化、Flux、同构、算法、ECMAScript6、Nodejs、HTTP、 其他: 主流MVVM框架(React\Vue\Angular)、Hybrid App\React Native\Weex、TypeScript、RESTFul、WEB安全、前端...

    前端面试题基础篇293题

    第二部分CSS,第三部分JavaScript,第四部分Jquery,第五部分Bootstrap,第六部分微信小程序,第七部分webpack,第八部分常用编程题,第九部分其他常问内容如负载均衡、CDN、内存泄露、babel原理、js自定义事件等,...

    前端最全汇总面试题及答案.docx

    JavaScript: 数据类型、面向对象、继承、闭包、插件、作用域、跨域、原型链、模块化、自定义事件、内存泄漏、事件机制、异步装载回调、模板引擎、Nodejs、JSON、ajax等。 其他: HTTP、安全、正则、优化、重构、...

    leetcode题库-fe-material:整理收集最有趣的前端技术教程及文档

    leetcode题库 fe-material ...内存泄漏 CSS 教程 Less Sass CSS3 布局 JavaScript 教程 ES6 Node ES3 ES5 服务端教程 好玩的网站 开源图标 视频技术 动画技术 游戏方向 硬件方向 神秘工具 文档&文章 数据

    leetcode中国-webLinks:史上最全的前端技术教程及文档

    leetcode中国 fe-material ...内存泄漏 CSS教程 Less Sass CSS3布局 JavaScript教程 ES6 Node ES3 ES5 服务端教程 好玩的网站 开源图标 视频技术 动画技术 游戏方向 硬件方向 神秘工具 文档&文章 数据

    Front-End:前端那些事儿

    前端那些事儿:high_voltage::bullseye::artist_palette:一、JS篇原型 原型链 继承作用域 闭包 堆栈溢出 内存泄漏异步 单线程 线程DOM操作(增删改查)数组去重、排序---什么是函数柯里化?以及说一下JS的API有哪些应用...

    presentation:唱吧前端分享会

    by @HexJavaScript 是如何工作的:Web Workers 分类及 5 个使用场景 by 姜志伟Webpack 基础 by 李奡内存管理及如何处理4类常见的内存泄漏问题 by 韩红淑唱歌分一亿之动画 by 鄢敏不摸手后台开发引导 by 小西瓜深入...

    front-end-interview:前端面试问题

    前端采访前端面试问题核心JS 闭包语境Function.prototype.bind polyfill JavaScript(es5)/原型继承中的类Object.create polyfill Array.prototype.*方法事件循环承诺,异步功能DOM API /事件处理/事件委托use ...

    FE-Knowledge:前端知识整理

    三、JavaScriptJavaScript的原型与原型链JavaScript的执行上下文与执行上下文栈JavaScript的作用域与作用域链JavaScript的thisJavaScript的闭包JavaScript的垃圾回收机制和内存泄露JavaScript创建对象的多种方式及优...

    leetcode中国-technology:整理收集最有趣的前端技术教程及文档

    leetcode中国 technology ...内存泄漏 CSS 教程 Less Sass CSS3 布局 JavaScript 教程 ES6 Node ES3 ES5 服务端教程 好玩的网站 开源图标 视频技术 动画技术 游戏方向 硬件方向 神秘工具 文档&文章 数据

    程序员面试刷题的书哪个好-front-end-interview:前端面试

    数据类型、面向对象、继承、闭包、插件、作用域、跨域、原型链、模块化、自定义事件、内存泄漏、事件机制、异步装载回调、模板引擎、Nodejs、JSON、ajax 等。 1.浅拷贝和深拷贝的区别 2.defer 和 async 的区别(JS ...

Global site tag (gtag.js) - Google Analytics