`
babydeed
  • 浏览: 238809 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

权威JavaScript 中的内存泄露模式

    博客分类:
  • Ext
阅读更多

如果您知道内存泄漏的起因,那么在 JavaScript 中进行相应的防范就应该相当容易。在这篇文章中,作者 Kiran Sundar 和 Abhijeet Bhattacharya 将带您亲历 JavaScript 中的循环引用的全部基本知识,向您介绍为何它们会在某些浏览器中产生问题,尤其是在结合了闭包的情况下。在了解了您应该引起注意的常见内存泄漏模式之后,您还将学到应对这些泄漏的诸多方法。

JavaScript 是用来向 Web 页面添加动态内容的一种功能强大的脚本语言。它尤其特别有助于一些日常任务,比如验证密码和创建动态菜单组件。JavaScript 易学易用,但却很容易在某些浏览器中引起内存的泄漏。在这个介绍性的文章中,我们解释了 JavaScript 中的泄漏由何引起,展示了常见的内存泄漏模式,并介绍了如何应对它们。

注意本文假设您已经非常熟悉使用 JavaScript 和 DOM 元素来开发 Web 应用程序。本文尤其适合使用 JavaScript 进行 Web 应用程序开发的开发人员,也可供有兴趣创建 Web 应用程序的客户提供浏览器支持以及负责浏览器故障排除的人员参考。

JavaScript 中的内存泄漏

JavaScript 是一种垃圾收集式语言,这就是说,内存是根据对象的创建分配给该对象的,并会在没有对该对象的引用时由浏览器收回。JavaScript 的垃圾收集机制本身并没有问题,但浏览器在为 DOM 对象分配和恢复内存的方式上却有些出入。

Internet Explorer 和 Mozilla Firefox 均使用引用计数来为 DOM 对象处理内存。在引用计数系统,每个所引用的对象都会保留一个计数,以获悉有多少对象正在引用它。如果计数为零,该对象就会被销毁,其占用的内存也会返回给堆。虽然这种解决方案总的来说还算有效,但在循环引用方面却存在一些盲点。

循环引用的问题何在?

当两个对象互相引用时,就构成了循环引用,其中每个对象的引用计数值都被赋 1。在纯垃圾收集系统中,循环引用问题不大:若涉及到的两个对象中的一个对象被任何其他对象引用,那么这两个对象都将被垃圾收集。而在引用计数系统,这两个对象都不能被销毁,原因是引用计数永远不能为零。在同时使用了垃圾收集和引用计数的混合系统中,将会发生泄漏,因为系统不能正确识别循环引用。在这种情况下,DOM 对象和 JavaScript 对象均不能被销毁。清单 1 显示了在 JavaScript 对象和 DOM 对象间存在的一个循环引用。

清单 1. 循环引用导致了内存泄漏
<html>
    <body>
    <script type="text/javascript">
    document.write("circular references between JavaScript and DOM!");
    var obj;
    window.onload = function(){
  obj=document.getElementById("DivElement");
       document.getElementById("DivElement").expandoProperty=obj;
       obj.bigString=new Array(1000).join(new Array(2000).join("XXXXX"));
       };
    </script>
    <div id="DivElement">Div Element</div>
    </body>
    </html>

如上述清单中所示,JavaScript 对象 obj 拥有到 DOM 对象的引用,表示为 DivElement。而 DOM 对象则有到此 JavaScript 对象的引用,由 expandoProperty 表示。可见,JavaScript 对象和 DOM 对象间就产生了一个循环引用。由于 DOM 对象是通过引用计数管理的,所以两个对象将都不能销毁。

另一种内存泄漏模式

在清单 2 中,通过调用外部函数 myFunction 创建循环引用。同样,JavaScript 对象和 DOM 对象间的循环引用也会导致内存泄漏。

清单 2. 由外部函数调用引起的内存泄漏
<html>
 <head>
 <script type="text/javascript">
 document.write(" object s between JavaScript and DOM!");
 function myFunction(element)
 {
  this.elementReference = element;
  // This code forms a circular reference here
  //by DOM-->JS-->DOM
  element.expandoProperty = this;
 }
 function Leak() {
  //This code will leak
  new myFunction(document.getElementById("myDiv"));
 }
 </script>
 </head>
 <body onload="Leak()">
 <div id="myDiv"></div>
 </body>
 </html>

正如这两个代码示例所示,循环引用很容易创建。在 JavaScript 最为方便的编程结构之一:闭包中,循环引用尤其突出。

JavaScript 中的闭包

JavaScript 的过人之处在于它允许函数嵌套。一个嵌套的内部函数可以继承外部函数的参数和变量,并由该外部函数私有。清单 3 显示了内部函数的一个示例。


清单 3. 一个内部函数
function parentFunction(paramA)
 {
    var a = paramA;
    function childFunction()
    {
   return a + 2;
    }
    return childFunction();
 }

JavaScript 开发人员使用内部函数来在其他函数中集成小型的实用函数。如清单 3 所示,此内部函数 childFunction 可以访问外部函数 parentFunction 的变量。当内部函数获得和使用其外部函数的变量时,就称其为一个闭包。

了解闭包

考虑如清单 4 所示的代码片段。


清单 4. 一个简单的闭包
<html>
 <body>
 <script type="text/javascript">
 document.write("Closure Demo!!");
 window.onload=
 function closureDemoParentFunction(paramA)
 {
    var a = paramA;
    return function closureDemoInnerFunction (paramB)
    {
      alert( a +" "+ paramB);
    };
 };
 var x = closureDemoParentFunction("outer x");
 x("inner x");
 </script>
 </body>
 </html>

在上述清单中,closureDemoInnerFunction 是在父函数 closureDemoParentFunction 中定义的内部函数。当用外部的 x 对 closureDemoParentFunction 进行调用时,外部函数变量 a 就会被赋值为外部的 x。函数会返回指向内部函数 closureDemoInnerFunction 的指针,该指针包括在变量 x 内。

外部函数 closureDemoParentFunction 的本地变量 a 即使在外部函数返回时仍会存在。这一点不同于 C/C++ 这样的编程语言,在 C/C++ 中,一旦函数返回,本地变量也将不复存在。在 JavaScript 中,在调用 closureDemoParentFunction 的时候,带有属性 a 的范围对象将会被创建。该属性包括值 paramA,又称为“外部 x”。同样地,当 closureDemoParentFunction 返回时,它将会返回内部函数 closureDemoInnerFunction,该函数包括在变量 x 中。

由于内部函数持有到外部函数的变量的引用,所以这个带属性 a 的范围对象将不会被垃圾收集。当对具有参数值 inner x 的 x 进行调用时,即 x("inner x"),将会弹出警告消息,表明 “outer x innerx”。

清单 4 简要解释了 JavaScript 闭包。闭包功能非常强大,原因是它们使内部函数在外部函数返回时也仍然可以保留对此外部函数的变量的访问。不幸的是,闭包非常易于隐藏 JavaScript 对象 和 DOM 对象间的循环引用。

闭包和循环引用

在清单 5 中,可以看到一个闭包,在此闭包内,JavaScript 对象(obj)包含到 DOM 对象的引用(通过 id "element" 被引用)。而 DOM 元素则拥有到 JavaScript obj 的引用。这样建立起来的 JavaScript 对象和 DOM 对象间的循环引用将会导致内存泄漏。


清单 5. 由事件处理引起的内存泄漏模式
<html>
 <body>
 <script type="text/javascript">
 document.write("Program to illustrate memory leak via closure");
 window.onload=function outerFunction(){
  var obj = document.getElementById("element");
  obj.onclick=function innerFunction(){
  alert("Hi! I will leak");
  };
  obj.bigString=new Array(1000).join(new Array(2000).join("XXXXX"));
  // This is used to make the leak significant
 };
 </script>
 <button id="element">Click Me</button>
 </body>
 </html>

避免内存泄漏

幸好,JavaScript 中的内存泄漏是可以避免的。当确定了可导致循环引用的模式之后,正如我们在上述章节中所做的那样,您就可以开始着手应对这些模式了。这里,我们将以上述的 由事件处理引起的内存泄漏模式 为例来展示三种应对已知内存泄漏的方式。

一种应对 清单 5 中的内存泄漏的解决方案是让此 JavaScript 对象 obj 为空,这会显式地打破此循环引用,如清单 6 所示。

清单 6. 打破循环引用
<html>
 <body>
 <script type="text/javascript">
 document.write("Avoiding memory leak via closure by breaking the circular
  reference");
  window.onload=function outerFunction(){
  var obj = document.getElementById("element");
  obj.onclick=function innerFunction()
  {
   alert("Hi! I have avoided the leak");
   // Some logic here
  };
  obj.bigString=new Array(1000).join(new Array(2000).join("XXXXX"));
  obj = null; //This breaks the circular reference
  };
 </script>
 <button id="element">"Click Here"</button>
 </body>
 </html>

清单 7 是通过添加另一个闭包来避免 JavaScript 对象和 DOM 对象间的循环引用。

清单 7. 添加另一个闭包
<html>
 <body>
 <script type="text/javascript">
 document.write("Avoiding a memory leak by adding another closure");
  window.onload=function outerFunction(){
 var anotherObj = function innerFunction()
    {
    // Some logic here
    alert("Hi! I have avoided the leak");
     };
   (function anotherInnerFunction(){
   var obj = document.getElementById("element");
   obj.onclick=anotherObj })();
    };
 </script>
 <button id="element">"Click Here"</button>
 </body>
 </html>

清单 8 则通过添加另一个函数来避免闭包本身,进而阻止了泄漏。

清单 8. 避免闭包自身
<html>
 <head>
 <script type="text/javascript">
 document.write("Avoid leaks by avoiding closures!");
 window.onload=function()
 {
  var obj = document.getElementById("element");
  obj.onclick = doesNotLeak;
 }
 function doesNotLeak()
 {
  //Your Logic here
  alert("Hi! I have avoided the leak");
 }

 </script>
 </head>
 <body>
 <button id="element">"Click Here"</button>
 </body>
 </html>

 

转自:http://04js.cn/content.asp?id=1610

分享到:
评论

相关推荐

    Javascript内存泄露

    在JavaScript编程中,内存泄露指的是在浏览器中不再使用的变量或对象占用的内存没有被及时回收,导致可用内存逐渐减少的现象。这种现象通常发生在循环引用、闭包等复杂场景中。 #### 二、JavaScript垃圾回收机制 ...

    【温故而知新】JavaScript中内存泄露有那几种.md

    【温故而知新】JavaScript中内存泄露有那几种

    测试JavaScript在IE中的内存泄露

    标题"测试JavaScript在IE中的内存泄露"表明我们将讨论如何检测和分析JavaScript在IE浏览器中的内存泄露问题。在IE中,由于其JavaScript引擎(JScript)的一些特性,如活动对象模型(ActiveXObject)和遗留的COM组件...

    本文主要介绍了JavaScript几种常见的内存泄露

    本文将深入探讨JavaScript中的几种常见内存泄露模式,并提供相应的解决方案。 1. **全局变量和闭包引用** 全局变量生命周期长,如果没有正确释放,可能导致内存占用过多。闭包可以访问并保持对外部作用域的引用,...

    【JavaScript源代码】vue内存泄露详解.docx

    ### Vue中的内存泄露详解 #### 一、内存泄露的概念与危害 **内存泄露**是指程序在运行过程中,分配了一块内存后,由于某些原因导致该内存无法被释放或被垃圾回收机制回收,从而造成资源浪费的现象。对于JavaScript...

    ajax js性能优化和内存泄露检测工具

    本文将深入探讨AJAX和JavaScript在性能优化方面的策略,以及如何使用内存泄露检测工具来确保高效且无泄漏的代码。 一、AJAX性能优化 1. **减少HTTP请求**:每个HTTP请求都会带来一定的开销,包括建立连接、发送...

    jquery 内存泄露bug

    通常,内存泄露在JavaScript中发生是因为动态创建的元素、事件处理函数或其他资源没有被正确地清理和释放。 jQuery库在处理DOM操作和事件绑定时,如果不注意,可能会引发内存泄漏。例如: 1. **事件监听器**:当...

    ExtJS 内存泄露补丁

    2. **闭包引用**:JavaScript中的闭包可以保留对上下文的引用,如果闭包在不再需要时未被释放,它可能会导致外部对象无法被垃圾回收。 3. **缓存和集合未清空**:ExtJS的一些组件或功能可能会存储大量的对象引用,...

    JavaScript设计模式与开发实践.pdf

    本书《JavaScript设计模式与开发实践》是JavaScript语言的设计模式和开发实践的指南,旨在帮助初、中、高级Web前端开发人员和想往架构师晋级的中高级程序员,掌握JavaScript设计模式和开发实践的知识。本书共分为三...

    JavaScript权威指南

    JavaScript 权威指南 JavaScript 权威指南 JavaScript 权威指南 JavaScript 权威指南 JavaScript 权威指南 JavaScript 权威指南 JavaScript 权威指南 JavaScript 权威指南 JavaScript 权威指南 JavaScript 权威指南 ...

    JavaScript设计模式.pdf

    单体模式是JavaScript中最基本的设计模式之一。它的主要作用是提供一个命名空间,减少全局变量的数量,避免代码冲突,并组织代码使其易于维护。单体模式可以用来划分命名空间,组织代码和避免代码冲突。 2. 工厂...

    JavaScript高级与设计模式.zip

    5. **模块模式(Module)**:JavaScript中,可以利用闭包来创建私有作用域,保护内部变量和方法,只暴露必要的接口。这有助于代码组织和防止命名冲突。 6. **策略模式(Strategy)**:定义了一系列的算法,并将每个...

    JavaScript权威指南(JavaScript犀牛书一本)

    3. **作用域和闭包**:解析了JavaScript中的变量作用域规则以及闭包的概念,这是解决许多复杂问题的基础。 4. **异步编程**:详述了事件循环、回调函数、Promise、async/await等异步处理方式,帮助开发者应对...

    JavaScript中的工厂模式:构建灵活的对象创建机制

    通过本文的介绍,你应该能够理解工厂模式的基本概念,掌握在JavaScript中实现工厂模式的方法,并能够在实际项目中应用这一模式来构建灵活的对象创建机制。 通过本文的介绍,你应该能够理解工厂模式的重要性,掌握在...

    ext内存泄露的两个补兵

    然而,由于JavaScript的垃圾回收机制和EXT框架自身的特性,可能会出现内存泄露的情况。本文将深入探讨EXT内存泄露的问题,并提供两个补救措施。 一、EXT内存泄露的原因 1. 事件监听器:EXT中广泛使用事件监听来...

    [JavaScript权威指南(第6版)]

    《JavaScript权威指南(第6版)》主要讲述的内容涵盖JavaScript语言本身,以及Web浏览器所实现的JavaScript API。本书第6版涵盖了HTML5和ECMAScript 5,很多章节完全重写,增加了当今Web开发的最佳实践的内容,新增...

    总结JavaScript设计模式编程中的享元模式使用

    在JavaScript中实现享元模式时,我们需要关注几个角色: 1. 客户端(Client):客户端是调用享元工厂来获取对象的类,通常指的是应用中需要用到对象的部分。 2. 享元工厂(Flyweight Factory):负责创建和管理享...

    javascript 数组内存释放

    总之,JavaScript中的数组内存释放依赖于垃圾回收机制,正确地解除引用、避免循环引用、使用弱引用以及及时清理定时器和事件监听器,都是有效管理数组内存的方法。理解这些概念并应用到实践中,有助于编写出更高效、...

    Raphaeljs的一个bug(内存泄露)

    在IT行业中,内存泄露是一个非常重要的问题,尤其是在JavaScript这样的语言中,因为它在浏览器环境中没有自动的垃圾回收机制。本文将深入探讨"Raphaeljs的一个bug(内存泄露)"这个主题,结合标签“源码”和“工具”...

Global site tag (gtag.js) - Google Analytics