<menu id="w8yyk"><menu id="w8yyk"></menu></menu>
  • <dd id="w8yyk"><nav id="w8yyk"></nav></dd>
    <menu id="w8yyk"></menu>
    <menu id="w8yyk"><code id="w8yyk"></code></menu>
    <menu id="w8yyk"></menu>
    <xmp id="w8yyk">
    <xmp id="w8yyk"><nav id="w8yyk"></nav>
  • 網站首頁 > 物聯資訊 > 技術分享

    找出并解決 JavaScript 和 Dojo 引起的瀏覽器內存泄露問題

    2016-09-28 00:00:00 廣州睿豐德信息科技有限公司 閱讀
    睿豐德科技 專注RFID識別技術和條碼識別技術與管理軟件的集成項目。質量追溯系統、MES系統、金蝶與條碼系統對接、用友與條碼系統對接

    簡介: 如果大量使用 JavaScript 和 Ajax 技術開發 Web 2.0 應用程序,您很有可能會遇到瀏覽器的內存泄漏問題。如果您有一個單頁應用程序或者一個頁面要處理很多 UI 操作,問題可能比較嚴重。在本文中,學習如何使用 sIEve 工具檢測并解決內存泄漏問題,本文也包含內存泄漏問題的應用示例以及解決方案。

    發布日期: 2012 年 4 月 09 日 
    級別: 中級 原創語言: 英文 
    訪問情況 : 10932 次瀏覽 
    評論: 0 (查看 | 添加評論 - 登錄)

    平均分 5 星 共 7 個評分 平均分 (7個評分)
    為本文評分

    簡介

    一般來說,瀏覽器的內存泄漏對于 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

    關于下載方法的信息


    參考資料

    學習

    獲得產品和技術

    討論

    關于作者

    Yi Ming Huang 是在 China Development Lab 從事 Lotus ActiveInsight 的軟件工程師。他擅長與 Portlet/Widget 相關的 Web 開發并對 REST、OSGi 和 Spring 技術感興趣。

     

    from:http://www.ibm.com/developerworks/cn/web/wa-sieve/

    RFID管理系統集成商 RFID中間件 條碼系統中間層 物聯網軟件集成
    最近免费观看高清韩国日本大全