首页 > 网站 > WEB开发 > 正文

关于Javascript的内存泄漏问题的整理稿

2024-04-27 14:21:42
字体:
来源:转载
供稿:网友

关于javascript的内存泄漏问题的整理稿

写了好长时间Javascript小功能模块,从来没有关注过内存泄漏问题。记得以前写C++程序的时候,内存泄漏是个严重的问题,我想是时候关注一下了。网上找了篇文章,Mark一下。原文地址:http://www.blogjava.net/tim-wu/archive/2006/05/29/48729.html常规循环引用内存泄漏和Closure内存泄漏要了解javascript的内存泄漏问题,首先要了解的就是javascript的GC原理。我记得原来在犀牛书《JavaScript: The Definitive Guide》中看到过,IE使用的GC算法是计数器,因此只碰到循环 引用就会造成memory leakage。后来一直觉得和观察到的现象很不一致,直到看到Eric的文章,才明白犀牛书的说法没有说得很明确,估计该书成文后IE升级过算法吧。 在IE 6中,对于javascript object内部,jscript使用的是mark-and-sweep算法,而对于javascript object与外部object(包括native object和vbscript object等等)的引用时,IE 6使用的才是计数器的算法。Eric Lippert在http://blogs.msdn.com/ericlippert/archive/2003/09/17/53038.aspx一文中提到IE 6中JScript的GC算法使用的是nongeneration mark-and-sweep。对于javascript对算法的实现缺陷,文章如是说:"The benefits of this apPRoach are numerous, but the principle benefit is that circular references are not leaked unless the circular reference involves an object not owned by JScript. "也就是说,IE 6对于纯粹的Script Objects间的Circular References是可以正确处理的,可惜它处理不了的是JScript与Native Object(例如Dom、ActiveX Object)之间的Circular References。所以,当我们出现Native对象(例如Dom、ActiveX Object)与Javascript对象间的循环引用时,内存泄露的问题就出现了。当然,这个bug在IE 7中已经被修复了[http://www.quirksmode.org/blog/archives/2006/04/ie_7_and_javasc.html]。http://msdn.microsoft.com/library/default.asp?url=/library/en-us/IETechCol/dnwebgen/ie_leak_patterns.asp 中有个示意图和简单的例子体现了这个问题:

Html代码收藏代码
  1. <html>
  2. <head>
  3. <scriptlanguage="JScript">
  4. varmyGlobalObject;
  5. functionSetupLeak()//产生循环引用,因此会造成内存泄露
  6. {
  7. //Firstsetupthescriptscopetoelementreference
  8. myGlobalObject=
  9. document.getElementById("LeakedDiv");
  10. //Nextsetuptheelementtoscriptscopereference
  11. document.getElementById("LeakedDiv").expandoProperty=
  12. myGlobalObject;
  13. }
  14. functionBreakLeak()//解开循环引用,解决内存泄露问题
  15. {
  16. document.getElementById("LeakedDiv").expandoProperty=
  17. null;
  18. }
  19. </script>
  20. </head>
  21. <bodyonload="SetupLeak()"onunload="BreakLeak()">
  22. <divid="LeakedDiv"></div>
  23. </body>
  24. </html>

上面这个例子,看似很简单就能够解决内存泄露的问题。可惜的是,当我们的代码中的结构复杂了以后,造成循环引用的原因开始变得多样,我们就没法那么容易观察到了,这时候,我们必须对代码进行仔细的检查。尤其是当碰到Closure,当我们往Native对象(例如Dom对象、ActiveX Object)上绑定事件响应代码时,一个不小心,我们就会制造出Closure Memory Leak。其关键原因,其实和前者是一样的,也是一个跨javascript object和native object的循环引用。只是代码更为隐蔽,这个隐蔽性,是由于javascript的语言特性造成的。但在使用类似内嵌函数的时候,内嵌的函数有拥有一 个reference指向外部函数的scope,包括外部函数的参数,因此也就很容易造成一个很隐蔽的循环引用,例如:DOM_Node.onevent ->function_object.[ [ scope ] ] ->scope_chain ->Activation_object.nodeRef ->DOM_Node。[http://msdn.microsoft.com/library/default.asp?url=/library/en-us/IETechCol/dnwebgen/ie_leak_patterns.asp]有个例子极深刻地显示了该隐蔽性:

Html代码收藏代码
  1. <html>
  2. <head>
  3. <scriptlanguage="JScript">
  4. functionAttachEvents(element)
  5. {
  6. //ThisstructurecauseselementtorefClickEventHandler//element有个引用指向函数ClickEventHandler()
  7. element.attachEvent("onclick",ClickEventHandler);
  8. functionClickEventHandler()
  9. {
  10. //Thisclosurerefselement//该函数有个引用指向AttachEvents(element)调用Scope,也就是执行了参数element。
  11. }
  12. }
  13. functionSetupLeak()
  14. {
  15. //Theleakhappensallatonce
  16. AttachEvents(document.getElementById("LeakedDiv"));
  17. }
  18. </script>
  19. </head>
  20. <bodyonload="SetupLeak()"onunload="BreakLeak()">
  21. <divid="LeakedDiv"></div>
  22. </body>
  23. </html>

还有这个例子在IE 6中同样原因会引起泄露

Html代码收藏代码
  1. functionleakmaybe(){
  2. varelm=document.createElement("DIV");
  3. elm.onclick=function(){
  4. return2+2;
  5. }
  6. }
  7. for(vari=0;i10000;i++){
  8. leakmaybe();
  9. }

btw:关于Closure的知识,大家可以看看http://jibbering.com/faq/faq_notes/closures.html这篇文章,习惯中文也可以看看zkjbeyond的blog,他对Closure这篇文章进行了简要的翻译:http://www.blogjava.net/zkjbeyond/archive/2006/05/19/47025.html。 之所以会有这一系列的问题,关键就在于javascript是种函数式脚本解析语言,因此javascript中“函数中的变量的作用域是定义作用域,而 不是动态作用域”,这点在犀牛书《JavaScript: The Definitive Guide》中的“Funtion”一章中有所讨论。http://support.microsoft.com/default.aspx?scid=KB;EN-US;830555中也对这个问题举了很详细的例子。一些 简单的解决方案目前大多数Ajax前端的javascript framework都利用对事件的管理,解决了该问题。如果你需要自己解决这个问题,可以参考以下的一些方法:http://outofhanwell.com/ieleak/index.php?title=Main_Page:有个不错的检测工具http://youngpup.net/2005/0221010713 中提到:可以利用递归Dom树,解除event绑定,从而解除循环引用:

Html代码收藏代码
  1. if(window.attachEvent){
  2. varclearElementProps=[
  3. 'data',
  4. 'onmouSEOver',
  5. 'onmouseout',
  6. 'onmousedown',
  7. 'onmouseup',
  8. 'ondblclick',
  9. 'onclick',
  10. 'onselectstart',
  11. 'oncontextmenu'
  12. ];
  13. window.attachEvent("onunload",function(){
  14. varel;
  15. for(vard=document.all.length;d--;){
  16. el=document.all[d];
  17. for(varc=clearElementProps.length;c--;){
  18. el[clearElementProps[c]]=null;
  19. }
  20. }
  21. });
  22. }

而http://novemberborn.net/javascript/event-cache一文中则通过增加EventCache,从而给出一个相对结构化的解决方案

Html代码收藏代码
  1. /*EventCacheVersion1.0
  2. Copyright2005MarkWubben
  3. Providesawayforautomagicallyremovingeventsfromnodesandthuspreventingmemoryleakage.
  4. See<http://novemberborn.net/javascript/event-cache>formoreinformation.
  5. ThissoftwareislicensedundertheCC-GNULGPL<http://creativecommons.org/licenses/LGPL/2.1/>
  6. */
  7. /*Implementarray.pushforbrowserswhichdon'tsupportitnatively.
  8. Pleaseremovethisifit'salreadyinothercode*/
  9. if(Array.prototype.push==null){
  10. Array.prototype.push=function(){
  11. for(vari=0;i<arguments.length;i++){
  12. this[this.length]=arguments[i];
  13. };
  14. returnthis.length;
  15. };
  16. };
  17. /*EventCacheusesananonymousfunctiontocreateahiddenscopechain.
  18. Thisistopreventscopingissues.*/
  19. varEventCache=function(){
  20. varlistEvents=[];
  21. return{
  22. listEvents:listEvents,
  23. add:function(node,sEventName,fHandler,bCapture){
  24. listEvents.push(arguments);
  25. },
  26. flush:function(){
  27. vari,item;
  28. for(i=listEvents.length-1;i>=0;ii=i-1){
  29. item=listEvents[i];
  30. if(item[0].removeEventListener){
  31. item[0].removeEventListener(item[1],item[2],item[3]);
  32. };
  33. /*Fromthispointonweneedtheeventnamestobeprefixedwith'on"*/
  34. if(item[1].substring(0,2)!="on"){
  35. item[1]="on"+item[1];
  36. };
  37. if(item[0].detachEvent){
  38. item[0].detachEvent(item[1],item[2]);
  39. };
  40. item[0][item[1]]=null;
  41. };
  42. }
  43. };
  44. }();

使用方法也很简单:

Html代码收藏代码
  1. <scripttype="text/javascript">
  2. functionaddEvent(oEventTarget,sEventType,fDest){
  3. if(oEventTarget.attachEvent){
  4. oEventTarget.attachEvent("on"+sEventType,fDest);
  5. }elseif(oEventTarget.addEventListener){
  6. oEventTarget.addEventListener(sEventType,fDest,true);
  7. }elseif(typeofoEventTarget[sEventType]=="function"){
  8. varfOld=oEventTarget[sEventType];
  9. oEventTarget[sEventType]=function(e){fOld(e);fDest(e);};
  10. }else{
  11. oEventTarget[sEventType]=fDest;
  12. };
  13. /*ImplementingEventCachefora
发表评论 共有条评论
用户名: 密码:
验证码: 匿名发表