JavaScript 中的記憶體流失問題

來源:互聯網
上載者:User
由於最近自己的FireFox經常出現調用JS指令碼時造成記憶體溢出而產生假死的現象,今天剛好有幸看的IBM網站裡這篇文章,而自己現在又在學習JS,看了以後覺得寫的非常不錯,提到了很多我們平時在編寫JS代碼時容易造成的錯誤。現將此文分享給大家!

Internet Explorer 和 Mozilla Firefox 是兩個與 JavaScript 中的記憶體流失聯絡最為緊密的瀏覽器。兩個瀏覽器中造成這種問題的“罪魁禍首”是用來管理 DOM 對象的元件物件模型。本機 Windows COM 和 Mozilla's XPCOM 都使用引用計數的垃圾收集來進行記憶體配置和檢索。引用計數與用於 JavaScript 的標記-清除式的垃圾收集並不總是能相互相容。本文側重介紹的是如何應對 JavaScript 代碼中的記憶體流失。

JavaScript 中的記憶體流失

JavaScript 是一種垃圾收集式語言,這就是說,記憶體是根據對象的建立分配給該對象的,並會在沒有對該對象的引用時由瀏覽器收回。JavaScript 的垃圾收集機制本身並沒有問題,但瀏覽器在為 DOM 對象分配和恢複記憶體的方式上卻有些出入。

Internet Explorer 和 Mozilla Firefox 均使用引用計數來為 DOM 對象處理記憶體。在引用計數系統,每個所引用的對象都會保留一個計數,以獲悉有多少對象正在引用它。如果計數為零,該對象就會被銷毀,其佔用的記憶體也會返回 給堆。雖然這種解決方案總的來說還算有效,但在循環參考方面卻存在一些盲點。

循環參考的問題何在?

當兩個對象互相引用時,就構成了循環參考,其中每個對象的引用計數值都被賦 1。在純垃圾收集系統中,循環參考問題不大:若涉及到的兩個對象中的一個對象被任何其他對象引用,那麼這兩個對象都將被垃圾收集。而在引用計數系統,這兩 個對象都不能被銷毀,原因是引用計數永遠不能為零。在同時使用了垃圾收集和引用計數的混合系統中,將會發生泄漏,因為系統不能正確識別循環參考。在這種情 況下,DOM 對象和 JavaScript 對象均不能被銷毀。清單 1 顯示了在 JavaScript 對象和 DOM 對象間存在的一個循環參考。

清單 1. 循環參考導致了記憶體流失
        

<html>
      <body>
      <script type="text/javascript">
      document.write("circular references between JavaScript and DOM!");
      var obj;
      window.onload = function(){
     obj=document.getElementById("DivElement");
         document.getElementById("DivElement").expandoProperty=obj;
         obj.bigString=new Array(1000).join(new Array(2000).join("XXXXX"));
         };
      </script>
      <div id="DivElement">Div Element</div>
      </body>
      </html>

如上述清單中所示,JavaScript 對象 obj 擁有到 DOM 對象的引用,表示為 DivElement。而 DOM 對象則有到此 JavaScript 對象的引用,由 expandoProperty 表示。可見,JavaScript 對象和 DOM 對象間就產生了一個循環參考。由於 DOM 對象是通過引用計數管理的,所以兩個對象將都不能銷毀。

另一種記憶體流失模式

在清單 2 中,通過調用外部函數 myFunction 建立循環參考。同樣,JavaScript 對象和 DOM 對象間的循環參考也會導致記憶體流失。

清單 2. 由外部函數調用引起的記憶體流失
        

  <html>
   <head>
   <script type="text/javascript">
   document.write(" object s between JavaScript and DOM!");
   function myFunction(element)
   {
     this.elementReference = element;
     // This code forms a circular reference here
     //by DOM-->JS-->DOM
     element.expandoProperty = this;
   }
   function Leak() {
     //This code will leak
     new myFunction(document.getElementById("myDiv"));
   }
   </script>
   </head>
   <body onload="Leak()">
   <div id="myDiv"></div>
   </body>
   </html>

正如這兩個程式碼範例所示,循環參考很容易建立。在 JavaScript 最為方便的編程結構之一:閉包中,循環參考尤其突出。

JavaScript 中的閉包

JavaScript 的過人之處在於它允許函數嵌套。一個嵌套的內建函式可以繼承外部函數的參數和變數,並由該外部函數私人。清單 3 顯示了內建函式的一個樣本。


清單 3. 一個內建函式
        

  function parentFunction(paramA)
   {
       var a = paramA;
       function childFunction()
       {
       return a + 2;
       }
       return childFunction();
   }

JavaScript 開發人員使用內建函式來在其他函數中整合小型的實用函數。如清單 3 所示,此內建函式 childFunction 可以訪問外部函數 parentFunction 的變數。當內建函式獲得和使用其外部函數的變數時,就稱其為一個閉包。

瞭解閉包

考慮如清單 4 所示的程式碼片段。

清單 4. 一個簡單的閉包
        

   <html>
   <body>
   <script type="text/javascript">
   document.write("Closure Demo!!");
   window.onload=
   function closureDemoParentFunction(paramA)
   {
       var a = paramA;
       return function closureDemoInnerFunction (paramB)
       {
          alert( a +" "+ paramB);
       };
   };
   var x = closureDemoParentFunction("outer x");
   x("inner x");
   </script>
   </body>
   </html>

在上述清單中,closureDemoInnerFunction 是在父函數 closureDemoParentFunction 中定義的內建函式。當用外部的 x 對 closureDemoParentFunction 進行調用時,外部函數變數 a 就會被賦值為外部的 x。函數會返回指向內建函式 closureDemoInnerFunction 的指標,該指標包括在變數 x 內。

外部函數 closureDemoParentFunction 的本地變數 a 即使在外部函數返回時仍會存在。這一點不同於 C/C++ 這樣的程式設計語言,在 C/C++ 中,一旦函數返回,本地變數也將不複存在。在 JavaScript 中,在調用 closureDemoParentFunction 的時候,帶有屬性 a 的範圍對象將會被建立。該屬性包括值 paramA,又稱為“外部 x”。同樣地,當 closureDemoParentFunction 返回時,它將會返回內建函式 closureDemoInnerFunction,該函數包括在變數 x 中。

由於內建函式持有到外部函數的變數的引用,所以這個帶屬性 a 的範圍對象將不會被垃圾收集。當對具有參數值 inner x 的 x 進行調用時,即 x("inner x"),將會彈出警告訊息,表明 “outer x innerx”。

清單 4 簡要解釋了 JavaScript 閉包。閉包功能非常強大,原因是它們使內建函式在外部函數返回時也仍然可以保留對此外部函數的變數的訪問。不幸的是,閉包非常易於隱藏 JavaScript 對象 和 DOM 對象間的循環參考。

閉包和循環參考

在清單 5 中,可以看到一個閉包,在此閉包內,JavaScript 對象(obj)包含到 DOM 對象的引用(通過 id "element" 被引用)。而 DOM 元素則擁有到 JavaScript obj 的引用。這樣建立起來的 JavaScript 對象和 DOM 對象間的循環參考將會導致記憶體流失。

清單 5. 由事件處理引起的記憶體流失模式
        

  <html>
   <body>
   <script type="text/javascript">
   document.write("rogram to illustrate memory leak via closure");
   window.onload=function outerFunction(){
     var obj = document.getElementById("element");
     obj.onclick=function innerFunction(){
     alert("Hi! I will leak");
     };
     obj.bigString=new Array(1000).join(new Array(2000).join("XXXXX"));
     // This is used to make the leak significant
   };
   </script>
   <button id="element">Click Me</button>
   </body>
   </html>

避免記憶體流失

幸好,JavaScript 中的記憶體流失是可以避免的。當確定了可導致循環參考的模式之後,正如我們在上述章節中所做的那樣,您就可以開始著手應對這些模式了。這裡,我們將以上述的 由事件處理引起的記憶體流失模式 為例來展示三種應對已知記憶體流失的方式。

一種應對 清單 5 中的記憶體流失的解決方案是讓此 JavaScript 對象 obj 為空白,這會顯式地打破此循環參考,如清單 6 所示。

清單 6. 打破循環參考
        

  <html>
   <body>
   <script type="text/javascript">
   document.write("Avoiding memory leak via closure by breaking the circular
   reference");
     window.onload=function outerFunction(){
     var obj = document.getElementById("element");
     obj.onclick=function innerFunction()
     {
       alert("Hi! I have avoided the leak");
       // Some logic here
     };
     obj.bigString=new Array(1000).join(new Array(2000).join("XXXXX"));
     obj = null; //This breaks the circular reference
     };
   </script>
   <button id="element">"Click Here"</button>
   </body>
   </html>

清單 7 是通過添加另一個閉包來避免 JavaScript 對象和 DOM 對象間的循環參考。

清單 7. 添加另一個閉包
        

  <html>
   <body>
   <script type="text/javascript">
   document.write("Avoiding a memory leak by adding another closure");
    window.onload=function outerFunction(){
   var anotherObj = function innerFunction()
       {
         // Some logic here
         alert("Hi! I have avoided the leak");
        };
     (function anotherInnerFunction(){
       var obj = document.getElementById("element");
       obj.onclick=anotherObj })();
       };
   </script>
   <button id="element">"Click Here"</button>
   </body>
   </html>

清單 8 則通過添加另一個函數來避免閉包本身,進而阻止了泄漏。

清單 8. 避免閉包自身
        

  <html>
   <head>
   <script type="text/javascript">
   document.write("Avoid leaks by avoiding closures!");
   window.onload=function()
   {
     var obj = document.getElementById("element");
     obj.onclick = doesNotLeak;
   }
   function doesNotLeak()
   {
     //Your Logic here
     alert("Hi! I have avoided the leak");
   }

   </script>
   </head>
   <body>
   <button id="element">"Click Here"</button>
   </body>
   </html>

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.