dojo 选择器
通常,浏览器内存泄漏对于Web应用程序来说不是问题。 用户在页面之间导航,并且每个页面切换都会导致浏览器刷新。 即使一页内存泄漏,在页面切换后泄漏也会被释放。 泄漏的大小很小,因此经常被忽略。
引入Ajax技术后,内存泄漏成为一个更大的问题。 在Web 2.0样式页面上,用户不经常刷新页面。 Ajax技术用于异步更新页面内容。 在极端情况下,整个Web应用程序都构建在一页上。 在这种情况下,泄漏会累积,不能忽略。
在本文中,学习如何发生内存泄漏,以及如何使用sIEve查找泄漏的来源。 问题和解决方案的实际示例可帮助您探索问题。 您可以下载本文示例的源代码 。
使用JavaScript和Dojo Toolkit的经验会有所帮助,但了解本文并非必需。
泄漏模式
正如Web开发人员所知,Internet Explorer(IE)与Firefox和其他浏览器不同。 在本文中,讨论的内存泄漏模式和问题主要针对但不限于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
显式设置为null。
关闭
-
闭包会导致内存泄漏,因为它们会在不知情的情况下创建循环引用。
只要关闭处于活动状态,父函数的变量将被保留。
它的生命周期超出了功能范围,如果不小心处理将导致泄漏。
清单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参考仍将包含一个孤立节点。 该孤立节点将导致内存泄漏。了解闭包为何创建循环引用很重要。 “ 重新浏览Internet Explorer中的内存泄漏 ”一文中的图表清楚地说明了该问题, 如图1所示。
解决此问题的一种方法是删除封盖。
图1.在DOM和JavaScript之间创建循环引用的闭包
![在DOM和JavaScript之间创建循环引用的闭包](https://i-blog.csdnimg.cn/blog_migrate/bee5d78d959a59be781481ce59232a8f.gif)
筛选
sIEve是帮助检测内存泄漏的工具。 您可以从下载筛和访问文档相关主题 。 主屏幕窗口如图2所示。
图2. sIEve主窗口
单击显示使用中后,该工具特别有用。 您将看到所有正在使用的DOM节点,包括孤立节点和对DOM节点的增加/减少的引用。
图3显示了一个示例视图。 泄漏的原因有:
- 孤立节点,在“孤立列”中标记为“是”。
- 蓝色错误地增加了对DOM节点的引用。
使用sIEve查找泄漏的节点并查看修复它们的代码。
图3.服务器:正在使用的DOM节点
![筛选:DOM节点正在使用](https://i-blog.csdnimg.cn/blog_migrate/668041efaf519255da639b40867213a6.png)
使用sIEve查找泄漏的节点
使用以下步骤检测泄漏的节点。
- 使用Web应用程序的URL启动sIEve。
- 单击立即扫描以查找当前文档中正在使用的所有DOM节点(可选)。
- 单击使用中的显示以查看所有DOM节点。 此时,由于您刚刚开始,所有节点都将显示为红色(新项)。
- 在您的Web应用程序上执行一些操作,以测试是否存在泄漏。
- 单击立即扫描以刷新正在使用的DOM节点(可选)。
- 单击使用中的显示 。 现在,该视图包含一些有趣的信息。 可以找到孤立节点,或者可以增加对某个DOM节点的意外引用。
- 分析报告并查看您的代码。
- 根据需要重复步骤4-8。
sIEve找不到您的应用程序的所有泄漏,但它将查找由孤立节点引起的泄漏。 附加信息,例如ID和externalHTML,可以帮助您识别泄漏的节点。 查看处理泄漏节点的代码,并进行相应的更改。
实际例子
本节包含更多可能导致内存泄漏的情况示例。 这些示例和最佳实践基于Dojo工具箱,但是大多数示例在常规JavaScript编程中均有效。
清理时,通常的做法是删除DOM并删除JavaScript对象,以避免内存泄漏。 不过,还有更多。 本节的其余部分以先前介绍的模式为基础。
以下示例包括您可以创建的网站。 您还将从页面中删除Web窗口小部件。 这些操作在单个页面上执行,没有页面刷新。 清单3显示了在Dojo类中定义的小部件(不是dijit),本文的其余部分将逐步对其进行增强。
清单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.小部件节点的泄漏
这种情况可能是IE错误。 即使创建了一个元素并将其附加到文档上,然后立即使用parentNode.removeChild()
将其删除,该孤立节点仍将存在。
您可以使用dojo.destroy()
或dojo.empty()
清除DOM节点。 Dojo实现了dojo.destroy(<domNode>)
将删除的节点移到其他位置,然后销毁它们。 它将为这种垃圾收集创建一个节点。 您要删除的节点已删除。 (有关实现的详细信息,请参见Dojo源代码。) 清单5显示了如何解决此问题。
清单5.使用dojo.destroy()
除去DOM节点
## change the destroy() method of MyWidget.js
destroy: function() {
dojo.destroy(dojo.byId(this.ID));
}
使用sIEve进行验证时,您会发现第一次删除窗口小部件时,Dojo会创建一个空的DIV
(垃圾)。 在随后的添加和删除过程中,没有DOM节点将成为孤立节点,因此不会发生泄漏。
取消对DOM节点JavaScript引用
在进行清理时,最好取消对DOM节点JavaScript引用。 在清单3中 , destroy
方法不会使对DOM节点( this.domNode, this.container
)JavaScript引用无效。 在大多数情况下,这种情况不会导致内存泄漏,但是当您在更复杂的应用程序上工作时,其他对象持有对小部件的引用,则可能会出现问题。
假设有另一个您不知道的存储库可用,并保存了对小部件的引用,并且由于某种原因,该存储库无法清除。 删除小部件将导致其所引用的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的孤立节点以及对widgetContainer
DIV的增加的引用。 在“引用”列中, widgetContainer
DIV在文档中应该只有一个引用。
图5.孤立节点
![孤立节点](https://i-blog.csdnimg.cn/blog_migrate/a9e9de8ae4400a60080ab9203220ca18.png)
解决方案是在清理过程中使DOM节点引用无效,如清单7所示。 最好在可能的情况下添加这些nullify语句,因为这不会损害您的原始功能。
清单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和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中设置组件之间的连接。 它被实现为观察者模式。 在这种情况下,最佳做法是在清理时取消订阅该主题,以避免内存泄漏。 对于以下两种方法,请使用以下API:
- dojo.subscribe(/ *字符串* /主题,/ *功能* /功能)
- dojo.unsubscribe(/ *字符串* /主题)
设置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”);
// now the node is not orphan anymore
document.body.appendChild(elem);
elem.innerHTML = “<a onclick=’alert(1)’>no leak</a>”;
结论
识别导致浏览器内存泄漏的模式相对容易。 在应用程序源代码中查找问题的根源可能会更加困难。 sIEve可以帮助您找到大多数由孤立节点引起的泄漏。 本文介绍了仅使用一些粗心JavaScript怎么会发生内存泄漏。 本文概述的最佳实践可以帮助您防止发生泄漏。
翻译自: https://www.ibm.com/developerworks/web/library/wa-sieve/index.html
dojo 选择器