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-->DOMelement.expandoProperty = this;}function Leak() {//This code will leaknew myFunction(document.getElementById("myDiv"));      }</script>
</head>
<body οnlοad="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.οnlοad=      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 xx 进行调用时,即 x(“inner x”),将会弹出警告消息,表明 “outer x innerx”。

简要解释了 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.οnlοad=function outerFunction(){var obj = document.getElementById("element");obj.οnclick=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.οnlοad=function outerFunction(){var obj = document.getElementById("element");obj.οnclick=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.οnlοad=function outerFunction(){var anotherObj = function innerFunction()            {// Some logic herealert("Hi! I have avoided the leak");}; (function anotherInnerFunction(){ var obj = document.getElementById("element"); obj.οnclick=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.οnlοad=function()     {var obj = document.getElementById("element");obj.onclick = doesNotLeak;}function doesNotLeak()    {//Your Logic herealert("Hi! I have avoided the leak");}</script></head><body><button id="element">"Click Here"</button></body>
</html>

结束语

本文解释了循环引用是如何导致 JavaScript 中的内存泄漏的 —— 尤其是在结合了闭包的情况下。您还了解了涉及到循环引用的一些常见内存泄漏模式以及应对这些泄漏模式的几种简单方式。有关本文所讨论的主题的更多信息

JavaScript 中的内存泄露模式相关推荐

  1. 理解在javascript中的内存泄露

    在处理像javascript这类的脚本语言时,很容易忽视对象,实例,字符串,数字和方法都需要分配内存.分配与重新分配都被脚本语言本身和运行时的垃圾回收器隐藏. 没有内存管理,也许你可以完成许多事情.但 ...

  2. 魔鬼的梦魇—验证IE中的js内存泄露模式(三)

    魔鬼的梦魇-验证IE中的js内存泄露模式(三) 按照Justin Rogers文章的顺序,接下来的这个模式应该是跨页内存泄露模式(cross-page leak),但是由于这个模式产生的中间对象,我们 ...

  3. 梦魇java_[Java教程]魔鬼的梦魇—验证IE中的JS内存泄露模式(一)

    [Java教程]魔鬼的梦魇-验证IE中的JS内存泄露模式(一) 0 2012-05-08 07:00:04 随着移动互联网的发展,现在越来越多的应用开始想bs方向转移,原来很多复杂的计算逻辑也自然随着 ...

  4. javascript中的内存管理

    文章目录 简介 内存生命周期 JS中的垃圾回收器 引用计数垃圾回收算法 Mark-and-sweep回收算法 调试内存问题 闭包Closures中的内存泄露 简介 在c语言中,我们需要手动分配和释放对 ...

  5. 说说JS在IE中的内存泄露问题

    首先说说什么是内存泄露,在一个进程中,如果某一块内存无法访问,且直到进程结束为止也无法释放,那么就发生了内存泄露.通常这种情况发生在C++之类的手动管理内存的语言编写的程序中,程序员忘记delete或 ...

  6. 如何避免 JavaScript 中的内存泄漏?

    大家好,我是CUGGZ.SPA(单页应用程序)的兴起,促使我们更加关注与内存相关的 JavaScript 编码实践.如果应用使用的内存越来越多,就会严重影响性能,甚至导致浏览器的崩溃.下面就来看看Ja ...

  7. Javascript中理解发布--订阅模式

    Javascript中理解发布--订阅模式 阅读目录 发布订阅模式介绍 如何实现发布--订阅模式? 发布---订阅模式的代码封装 如何取消订阅事件? 全局--发布订阅对象代码封装 理解模块间通信 回到 ...

  8. vs调试c语言检查内存泄露,VisualStudio中检查内存泄露方法

    项目工程中存在内存泄露,被折磨了一晚上,终于查了出来,因为之前没有相关的经验,还比较生疏,在此记录下来,方便以后查找. 对于malloc出的内存的检测方法 这篇文章中详细地记录了从检查到找到确定位置到 ...

  9. LeakCanary——消除Android中的内存泄露

    2019独角兽企业重金招聘Python工程师标准>>> ##LeakCanary ####简介 LeakCanary是Square公司最近公布的开源项目,旨在消除Android中的内 ...

最新文章

  1. [Manthan, Codefest 18][Codeforces 1037E. Trips]
  2. 防止非法链接(referer)
  3. linux-Tcp IP协议栈源码阅读笔记
  4. Ext之Combobox的远程加载数据实例(附前后台代码)
  5. 七十四、滑动窗口最值问题
  6. nginx php7 fastcgi,[Mac php7 nginx]解决nginx FastCGI sent in stderr: “Primary script unknown”
  7. ApacheCN 安卓译文集 20211225 更新
  8. Cent OS 6.X 开机错误修复
  9. 扬州大学c语言上机作业答案,扬州大学C语言上机作业1-9整理
  10. python 生存分析,利用python进行泰坦尼克生存预测——数据探索分析
  11. SitePoint Podcast#165:您说的是缓存,我说的是Caché
  12. shell卸载 simatic_西门子软件在WIN7操作系统中安装步骤和须知
  13. 【kafka专栏】集成apache kafka-clients实现数据消费者
  14. Linux系统的基本使用指南(速成,帮助快速上手使用Linux系统)
  15. 植树节种树-第12届蓝桥杯Scratch选拔赛真题精选
  16. python字符串这一篇就够了
  17. 码农深耕 - 35岁的程序员何去何从?
  18. 十分钟学python-【译】10分钟学会Pandas
  19. mysql ssl连接错误_MySQL 5.1.66 SSL连接错误ERROR 2026(HY000)
  20. [python] 圆形嵌套图Circular Packing

热门文章

  1. nodejs+express+mongodb简单的例子
  2. (转)IOS学习笔记-2015-03-29 int、long、long long取值范围
  3. vs2013 编译 notepad++ 源代码 2014-7-23
  4. 爱上MVC3系列~同步与异步提交,在过滤器里如何进行重定向~续
  5. 【转】C# DateTime 日期计算
  6. C语言运算符优先级 详细列表
  7. “error LNK1169: 找到一个或多个多重定义的符号”的解决方法
  8. NYOJ 35 表达式求值
  9. hdu 1418 抱歉 (欧拉公式)
  10. reduction_indices的用法