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

JavaScript 中的内存泄露模式

阅读更多

From dW

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

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

我的浏览器存在泄漏么?
Internet Explorer 和 Mozilla Firefox 是两个与 JavaScript 中的内存泄漏联系最为紧密的浏览器。两个浏览器中造成这种问题的“罪魁祸首”是用来管理 DOM 对象的组件对象模型。本机 Windows COM 和 Mozilla's XPCOM 都使用引用计数的垃圾收集来进行内存分配和检索。引用计数与用于 JavaScript 的标记-清除式的垃圾收集并不总是能相互兼容。本文侧重介绍的是如何应对 JavaScript 代码中的内存泄漏。有关如何处理 Firefox 和 IE 中 COM 层内存泄漏的更多信息,请参看 参考资料

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>






 


结束语

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



参考资料

学习

分享到:
评论

相关推荐

    JavaScript_中的内存泄露模式

    如果您知道内存泄漏的起因,那么在 JavaScript 中进行相应的防范就应该相当容易。在这篇文章中,作者 Kiran Sundar 和 Abhijeet Bhattacharya 将带您亲历 JavaScript 中的循环引用的全部基本知识,向您介绍为何它们...

    JavaScript 内存泄露的4种方式及如何避免 – 码农网1

    常见的JavaScript内存泄露类型有: 1. 意外的全局变量:未声明的变量默认成为全局变量,这可能导致意外的内存占用。例如,忘记在函数内部使用`var`关键字会导致变量被附加到全局对象window上,形成持久化的内存占用...

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

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

    js内存泄露问题

    JavaScript内存泄漏是一个重要的主题,尤其是在开发复杂且性能敏感的Web应用程序时。JavaScript是一种动态类型语言,它在浏览器环境中运行,并依赖于垃圾收集机制来管理内存。然而,由于一些特性及浏览器实现的差异...

    常见的JavaScript内存泄露原因及解决方案.docx

    ### 常见的JavaScript内存泄露原因及解决方案 #### 引言 在现代Web开发中,JavaScript已成为构建复杂用户界面的主要编程语言之一。随着应用程序变得越来越庞大与复杂,有效地管理和优化内存成为确保应用性能和用户...

    权威JavaScript 中的内存泄露模式

    【JavaScript中的内存泄漏模式】 JavaScript是一种广泛用于网页动态交互的脚本语言,它以其便捷性和易用性著称。然而,对于开发人员来说,如果不理解内存管理机制,可能会导致内存泄漏,这会严重影响Web应用程序的...

    JavaScript深入编程网页收集

    JavaScript 中的内存泄露模式_filesJavaScript高级应用(一) - 静 - CSDNBlog_files javascript函数作用域与闭包 - dh20156's New World!_filesjavascript如何避免内存泄露 - - JavaEye技术网站_filesprototype_js...

    藏经阁-穆客带你快速定位 Node.js 内存泄露.pdf

    4. **代码审查**:审查代码,查找可能引起内存泄露的模式,如全局变量、闭包、Buffer管理不当等。 5. **利用工具**:使用如Node.js的--inspect-brk选项配合Chrome DevTools,或者专门的内存分析工具,深入查看内存...

    浅谈js 闭包引起的内存泄露问题

    在JavaScript编程中,闭包是一个非常重要的概念,它的特性使得函数可以访问到外部函数作用域中的变量。然而,闭包如果使用不当,非常容易引起内存泄漏问题。内存泄漏会逐渐消耗计算机的可用内存,进而影响程序的性能...

    万恶的前端内存泄漏及万善的解决方案详解.docx

    虽然别的地方删除了,但是对象中还存在对 dom 的引用,这也会导致内存泄露。 解决方法: * 手动删除,删除对 dom 的引用 四、被遗忘的定时器或者回调 定时器中有 dom 的引用,即使 dom 删除了,但是定时器还在,...

    IE8 内存泄露(内存一直增长 )的原因及解决办法

    6. 使用兼容模式:在必要时,可以考虑使用IE8的IE7兼容模式,看是否能减轻内存泄露问题。 综上所述,IE8内存泄露问题主要是由于特定DOM节点的内存管理bug,开发者需要采取一些针对性的措施来预防和解决这个问题。...

    V8堆快照的内存自动泄漏检测

    在实际应用中,除了利用V8-mat-master这样的工具,还需要了解JavaScript内存管理的基本概念,如垃圾收集机制、闭包、全局变量等,才能更好地理解和解决内存泄漏问题。同时,良好的编程习惯,如及时解除不再使用的...

    Javascript设计模式之观看者模式(推举)_.docx

    观看者模式(Observer Pattern)或称为发布-订阅模式,是这些设计模式中的一种,它提供了一种方法来实现对象之间的松耦合,使得当一个对象的状态发生改变时,所有依赖于它的对象都会得到通知并自动更新。 1. 观看者...

    JSLeaksDetector

    此外,了解JavaScript内存管理的基本原理,例如垃圾回收机制的工作方式,以及如何避免常见的内存泄漏模式,也是每个前端开发者应具备的技能。通过持续学习和实践,开发者可以创建更高效、更稳定的Web应用。

    Memory management(内存管理)

    ### 内存管理在JavaScript开发中的重要性及内存泄漏检测 #### 一、引言 在现代Web开发中,JavaScript已成为构建复杂应用的核心语言之一。然而,随着应用程序规模的不断增大,有效地管理内存成为了保证应用性能和...

    深入理解JavaScript系列

    从第十五篇开始,汤姆大叔详细介绍了JavaScript中的各种功能特性、设计模式以及代码组织方式等内容。这些内容覆盖了从基本概念到高级应用的各个方面,非常适合想要深入了解JavaScript的开发者阅读。例如: - **函数...

    Javascript笔记

    不恰当使用`var`可能导致变量泄露到全局作用域,增加内存消耗和可能的命名冲突。 接下来是一些JavaScript使用技巧: 1. 使用严格模式('use strict')来提高代码质量,防止某些可能引发错误的行为。 2. 尽量避免...

    检测JSP服务器内存的Ajax程

    1. 安全性:确保只有授权的用户可以访问内存信息,防止敏感数据泄露。 2. 性能优化:频繁的内存检测可能会增加服务器负担,应合理设置检测间隔和优化响应数据的大小。 3. 用户界面:在客户端,展示内存使用情况时,...

    基于JavaScript的富客户端表格绘制库开发.pdf

    传统的Web数据呈现框架,如jsp或asp框架,通常在服务器端处理数据转换成HTML标签,然后将完整的HTML文档返回给浏览器,这增加了服务器负载并可能导致内存泄露。此外,由于传输的数据包含大量的HTML标签,网络传输...

    web前端面试题.pdf

    2. **Js内存泄露**:JavaScript中的内存泄露是指程序错误地保留了不再使用的内存,导致内存占用不断增加。常见的内存泄露原因包括全局变量、闭包、事件监听器未解除、定时器未清除等。解决方法包括使用工具检测、...

Global site tag (gtag.js) - Google Analytics