當前位置:編程學習大全網 - 源碼下載 - 如何在自定義 Dojo widget 時避免內存泄漏

如何在自定義 Dojo widget 時避免內存泄漏

在測試過程中,我發現很多自定義 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 中文社區。

  • 上一篇:炒股票好長時間了,壹直有個問題,炒股票究竟用什麽方法最好?
  • 下一篇:微信貸款平臺有哪些?
  • copyright 2024編程學習大全網