如何在自定义 Dojo widget 时避免内存泄漏

 我来答
day忘不掉的痛
2015-05-01 · 知道合伙人数码行家
day忘不掉的痛
知道合伙人数码行家
采纳数:62646 获赞数:223953
本人担任公司网络部总经理多年,有充足的网络经验、互联网相关知识和资讯。

向TA提问 私信TA
展开全部
在测试过程中,我发现很多自定义 widget 在销毁时,其内部包含的子 widget 并没有被释放。并且,通过在 Firebug 的 console 中执行 dojo.byId 函数,还能引用到这些未释放的 widget 实例。这就造成了很大的内存泄漏。为了解决这个问题,笔者通过阅读代码和实验研究了 Dojo widget 的销毁过程并总结除了几条在编写自定义 widget 时需要遵循的规则。在这里把研究结果和大家分享。回页首使用Firebug Dojo 插件监控内存泄漏FireBug 是一款基于 FireFox 浏览器的开发类浏览器插件。FireBug 本身支持多种插件,我们要用到的 Dojo 插件就是 firebug 的一个插件。Dojo 插件提供了以下功能: 查看所有已注册的 widget 查看Dojo 的版本信息、Dojo 模块信息、widget 总数、connections 和 subscriptions 总数 查看详细的 connection 和 subscription 信息 在connection 和 subscription 的处理过程中设置断点 打开widget 的帮助文档我们可以通过观察 widget 总数是否有变化来判断是否存在内存泄漏(先创建自定义 widget 然后销毁,如果 widget 总数变大说明该自定义 widget 中包含的子 widget 没有释放)。回页首Widget 销毁过程分析我们在项目过程中创建的自定义 widget 大部分是继承自 dijit._Widget 和 dijit._Templated 这两个 widget 类。其中,dijit._Templated 这个类是一个 mixin 类,其保存了 attachpoints 和 attachEvents 两个数组并在自身的 destroyRendering 方法中释放了这两个数组。因此,我们不必考虑其资源释放的问题。dijit._Widget 类中虽然也没有定义 destroy 方法,但是其父类 dijit._WidgetBase 中有一系列用于销毁自身实例的方法需要我们仔细分析,具体方法见下表。表1.dijit._WidgetBase destroy 方法 方法名 描述 destroyRecursive Destroy this widget and its descendants destroy Destroy this widget, but not its descendants. destroyRendering Destroys the DOM nodes associated with this widget destroyDescendants Recursively destroy the children of this widget and their descendants. uninitialize a stub function destroyRecursive 方法分析首先来看 destroyRecursive 方法,根据 Dojo 代码中的注释我们可以看到这个方法是销毁过程的一个总的入口点。其代码如下:清单1. _WidgetBase destroyRecursive 方法 this._beingDestroyed = true; this.destroyDescendants(preserveDom); this.destroy(preserveDom); 我们可以看到,widget 在这个方法内先调用 destroyDescendants 销毁所有的子 widget, 再调用 destroy 方法销毁自身实例。destroyDescendants 方法分析让我们先考察下 destroyDescendants 这个方法都做了哪些工作。先看如下的代码:清单2. _WidgetBase destroyDescendants 方法 dojo.forEach(this.getChildren(), function(widget) { if(widget.destroyRecursive) { widget.destroyRecursive(preserveDom); } }); 看来,子 widget 能否被正确销毁的关键是 this.getChildren 这个函数能否返回所有的子 widget。从 _WidgetBase 的代码可以看到,该函数是以 Widget 的 containerNode 为 root Node,采用 dijit.findWidgets 查找并得到 widget 列表。让我们做一个实验,创建一个自定义控件:MemoryLeakTest。该控件继承自 dijit._Widget 和 dijit._Templated. 模板代码如下:清单3.MemoryLeakTest Widget Template I have attach point no attach point I am sub's sub with attachpoint js 代码中只定义两个方法:清单4 MemoryLeakTest widget 部分 js 代码 addContentPane:function() { var temp=new dijit.layout.ContentPane({id:"dcd"}); this.domNode.appendChild(temp.domNode); }, listChildren:function() { dojo.forEach(this.getChildren(), function(widget){ console.log("Widget:"+widget.get("id")); }); } 在测试页面中,我们直接调用方法 listChildren,控制台中没有任何输出。通过 firebug,我们可以跟踪的当前 widget 的 containerNode 等于 null。让我们重载下 buildRendering 方法,在该方法中给 containerNode 赋值: this.containerNode=this.domNode; 再调用 listChildren,可以看到几个子 widget 的 id 都被输出。由此,我们可以得出编写自定义 widget 时需要注意的几条规则: 给containerNode 赋值。一般是将 domNode 赋给 containerNode,在 dijit._Container 中已经有相应的动作,如有需要可以从该类继承。 如果有特殊需求,不能将 containerNode 设置为 domNode,请自行调用位于 containerNode 外部的子 widget 的 destroyRecursive 方法destroy 方法分析接下来,我们接着看 destroy 方法中都做了些什么。清单5. _WidgetBase destroy 方法 this._beingDestroyed = true; this.uninitialize(); var d = dojo, dfe = d.forEach, dun = d.unsubscribe; dfe(this._connects, function(array) { dfe(array, d.disconnect); }); dfe(this._subscribes, function(handle) { dun(handle); }); // destroy widgets created as part of template, etc. dfe(this._supportingWidgets || [], function(w) { if(w.destroyRecursive) { w.destroyRecursive(); }else if(w.destroy) { w.destroy(); } }); this.destroyRendering(preserveDom); dijit.registry.remove(this.id); this._destroyed = true; 我们可以看到,在清单 5 的第二行调用了 dijit._WidgetBase 的 uninitialize 方法。这是一个虚方法。在这个方法里面,我们应该将创建和使用 Widget 过程中收集的资源释放掉(例如,取消属性对象的引用,清空数组类型属性中的对象等)。清单5 的第 6 行至第 11 行完成了两样工作:对 _connects 数组中的每个元素执行 dojo.disconnect 和对数组 _subcribes 中的每个元素执行 dojo.unsubscribe 方法。从 _WidgetBase 的其他部分代码,我们可以得知这两个数组中分别存储的是 dojo.connect(在 this.connect 方法中)和 dojo.subscribe(在 this.subscribe 方法中)得到的句柄。由此,我们可以得出编写自定义 widget 时需要注意的另外几条规则: 重载uninitialize 方法释放资源。 采用this.connect 方法代替 dojo.connect。 采用this.subscribe 方法代替 dojo.subscribe。回页首总结从上面的分析可以看出,在自定义 Dojo widget 时,我们遵循下面的几条规则可以很轻松的避免不必要的内存泄漏。 给containerNode 赋值。一般是将 domNode 赋给 containerNode,在 dijit._Container 中已经有相应的动作,如有需要可以从该类继承。 如果有特殊需求,不能将 containerNode 设置为 domNode,请自行调用位于 containerNode 外部的子 widget 的 destroyRecursive 方法。 重载uninitialize 方法释放资源。 采用this.connect 方法代替 dojo.connect。 采用this.subscribe 方法代替 dojo.subscribe。参考资料 学习 参考GetFireBug.com: FireBug 官方网站。
参考DojoFirebugExtension Reference Guide: FireBug Dojo 插件的详细说明和参考手册。
参考Dojo Tool kit: 获取 Dojo 源代码和帮助文档。
developerWorks Web development 专区:通过专门关于 Web 技术的文章和教程,扩展您在网站开发方面的技能。
developerWorks Ajax 资源中心:这是有关 Ajax 编程模型信息的一站式中心,包括很多文档、教程、论坛、blog、wiki 和新闻。任何 Ajax 的新信息都能在这里找到。
developerWorks Web 2.0 资源中心,这是有关 Web 2.0 相关信息的一站式中心,包括大量 Web 2.0 技术文章、教程、下载和相关技术资源。您还可以通过 Web 2.0 新手入门 栏目,迅速了解 Web 2.0 的相关概念。
查看HTML5 专题,了解更多和 HTML5 相关的知识和动向。
讨论加入developerWorks 中文社区。
推荐律师服务: 若未解决您的问题,请您详细描述您的问题,通过百度律临进行免费专业咨询

为你推荐:

下载百度知道APP,抢鲜体验
使用百度知道APP,立即抢鲜体验。你的手机镜头里或许有别人想知道的答案。
扫描二维码下载
×

类别

我们会通过消息、邮箱等方式尽快将举报结果通知您。

说明

0/200

提交
取消

辅 助

模 式