要了解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使用的才是计数器的算法。
。
在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对算法的实现缺陷,文章如是说:
一文中提到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://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
								>
								
										
    
								<
								head
								>
								
										
        
								<
								script language
								=
								"
								JScript
								"
								>
								
										
										
        
								var
								 myGlobalObject;
        
								function
								 SetupLeak() 
								//
								产生循环引用,因此会造成内存泄露
								
										
								
								        {
            
								//
								 First set up the script scope to element reference
								
										
								
								            myGlobalObject 
								=
								
										
                document.getElementById(
								"
								LeakedDiv
								"
								);
            
								//
								 Next set up the element to script scope reference
								
										
								
								            document.getElementById(
								"
								LeakedDiv
								"
								).expandoProperty 
								=
								
										
                myGlobalObject;
        }
        
								function
								 BreakLeak() 
								//
								解开循环引用,解决内存泄露问题
								
										
								
								        {
            document.getElementById(
								"
								LeakedDiv
								"
								).expandoProperty 
								=
								
										
                
								null
								;
        }
        
								</
								script
								>
								
										
    
								</
								head
								>
								
										
										
    
								<
								body onload
								=
								"
								SetupLeak()
								"
								 onunload
								=
								"
								BreakLeak()
								"
								>
								
										
        
								<
								div id
								=
								"
								LeakedDiv
								"
								></
								div
								>
								
										
    
								</
								body
								>
								
										
								
								</
								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
				>
				
						
    
				<
				head
				>
				
						
        
				<
				script language
				=
				"
				JScript
				"
				>
				
						
						
        
				function
				 AttachEvents(element)
        {
            
				//
				 This structure causes element to ref ClickEventHandler  //element有个引用指向函数ClickEventHandler()
				
						
				
				            element.attachEvent(
				"
				onclick
				"
				, ClickEventHandler);
            
				function
				 ClickEventHandler()
            {
                
				//
				 This closure refs element  //该函数有个引用指向AttachEvents(element)调用Scope,也就是执行了参数element。
				
						
				
				                
            }
        }
        
				function
				 SetupLeak()
        {
            
				//
				 The leak happens all at once
				
						
				
				            AttachEvents(document.getElementById(
				"
				LeakedDiv
				"
				));
        }
        
				</
				script
				>
				
						
    
				</
				head
				>
				
						
						
    
				<
				body onload
				=
				"
				SetupLeak()
				"
				 onunload
				=
				"
				BreakLeak()
				"
				>
				
						
        
				<
				div id
				=
				"
				LeakedDiv
				"
				></
				div
				>
				
						
    
				</
				body
				>
				
						
				
				</
				html
				>
		
		
		还有这个例子在IE 6中同样原因会引起泄露
		
				
				
						
						
				
				function
				 leakmaybe() {
				var
				 elm 
				=
				 document.createElement(
				"
				DIV
				"
				);
  elm.onclick 
				=
				 
				function
				() {
				return
				 
				2
				 
				+
				 
				2
				;
  }
}
				for
				 (
				var
				 i 
				=
				 
				0
				; i  
				10000
				; i
				++
				) {
  leakmaybe();
}
		
		
		btw:
关于Closure的知识,大家可以看看http://jibbering.com/faq/faq_notes/closures.html 这篇文章,习惯中文也可以看看zkjbeyond的blog,他对Closure这篇文章进行了简要的翻译:http://www.blogjava.net/zkjbeyond/archive/2006/05/19/47025.html
这篇文章,习惯中文也可以看看zkjbeyond的blog,他对Closure这篇文章进行了简要的翻译:http://www.blogjava.net/zkjbeyond/archive/2006/05/19/47025.html 。
之所以会有这一系列的问题,关键就在于javascript是种函数式脚本解析语言,因此javascript中“函数中的变量的作用域是定义作用域,而
不是动态作用域”,这点在犀牛书《JavaScript: The Definitive Guide》中的“Funtion”一章中有所讨论。
。
之所以会有这一系列的问题,关键就在于javascript是种函数式脚本解析语言,因此javascript中“函数中的变量的作用域是定义作用域,而
不是动态作用域”,这点在犀牛书《JavaScript: The Definitive Guide》中的“Funtion”一章中有所讨论。
http://support.microsoft.com/default.aspx?scid=KB;EN-US;830555 中也对这个问题举了很详细的例子。
中也对这个问题举了很详细的例子。
		
				
						
						
				
		
		
		
		
				
						
						
				
		
		目前大多数ajax前端的javascript framework都利用对事件的管理,解决了该问题。
		如果你需要自己解决这个问题,可以参考以下的一些方法:
		
		
		
				- 
						而http://novemberborn.net/javascript/event-cache 一文中则通过增加EventCache,从而给出一个相对结构化的解决方案 一文中则通过增加EventCache,从而给出一个相对结构化的解决方案
 
 
								
								/*
								    EventCache Version 1.0
 Copyright 2005 Mark Wubben
 
 Provides a way for automagically removing events from nodes and thus preventing memory leakage.
 See <http://novemberborn.net/javascript/event-cache> for more information.
 
 This software is licensed under the CC-GNU LGPL <http://creativecommons.org/licenses/LGPL/2.1/>
 */
 
 /*
								    Implement array.push for browsers which don't support it natively.
 Please remove this if it's already in other code 
								*/
 if
								(Array.prototype.push 
								==
								 
								null
								){
 Array.prototype.push 
								=
								 
								function
								(){
 for
								(
								var
								 i 
								=
								 
								0
								; i 
								<
								 arguments.length; i
								++
								){
 this
								[
								this
								.length] 
								=
								 arguments[i];
 };
 return
								 
								this
								.length;
 };
 };
 
 /*
								    Event Cache uses an anonymous function to create a hidden scope chain.
 This is to prevent scoping issues. 
								*/
 var
								 EventCache 
								=
								 
								function
								(){
 var
								 listEvents 
								=
								 [];
 
 return
								 {
 listEvents : listEvents,
 
 add : 
								function
								(node, sEventName, fHandler, bCapture){
 listEvents.push(arguments);
 },
 
 flush : 
								function
								(){
 var
								 i, item;
 for
								(i 
								=
								 listEvents.length 
								-
								 
								1
								; i 
								>=
								 
								0
								; i 
								=
								 i 
								-
								 
								1
								){
 item 
								=
								 listEvents[i];
 
 if
								(item[
								0
								].removeEventListener){
 item[
								0
								].removeEventListener(item[
								1
								], item[
								2
								], item[
								3
								]);
 };
 
 /*
								 From this point on we need the event names to be prefixed with 'on" 
								*/
 if
								(item[
								1
								].substring(
								0
								, 
								2
								) 
								!=
								 
								"
								on
								"
								){
 item[
								1
								] 
								=
								 
								"
								on
								"
								 
								+
								 item[
								1
								];
 };
 
 if
								(item[
								0
								].detachEvent){
 item[
								0
								].detachEvent(item[
								1
								], item[
								2
								]);
 };
 
 item[
								0
								][item[
								1
								]] 
								=
								 
								null
								;
 };
 }
 };
 }();
 
 
- 
						使用方法也很简单: 
								
										
												<script type= "text/javascript"> function addEvent(oEventTarget, sEventType, fDest){
 if(oEventTarget.attachEvent){
 oEventTarget.attachEvent("on" + sEventType, fDest);
 } elseif(oEventTarget.addEventListener){
 oEventTarget.addEventListener(sEventType, fDest, true);
 } elseif(typeof oEventTarget[sEventType] == "function"){
 var fOld = oEventTarget[sEventType];
 oEventTarget[sEventType] = function(e){ fOld(e); fDest(e); };
 } else {
 oEventTarget[sEventType] = fDest;
 };
 
 /* Implementing EventCache for all event systems */
 EventCache.add(oEventTarget, sEventType, fDest, true);
 };
 
 
 function createLeak(){
 var body = document.body;
 
 function someHandler(){
                return body;
 
 };
 
 addEvent(body, "click", someHandler);
 };
 
 window.onload = function(){
 var i = 500;
 while(i > 0){
 createLeak();
 i = i - 1;
 }
 };
 
 window.onunload = EventCache.flush;
 </script>
 
 
 
				- 
						
								
										http://talideon.com/weblog/2005/03/js-memory-leaks.cfm 一文中的方法类似: 一文中的方法类似:
 
								
								/*
								
										
 * EventManager.js
 * by Keith Gaughan
 *
 * This allows event handlers to be registered unobtrusively, and cleans
 * them up on unload to prevent memory leaks.
 *
 * Copyright (c) Keith Gaughan, 2005.
 *
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Common Public License v1.0
 * (CPL) which accompanies this distribution, and is available at
 * http://www.opensource.org/licenses/cpl.php
 *
 * This software is covered by a modified version of the Common Public License
 * (CPL), where Keith Gaughan is the Agreement Steward, and the licensing
 * agreement is covered by the laws of the Republic of Ireland.
 */
 
 //
								 For implementations that don't include the push() methods for arrays.
 if
								 (
								!
								Array.prototype.push) {
 Array.prototype.push 
								=
								 
								function
								(elem) {
 this
								[
								this
								.length] 
								=
								 elem;
 }
 }
 
 var
								 EventManager 
								=
								 {
 _registry: 
								null
								,
 
 Initialise: 
								function
								() {
 if
								 (
								this
								._registry 
								==
								 
								null
								) {
 this
								._registry 
								=
								 [];
 
 //
								 Register the cleanup handler on page unload.
 EventManager.Add(window, 
								"
								unload
								"
								, 
								this
								.CleanUp);
 }
 },
 
 /*
								*
 * Registers an event and handler with the manager.
 *
 * @param  obj         Object handler will be attached to.
 * @param  type        Name of event handler responds to.
 * @param  fn          Handler function.
 * @param  useCapture  Use event capture. False by default.
 *                     If you don't understand this, ignore it.
 *
 * @return True if handler registered, else false.
 */
 Add: 
								function
								(obj, type, fn, useCapture) {
 this
								.Initialise();
 
 //
								 If a string was passed in, it's an id.
 if
								 (
								typeof
								 obj 
								==
								 
								"
								string
								"
								) {
 obj 
								=
								 document.getElementById(obj);
 }
 if
								 (obj 
								==
								 
								null
								 
								||
								 fn 
								==
								 
								null
								) {
 return
								 
								false
								;
 }
 
 //
								 Mozilla/W3C listeners?
 if
								 (obj.addEventListener) {
 obj.addEventListener(type, fn, useCapture);
 this
								._registry.push({obj: obj, type: type, fn: fn, useCapture: useCapture});
 return
								 
								true
								;
 }
 
 //
								 IE-style listeners?
 if
								 (obj.attachEvent 
								&&
								 obj.attachEvent(
								"
								on
								"
								 
								+
								 type, fn)) {
 this
								._registry.push({obj: obj, type: type, fn: fn, useCapture: 
								false
								});
 return
								 
								true
								;
 }
 
 return
								 
								false
								;
 },
 
 /*
								*
 * Cleans up all the registered event handlers.
 */
 CleanUp: 
								function
								() {
 for
								 (
								var
								 i 
								=
								 
								0
								; i 
								<
								 EventManager._registry.length; i
								++
								) {
 with
								 (EventManager._registry[i]) {
 //
								 Mozilla/W3C listeners?
 if
								 (obj.removeEventListener) {
 obj.removeEventListener(type, fn, useCapture);
 }
 //
								 IE-style listeners?
 else
								 
								if
								 (obj.detachEvent) {
 obj.detachEvent(
								"
								on
								"
								 
								+
								 type, fn);
 }
 }
 }
 
 //
								 Kill off the registry itself to get rid of the last remaining
 //
								 references.
 EventManager._registry 
								=
								 
								null
								;
 }
 };
 
 使用起来也很简单 
								
										
												
<html> <head>
 <script type=text/javascript src=EventManager.js></script>
 <script type=text/javascript>
 function onLoad() {
 
 EventManager.Add(document.getElementById(testCase),click,hit );
 returntrue;
 }
 
 function hit(evt) {
 alert(click);
 }
 </script>
 </head>
 
 <body onload='javascript: onLoad();'>
 
 <div id='testCase' style='width:100%; height: 100%; background-color: yellow;'>
 <h1>Click me!</h1>
 </div>
 
 </body>
 </html>
 
 
 
 
				- 
						google map api同样提供了一个类似的函数用在页面的unload事件中,解决Closure带来的内存泄露问题。
    Cross-Page Leaks和下一节提到的Pseudo-Leaks在我看来,就是IE的bug, 虽然MS死皮赖脸不承认:)
		     大家可以看看这段例子代码:
		
		
		
				
				<
				html
				>
				
						
    
				<
				head
				>
				
						
        
				<
				script language
				=
				"
				JScript
				"
				>
				
						
						
        
				function
				 LeakMemory() 
				//
				这个函数会引发Cross-Page Leaks
				
						
				
				        {
            
				var
				 hostElement 
				=
				 document.getElementById(
				"
				hostElement
				"
				);
            
				//
				 Do it a lot, look at Task Manager for memory response
				
						
				
				
						
            
				for
				(i 
				=
				 
				0
				; i 
				<
				 
				5000
				; i
				++
				)
            {
                
				var
				 parentDiv 
				=
				
						
                    document.createElement(
				"
				<div onClick='foo()'>
				"
				);
                
				var
				 childDiv 
				=
				
						
                    document.createElement(
				"
				<div onClick='foo()'>
				"
				);
                
				//
				 This will leak a temporary object
				
						
				
				                parentDiv.appendChild(childDiv);
                hostElement.appendChild(parentDiv);
                hostElement.removeChild(parentDiv);
                parentDiv.removeChild(childDiv);
                parentDiv 
				=
				 
				null
				;
                childDiv 
				=
				 
				null
				;
            }
            hostElement 
				=
				 
				null
				;
        }
        
				function
				 CleanMemory() 
				//
				而这个函数不会引发Cross-Page Leaks
				
						
				
				        {
            
				var
				 hostElement 
				=
				 document.getElementById(
				"
				hostElement
				"
				);
            
				//
				 Do it a lot, look at Task Manager for memory response
				
						
				
				
						
            
				for
				(i 
				=
				 
				0
				; i 
				<
				 
				5000
				; i
				++
				)
            {
                
				var
				 parentDiv 
				=
				  document.createElement(
				"
				<div onClick='foo()'>
				"
				);
                
				var
				 childDiv 
				=
				  document.createElement(
				"
				<div onClick='foo()'>
				"
				);
                
				//
				 Changing the order is important, this won't leak
				
						
				
				                hostElement.appendChild(parentDiv);
                parentDiv.appendChild(childDiv);
                hostElement.removeChild(parentDiv);
                parentDiv.removeChild(childDiv);
                parentDiv 
				=
				 
				null
				;
                childDiv 
				=
				 
				null
				;
            }
            hostElement 
				=
				 
				null
				;
        }
        
				</
				script
				>
				
						
    
				</
				head
				>
				
						
						
    
				<
				body
				>
				
						
        
				<
				button onclick
				=
				"
				LeakMemory()
				"
				>
				Memory Leaking Insert
				</
				button
				>
				
						
        
				<
				button onclick
				=
				"
				CleanMemory()
				"
				>
				Clean Insert
				</
				button
				>
				
						
        
				<
				div id
				=
				"
				hostElement
				"
				></
				div
				>
				
						
    
				</
				body
				>
				
						
				
				</
				html
				>
		
		
		
				
						LeakMemory和CleanMemory这两段函数的唯一区别就在于他们的代码的循序,从代码上看,两段代码的逻辑都没有错。
		
		
但LeakMemory却会造成泄露。原因是LeakMemory()会先建立起parentDiv和childDiv之间的连接,这时候,为了让
childDiv能够获知parentDiv的信息,因此IE需要先建立一个临时的scope对象。而后parentDiv建立了和
hostElement对象的联系,parentDiv和childDiv直接使用页面document的scope。可惜的是,IE不会释放刚才那个临
时的scope对象的内存空间,直到我们跳转页面,这块空间才能被释放。而CleanMemory函数不同,他先把parentDiv和
hostElement建立联系,而后再把childDiv和parentDiv建立联系,这个过程不需要单独建立临时的scope,只要直接使用页面
document的scope就可以了, 所以也就不会造成内存泄露了
		详细原因,大家可以看看http://msdn.microsoft.com/library/default.asp?url=/library/en-us/IETechCol/dnwebgen/ie_leak_patterns.asp 这篇文章。
这篇文章。
		btw:
IE 6中垃圾回收算法,就是从那些直接"in scope"的对象开始进行mark清除的:
Every variable which is "in scope" is called a "scavenger". A scavenger
may refer to a number, an object, a string, whatever. We maintain a
list of scavengers – variables are moved on to the scav list when they
come into scope and off the scav list when they go out of scope.
		
		这个被称为“秀逗泄露”真是恰当啊:)
看看这个例子:
				
									
		
				
				<
				html
				>
				
						
    
				<
				head
				>
				
						
        
				<
				script language
				=
				"
				JScript
				"
				>
				
						
						
        
				function
				 LeakMemory()
        {
            
				//
				 Do it a lot, look at Task Manager for memory response
				
						
				
				
						
            
				for
				(i 
				=
				 
				0
				; i 
				<
				 
				5000
				; i
				++
				)
            {
                hostElement.text 
				=
				 
				"
				function foo() { }
				"
				;//看内存会不断增加
            }
        }
        
				</
				script
				>
				
						
    
				</
				head
				>
				
						
						
    
				<
				body
				>
				
						
        
				<
				button onclick
				=
				"
				LeakMemory()
				"
				>
				Memory Leaking Insert
				</
				button
				>
				
						
        
				<
				script id
				=
				"
				hostElement
				"
				>
				function
				 foo() { }
				</
				script
				>
				
						
    
				</
				body
				>
				
						
				
				</
				html
				>
		
		MS是这么解释的,这不是内存泄漏。如果您创建了许多无法获得也无法释放的对象,那才是内存泄漏。在这里,您将创建许多元素,Internet
Explorer 需要保存它们以正确呈现页面。Internet Explorer
并不知道您以后不会运行操纵您刚刚创建的所有这些对象的脚本。当页面消失时(当您浏览完,离开浏览器时)会释放内存。它不会泄漏。当销毁页面时,会中断循
环引用。 
		唉~~~
		详细原因,大家可以看看http://msdn.microsoft.com/library/default.asp?url=/library/en-us/IETechCol/dnwebgen/ie_leak_patterns.asp 这篇文章。
这篇文章。
		
		变量定义一定要用var,否则隐式声明出来的变量都是全局变量,不是局部变量;
全局变量没用时记得要置null;
注意正确使用delete,删除没用的一些函数属性;
注意正确使用try...cache,确保去处无效引用的代码能被正确执行;
open出来的窗口即使close了,它的window对象还是存在的,要记得删除引用;
frame和iframe的情况和窗口的情况类似。
		
		
				
						http://jibbering.com/faq/faq_notes/closures.html 
				
				
				
						http://javascript.weblogsinc.com/2005/03/07/javascript-memory-leaks/ 
				
				
				
						http://msdn.microsoft.com/library/default.asp?url=/library/en-us/IETechCol/dnwebgen/ie_leak_patterns.asp 
				
				
				
						http://72.14.203.104/search?q=cache:V9Bt4_HBzQ8J:jgwebber.blogspot.com/2005/01/dhtml-leaks-like-sieve.html+DHTML+Leaks+Like+a+Sieve+&hl=zh-CN&ct=clnk&cd=9 (这是DHTML Leaks Like a Sieve)一文在google上的cache,原文已经连不上了)
				  (这是DHTML Leaks Like a Sieve)一文在google上的cache,原文已经连不上了)
http://spaces.msn.com/siteexperts/Blog/cns!1pNcL8JwTfkkjv4gg6LkVCpw!338.entry
http://support.microsoft.com/default.aspx?scid=KB;EN-US;830555
http://www.ajaxtopics.com/leakpatterns.html
http://blogs.msdn.com/ericlippert/archive/2003/09/17/53028.aspx
http://www.quirksmode.org/blog/archives/2005/02/javascript_memo.html
http://youngpup.net/2005/0221010713
http://blogs.msdn.com/ericlippert/archive/2003/09/17/53038.aspx =
 =
http://support.microsoft.com/kb/266071/EN-US ==>IE 5.0至5.5一些版本中的GC bug
 ==>IE 5.0至5.5一些版本中的GC bug
http://www.quirksmode.org/blog/archives/2006/04/ie_7_and_javasc.html ==>ie 7的改进
 ==>ie 7的改进
http://erik.eae.net/archives/2006/04/26/23.23.02/ ==>ie 7的改进
 ==>ie 7的改进
http://www.feedbackarchive.com/spamvampire/today.html ==> Try this script for memory leaks - it leaked 50 megabytes in 15 minutes with firefox on linux:
 ==> Try this script for memory leaks - it leaked 50 megabytes in 15 minutes with firefox on linux:
http://birdshome.cnblogs.com/archive/2005/02/15/104599.html
http://www.quirksmode.org/dom/innerhtml.html
http://www.crockford.com/javascript/memory/leak.html
《JavaScript: The Definitive Guide》4th Edition
http://outofhanwell.com/ieleak/index.php?title=Main_Page