浅议javascript的内存泄露

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

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

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

我的浏览器存在泄漏么?

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

js 中的内存泄漏

js 是一种垃圾收集式语言,这就是说,内存是根据对象的创建分配给该对象的,并会在没有对该对象的引用时由浏览器收回。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 中的闭包

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

清单 3. 一个内部函数

	function parentFunction(paramA)
	{
    		var a = paramA;
    		function childFunction()
    		{
			return a + 2;
    		}
    		return childFunction();
	}

js开发人员使用内部函数来在其他函数中集成小型的实用函数。如清单 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++ 中,一旦函数返回,本地变量也将不复存在。在 js中,在调用 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 中的内存泄漏的 —— 尤其是在结合了闭包的情况下。您还了解了涉及到循环引用的一些常见内存泄漏模式以及应对这些泄漏模式的几种简单方式。有关本文所讨论的主题的更多信息,

时间: 2024-10-02 10:13:16

浅议javascript的内存泄露的相关文章

JavaScript避免内存泄露及内存管理技巧_javascript技巧

本文实例讲述了JavaScript避免内存泄露及内存管理技巧,非常实用.分享给大家供大家参考之用.具体方法如下: 本文内容源自谷歌WebPerf(伦敦WebPerf集团),2014年8月26日. 一般来说,高效的JavaScript Web应用必须流畅,快速.与用户交互的任何应用程序,都需要考虑如何确保内存有效使用,因为如果消耗过多,页面就会崩溃,迫使用户重新加载.而你只能躲在角落哭泣. 自动垃圾收集是不能代替有效的内存管理的,特别是在大型,长时间运行的Web应用程序中.本文中,我们将演示如何通

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

  这篇文章主要介绍了浅谈js 闭包引起的内存泄露问题的相关资料,需要的朋友可以参考下 在js闭包中,可以定义"局部变量",但是外部去调用的话,尤其是反复调用赋值,会造成内存的大量开销.如何防止这种现象的发生?关于闭包还有没有类似的内存或效率问题需要注意?如何去规避? 内存问题可能是如下原因造成: 1. 循环引用导致了内存泄漏 2. 由外部函数调用引起的内存泄漏 避免内存泄漏 1. 打破循环引用 2. 添加另一个闭包 3. 避免闭包自身 以上所述就是本文的全部内容了,希望大家能够喜欢.

如何处理JavaScript内存泄露

几周前,我们开始写一个系列,深入探讨JavaScript和它的工作原理.我们认为了解JavaScript的构成以及它们如何协作,有助于编写出更好的代码和应用程序. 本系列第一篇重点介绍了引擎.运行时.调用栈.第二篇揭示了谷歌V8 JavaScript引擎的内部机制,并且提供了一些关于如何写出更好的JavaScript代码的建议. 本文作为第三篇,将会讨论另一个开发者容易忽视的重要主题 :内存管理.我们也会提供一些关于如何处理JavaScript内存泄露的技巧.在SessionStack,我们需要

JavaScript内存泄露的4种方式及如何避免

简介 内存泄露是每个开发者最终都要面对的问题,它是许多问题的根源:反应迟缓,崩溃,高延迟,以及其他应用问题. 什么是内存泄露? 本质上,内存泄露可以定义为:应用程序不再需要占用内存的时候,由于某些原因,内存没有被操作系统或可用内存池回收.编程语言管理内存的方式各不相 同.只有开发者最清楚哪些内存不需要了,操作系统可以回收.一些编程语言提供了语言特性,可以帮助开发者做此类事情.另一些则寄希望于开发者对内存是否需 要清晰明了. JavaScript 内存管理 JavaScript 是一种垃圾回收语言

找出并解决 JavaScript 和 Dojo 引起的浏览器内存泄露问题

简介: 如果大量使用 JavaScript 和 Ajax 技术开发 Web 2.0 应用程序,您很有可能会遇到浏览器的内存泄漏问题.如果您有一个单页应用程序或者一个页面要处理很多 UI 操作,问题可能比较严重.在本文中,学习如何使用 sIEve 工具检测并解决内存泄漏问题,本文也包含内存泄漏问题的应用示例以及解决方案. 发布日期: 2012 年 4 月 09 日 级别: 中级 原创语言: 英文 访问情况 : 10932 次浏览 评论: 0 (查看 | 添加评论 - 登录)  平均分 (7个评分)

用 JavaScript 检测 CPU 占比和内存泄露

作者:玉伯   最近在项目中碰到 IE6-7 下的内存泄露,通过 Drip 能探测出来,问题也解决了.最近小组成员同时有在做前端质量工具,通过性能检测,可以排查出一些耗时较长的代码,但对内存泄露想不到好的自动化探测方式.本着集思广益的初衷,发了条微博: 通过 setTimeout 等方式,可以检测当前页面所在操作系统 CPU 的大体情况.请教万能的微博:有没有什么办法,通过 JavaScript 检测到当前页面所在操作系统的内存使用情况(比如是否持续上涨.存在内存泄露)?   CPU 占比探测

容易造成JavaScript内存泄露几个方面_javascript技巧

发表于谷歌WebPerf(伦敦WebPerf集团),​​2014年8月26日. 高效的JavaScript Web应用必须流畅,快速.与用户交互的任何应用程序,都需要考虑如何确保内存有效使用,因为如果消耗过多,页面就会崩溃,迫使用户重新加载.而你只能躲在角落哭泣. 自动垃圾收集是不能代替有效的内存管理的,特别是在大型,长时间运行的Web应用程序中.在这次讲座中,我们将演示如何通过Chrome的DevTools对内存进行有效的管理. 并了解如何解决性能问题,如内存泄漏,频繁的垃圾收集暂停,和整体内

Javascript 闭包引起的IE内存泄露分析_javascript技巧

复制代码 代码如下: function fors(){ obj_a = obj_b; obj_b.attr = obj_a; } 复制代码 代码如下: function fors(){ obj_b = {}; obj_b.attr = obj_b; } 上面是两个个很显示的循环引用,IE中产生了内存泄露,由于IE的内存回收机制,导至会长期占用内存而不能释放. 但闭包的内存泄露,有些隐蔽.因为闭包的循环引用,是间接的. 复制代码 代码如下: function iememery(){ var js_

浅谈Java编程中的内存泄露情况_java

必须先要了解的 1.c/c++是程序员自己管理内存,Java内存是由GC自动回收的. 我虽然不是很熟悉C++,不过这个应该没有犯常识性错误吧. 2.什么是内存泄露? 内存泄露是指系统中存在无法回收的内存,有时候会造成内存不足或系统崩溃. 在C/C++中分配了内存不释放的情况就是内存泄露. 3.Java存在内存泄露 我们必须先承认这个,才可以接着讨论.虽然Java存在内存泄露,但是基本上不用很关心它,特别是那些对代码本身就不讲究的就更不要去关心这个了. Java中的内存泄露当然是指:存在无用但是垃