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

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

阅读更多

简介

一般来说,浏览器的内存泄漏对于 web 应用程序来说并不是什么问题。用户在页面之间切换,每个页面切换都会引起浏览器刷新。即使页面上有内存泄漏,在页面切换后泄漏就解除了。由于泄漏的范围比较小,因此常常被忽视。

Ajax 技术引入后,内存泄漏就成了一个比较严重的问题。在 web 2.0 样式页面上,用户不需要经常刷新页面。Ajax 技术用于异步更新页面内容。特殊场景中,整个 web 应用程序构建在一个页面上。在这种情况下泄漏会被累积,不能忽略。

在本文中,了解内存泄漏是怎样发生的,以及如何通过 sIEve 找到泄漏的源头。这些问题和解决方案的的实际示例可以帮助您探究问题。您可以 下载 本文示例源代码。

使用 JavaScript 和 Dojo 工具包的经验有助于您理解这篇文章,但并不是必需的。

泄漏模式

如 web 开发人员所知道的,IE 不同于 Firefox 和其他的浏览器。本文所讨论的内存泄漏模式和问题主要是针对 IE 浏览器的,但不限于 IE。好的方法应该是适用于所有的浏览器的。

IE 怎样管理内存的话题不在本文范围内,参考资料 中有更多信息。

由于 JavaScript 的本质和 JavaScript 和 DOM 对象的浏览器内存管理,JavaScript 编码不慎导致了浏览器的内存泄露。造成了这些泄露的有两种常见的模式。

循环引用
循环引用几乎是每种泄露的根本原因。一般来说,IE 浏览器可以处理循环引用,并将它们正确放置在 JavaScript 环境中。当 DOM 对象被引入时会发生异常。当 JavaScript 对象引用 DOM 元素并且 DOM 元素的属性引用 JavaScript 对象时,循环应用发生并导致 DOM 节点泄露。 清单 1 是一个代码样例,通常用于在文章中演示内存泄漏问题。


清单 1. 循环引用引起的泄露

							
var obj = document.getElementById("someLeakingDIV");
document.getElementById("someLeakingDiv").expandoProperty = obj;

 

为了解决这个问题,当您准备把节点移出文档时,一定要将 expandoProperty 设置为空。

闭包
闭包会导致泄露,因为它们会不经意的引起循环引用。当闭包存在的时候,母函数的变量会一直被引用。变量的生命周期超越了函数的作用域,如果处理不当会引起泄露。清单 2 展示了由闭包引起的泄露,这是 JavaScript 的通用编码风格。


清单 2. 泄露的闭包

							
      
   <html>
<head>
<script type="text/javascript">
window.onload = function() {
    var obj = document.getElementById("element");
    // this creates a closure over "element"
    // and will leak if not handled properly.
    obj.onclick = function(evt) {
        alert("leak the element DIV");
    };
};
</script>
</head>
<body>
<div id="element">Leaking DIV</div>
</body>
</html>

 

如果您使用 sIEve — 一个检测孤立节点和内存泄漏的工具 — 您会发现元素 DIV 被引用了两次。其中一个引用是闭包持有的(匿名函数指定给 onclick 事件) 并且即使您删除了节点,也不会被检测到。如果您的应用程序之后删除了 element 节点,JavaScript 引用仍然会持有孤立节点。这个孤立节点将会造成内存泄露。

了解闭包为什么会产生循环引用是非常重要的。文章 “重访 IE 浏览器时的内存泄露” 中的图表清楚地说明了这个问题,并在图 1 中进行了演示。

解决问题的一个方法就是删除闭包。


图 1. 在 DOM 和 JavaScript 之间创建循环引用的闭包
图 1. 在 DOM 和 JavaScript 之间创建循环引用的闭包 

sIEve 简介

sIEve 是一个帮助检测内存泄露的工具。您可以从 参考资料 中下载 sIEve 和访问文档。主 sIEve 窗口如 图 2 所示。


图 2. sIEve 主窗口
图 2. sIEve 主窗口 

单击 Show in use 时,这个工具非常有用的。您将看到使用的所有 DOM 节点,包括孤立节点和 DOM 节点增加或减少的引用。

图 3 是一个样例视图。泄露的原因如下:

  • 孤立节点,在 Orphan 这一列被标记为 “YES” 。
  • 对 DOM 节点增加的不正确引用,显示蓝色。

使用 sIEve 找到泄露节点并查看修复它们的代码。


图 3. sIEve:使用的 DOM 节点
图 3. sIEve:使用的 DOM 节点 

使用 sIEve 找到泄露节点

通过以下步骤检测泄露节点。

  1. 通过您的 web 应用程序的 URL 启动 sIEve。
  2. 单击 Scan Now 寻找当前文档中使用的所有 DOM 节点(可选)。
  3. 单击 Show in use 查看所有 DOM 节点。在这里,所有节点将以红色标识(新条目),因为您刚刚开始。
  4. 使用 web 应用程序的一些功能,测试是否有泄漏。
  5. 单击 Scan Now 刷新使用的 DOM 节点(可选)。
  6. 单击 Show in use。现在,视图中含有一些有趣的信息。可在此找到孤立节点,或者对某个 DOM 节点的异常引用不断增加。
  7. 分析报告并检查您的代码。
  8. 必要时,重复步骤 4-8。

sIEve 不能找出您的应用程序中的所有泄露,但是它能找出由子节点造成的泄露。其他的一些信息,例如 ID 和 outerHTML 可以帮助您指出泄露节点。查看控制泄露节点的代码并相应的作出修改。

应用示例

这一部分包含更多示可引起内存泄露的示例。这些样例以及最佳实践虽然是基于 Dojo 工具包的,但是大多数示例在普通 JavaScript 编程中是有效的。

虽然有很多方法进行清理,但最常见的方法是删除 DOM 以及 JavaScript 对象以避免内存泄露。本节的其余部分将建立在之前介绍过的模式上。

下面的示例包括一个您可以创建的网站。您还可以从网页中删除网络小部件。这些操作将在一个页面上执行,而页面不会刷新。 清单 3 展示了在 Dojo 类中定义的小部件,这个小部件将在后面文章中频繁出现。


清单 3. MyWidget 类
				
  
dojo.declare("leak.sample.MyWidget", null, {
	constructor: function(container) {
		this.container = container;
		this.ID = dojox.uuid.generateRandomUuid();
		this.domNode = dojo.create("DIV", {id: this.ID, 
			innerHTML: "MyWidget "+this.ID}, this.container);
	},
	destroy: function() {
		this.container.removeChild(dojo.byId(this.ID));
	}
});

清单 4 展示了操作这些小部件的主页面。


清单 4. 该网站的 HTML 
				
            
<html>
<head>
<title>Dojo Memory Leak Sample</title>
<script type="text/javascript" src="js/dojo/dojo/dojo.js"></script>
<script type="text/javascript">
dojo.registerModulePath("leak.sample", "../../leak/sample");
dojo.require("leak.sample.MyWidget");

widgetArray = [];

function createWidget() {
	var container = dojo.byId("widgetContainer");
	var widget = new leak.sample.MyWidget(container);
	widgetArray.push(widget);
}
function removeWidget() {
	var widget = widgetArray.pop();
	widget.destroy();
}
</script>
</head>
<body>
	<button onclick="createWidget()">Create Widget</button>
	<button onclick="removeWidget()">Remove Widget</button>
	<div id="widgetContainer"></div>
</body>
</html>

使用 dojo.destroy() 或 dojo.empty()

乍看之下,这个问题似乎并不重要。小部件被创建并存储在数组中。它们从数组中弹出,并删除。DOM 节点也脱离了文档 。但是如果用 sIEve 追踪 create widget 和 remove widget 操作之间的不同,您会发现每次小部件节点都变成一个孤立节点,它会带来内存泄露。图 4 两次展示了创建和删除小部件的示例。


图 4. 小部件节点的泄露
图 4. 小部件节点的泄露 

这种情形可能是一个 IE bug。即使您创建了一个元素并将它附加到文档,然后立即使用 parentNode.removeChild() 删除。孤立节点仍然存在。

您可以使用 dojo.destroy() 或 dojo.empty() 来清理 DOM 节点。Dojo 执行 dojo.destroy(<domNode>) 来删除在其他地方已经删除的节点,然后销毁它们。Dojo 还将创建一个节点收集这种垃圾。这样您想删除的节点就删除了。(查看 Dojo 源代码获取实现细节。)清单 5 展示了修复该问题的方法。


Using 清单 5. 使用 dojo.destroy() 来删除 DOM 节点
				
           
                   
## change the destroy() method of MyWidget.js
destroy: function() {
	dojo.destroy(dojo.byId(this.ID));
}

使用 sIEve 验证,您会发现第一次删除组件时,Dojo 就创建了一个空 DIV (垃圾)。在随后的添加和删除中,没有 DOM 节点成为孤立节点,因此泄露不会再发生。

使 JavaScript 对 DOM 节点的引用无效

进行清理时,使 JavaScript 对 DOM 节点的引用无效是一个很好的方法。在 清单 3 中,destroy 方法不能使 JavaScript 对 DOM 节点(this.domNode, this.container)的引用无效。多数情况下,这种情形不会导致内存泄露,但当您在更加复杂的应用程序中工作时,其它对象可能引用您的小部件,这时可能会出现问题。

假设您不了解的其他库可是可用的,保持对您小部件的引用,而且由于某些原因,它不能被清除。删除小部件将导致引用的 DOM 节点成为孤立节点。清单 6 显示了更改。


清单 6. 网站的 HTML:添加更多对象 (widgetRepo)来容纳小部件
				
           
           
widgetArray = [];
widgetRepo = {};

function createWidget() {
	var container = dojo.byId("widgetContainer");
	var widget = new leak.sample.MyWidget(container);
	widgetArray.push(widget);
	widgetRepo[widget.ID] = widget;
}

现在试着添加或删除组件,然后使用 sIEve 来检测内存泄露。图 5 展示了小部件 DIV 的孤立节点,以及不断增加的 widgetContainerDIV 引用。在 Refs 列,widgetContainer DIV 应该在文档中只有一个引用。


图 5. 孤立节点
图 5. 孤立节点 

解决方案就是在清理过程中使 DOM 节点引用无效,如 清单 7 所示。可能时添加一些无效语句可能是一个好方法,因为这不会影响原始功能。


清单 7. 使 DOM 引用无效
				
   
## the destroy method of MyWidget class
destroy: function() {
	dojo.destroy(dojo.byId(this.ID));
	this.domNode = null;
	this.container = null;
}

断开事件以及取消主题订阅

使用 Dojo,另一个避免内存泄露的方法就是断开您连接的事件并取消您订阅的主题。 清单 8 展示了一个连接及断开事件的例子

使用 JavaScript 编程,通常建议在从文档中删除 DOM 节点之前先断开事件。使用下述的 API 在不同的浏览器上连接及断开事件。

  • 对于 IE:attachEvent 和 detachEvent
  • 对于其他浏览器:addEventListener 和 removeEventListener

清单 8. Dojo.connect and dojo.disconnect 
				
            
           
## the constructor method of MyWidget class
constructor: function(container) {
	// … old code here	
	this.clickHandler = dojo.connect(
	this.domNode, "click", this, "onNodeClick");
}

## the destroy method of MyWidget class
destroy: function() {
	// … old code here
	dojo.disconnect(this.clickHandler);
}


在 Dojo 中,您还可以通过订阅和发布主题在组件中建立连接。它作为 Observer 模式执行。在这种情况下,避免内存泄漏的最好方法是做清理时取消主题订阅。对着这两种方法使用下列 API:

  • dojo.subscribe(/*string*/topic, /*function*/function)
  • dojo.unsubscribe(/*string*/topic)

设置 innerHTML

如果您在如何使用 JavaScript 设置 innerHTML 方面不细心的话,可能会引起 IE 内存泄露。(查看 参考资料 获取详情。) 清单 9 展示了可能引起 IE 内存泄露的场景。


清单 9. IE 上的 innerHTML 泄露
				
// 1. An orphan node should be in the document
var elem = document.createElement(“DIV”);

// 2. Set the node’s innerHTML with an DOM 0 event wired
elem.innerHTML = “<a onclick=’alert(1)’>leak</a>”;

// 3. Attach the orphan node to the document
document.body.appendChild(elem);

以上显示的代码类型在 Web 2.0 应用程序中是很常见的,因此要小心对待。解决方案就是确保这个节点在设置 innerHTML 之前不是一个孤立节点。清单 10 是对清单 9 中代码的修复。


清单 10. 修复 innerHTML 泄露
				
            
var elem = document.createElement(“DIV”);

// 现在节点不再是叶子节点
document.body.appendChild(elem);

elem.innerHTML = “<a onclick=’alert(1)’>no leak</a>”;


结束语

识别导致浏览器内存泄露的模式很容易,而在您应用程序源代码寻找问题的根源就比较困难。sIEve 能够帮助您找到大多数由孤立节点引起的泄露。本文介绍了,在 JavaScript 编码中仅仅一点微小的疏忽就会引起内存泄漏。本文中介绍的最佳实践可以帮助您防止发生泄漏 。


下载

描述 名字 大小 下载方法
本文源代码 MyWidget.zip 1KB HTTP
分享到:
评论

相关推荐

    Dojo 基础1-语言及浏览器实用程序

    Dojo 是一个强大的JavaScript工具库,它为Web应用程序开发提供了丰富的功能和便利。在"基础1-语言及浏览器实用程序"这一部分,我们将探讨Dojo的一些核心特性,包括DOM节点操作、类型检查、字符串工具、数组处理以及...

    javascript dojo

    学习dojo的绝好资源学习dojo的绝好资源学习dojo的绝好资源学习dojo的绝好资源学习dojo的绝好资源学习dojo的绝好资源学习dojo的绝好资源学习dojo的绝好资源学习dojo的绝好资源学习dojo的绝好资源学习dojo的绝好资源...

    ArcGIS API for JavaScript 开发教程+DOJO指南+配置教程

    《ArcGIS API for JavaScript开发教程+DOJO指南+配置教程》是一份专为开发者准备的综合资源,旨在帮助他们熟练掌握使用ArcGIS API for JavaScript进行Web GIS应用开发,并结合DOJO框架进行高效编程。这份教程特别...

    dojo1.1 javascript框架

    总之,Dojo 1.1 JavaScript框架以其全面的功能和优秀的性能,为开发者提供了一站式解决方案,特别适合开发复杂的、交互性强的Web应用程序。尽管现在已经有了许多新的JavaScript框架,如React、Vue等,但Dojo的历史...

    Dojo.js核心dojo的javaScript类库Dojo.js核心dojo的javaScript类库Dojo.js核心dojo的javaScript类库

    dojo.js.核心jsDojo.js核心dojo的javaScript类库Dojo.js核心dojo的javaScript类库Dojo.js核心dojo的javaScript类库Dojo.js核心dojo的javaScript类库

    精通dojo 代码 javascript库

    《精通Dojo:JavaScript库深度解析》是一本专门为Dojo框架爱好者和开发者编写的书籍,旨在帮助读者深入理解和掌握Dojo这一强大的JavaScript库。Dojo是Web开发中的一个开源工具集,尤其在富互联网应用程序(RIA)开发...

    dojo的源码

    Dojo 是一个强大的JavaScript工具库,它为Web开发提供了丰富的功能和模块化支持。这个压缩包包含的是Dojo的核心源码,分为压缩版和未压缩版。`dojo.js.uncompressed.js`是未压缩的源码文件,适合学习和调试;而`dojo...

    dojo JavaScript框架 Ajax学习

    dojo JavaScript框架是Web开发中的一个强大工具,特别在处理Ajax(Asynchronous JavaScript and XML)请求时,它提供了丰富的功能和高效的性能。Dojo的核心在于它的模块化系统,这使得开发者可以方便地导入和管理所...

    dojo dojo实例 dojo例子 dojo资料 dojo项目 dojo实战 dojo模块 dojo编程

    Dojo 是一个强大的JavaScript工具库,它为Web开发提供...每个`code`文件可能就是一个具体的Dojo示例,演示了如何运用这些知识点解决问题。通过深入学习和实践,你可以更好地掌握Dojo库,并高效地开发高质量的Web应用。

    DOJO中文手册【出自dojo中国】

    它的主要目标是解决在开发DHTML跨浏览器应用程序时遇到的历史问题。 Dojo为Web开发者提供了诸多优势: 1. **组件库**:Dojo提供了一系列组件,如widgets,能够增强Web应用的可用性、交互性和功能性。 2. **降级友好...

    dojo-release-1.9.0-src.zip dojo javascript库源码

    Dojo 是一个强大的JavaScript工具包,它为Web开发提供了丰富的功能和组件,包括DOM操作、事件处理、Ajax交互、动画效果、模块化编程等。在"dojo-release-1.9.0-src.zip"这个压缩包中,我们获取的是Dojo 1.9.0版本的...

    dojo实战+实用例子

    通过实践,你将能够熟练运用Dojo解决实际项目中的问题,提高开发效率和代码质量。 总的来说,Dojo是一个功能全面的JavaScript框架,它提供了一整套解决方案,从基础的DOM操作到复杂的UI组件和模块化开发。通过深入...

    Dojo工具包javascript

    【Dojo工具包JavaScript】是JavaScript开发领域中的一个重要框架,尤其在Web应用程序的构建上具有广泛的应用。这个0.40版本的教程以其独特的中文资源,为中文开发者提供了宝贵的参考资料,帮助他们更好地理解和掌握...

    dojo是JavaScript语言实现的开源DHTML工具

    Dojo的目标是解决开发DHTML应用程序遇到的那些、长期存在 、历史问题,以及DHTML 跨浏览器问题。  Dojo能够让你更容易使Web页面具有动态能力,或者在任何能够稳定支持JavaScript语言的环境中发挥作用。  Dojo有...

    DOjo中文使用手册

    DOjo提供了许多调试工具,帮助用户快速定位和解决问题。DOjo的调试方法包括使用浏览器的调试工具、使用DOjo的调试API等。 DOjo的高级功能 DOjo提供了许多高级功能,如树(Tree)、数据Grid、图表等。这些功能可以...

    Requirejs异步加载Dojo1.6

    Requirejs是由James Burke创建的一个JavaScript库,它的主要功能是解决JavaScript的异步模块加载问题。通过Requirejs,开发者可以按需加载模块,提高页面加载速度,同时使得代码组织更加清晰和模块化。 ### Dojo:...

    DOJO 学习笔记 dojo

    `dojo.event` 用来绑定和解绑事件,`dojo.event.topic` 提供了消息传递机制,而 `dojo.event.browser` 是浏览器特定事件处理的接口。 `dojo.graphics.color` 模块处理颜色相关的操作,如颜色转换、解析和生成。`...

    Mastering Dojo-JavaScript and Ajax Tools for Great Web Experiences

    通过结合JavaScript和Ajax技术,Dojo能够帮助开发者轻松地创建出具有出色用户体验的应用程序。本书《Mastering Dojo—JavaScript和Ajax工具为伟大的网络体验》全面介绍了如何使用Dojo进行Web开发。 在本书的第一...

    dojo中文文档-dojo手册

    《dojo中文文档-dojo手册》提供了全面而深入的Dojo框架知识,这是一份非常有价值的资源,对于想要理解和掌握Dojo JavaScript库的开发者来说至关重要。Dojo是一个强大的JavaScript工具包,它提供了丰富的功能,包括...

Global site tag (gtag.js) - Google Analytics