搜尋
首頁web前端js教程常見的JavaScript記憶體洩露
常見的JavaScript記憶體洩露Dec 05, 2017 pm 04:31 PM
javascriptjs洩漏

什麼是記憶體洩漏

#記憶體洩漏指由於疏忽或錯誤造成程序未能釋放已不再使用的內存。記憶體洩漏並非指內存在物理上的消失,而是應用程式分配某段記憶體後,由於設計錯誤,導致在釋放該段記憶體之前就失去了對該段記憶體的控制,從而造成了記憶體的浪費。
記憶體洩漏通常只能由獲得程式原始碼的程式設計師才能分析出來。然而,有不少人習慣把任何不需要的記憶體使用的增加描述為記憶體洩漏,即使嚴格意義上來說這是不準確的。
————wikipedia

意外的全域變數

JavaScript對未宣告變數的處理方式:在全域物件上建立該變數的參考(即全域物件上的屬性,不是變量,因為它能透過delete刪除)。如果在瀏覽器中,全域物件就是window物件。

如果未宣告的變數快取大量的數據,會導致這些數據只有在視窗關閉或重新刷新頁面時才能釋放。這樣會造成意外的記憶體洩漏。

<span style="font-size: 14px;">function foo(arg) {<br>    bar = "this is a hidden global variable with a large of data";<br>}<br></span>

等於:

<span style="font-size: 14px;">function foo(arg) {<br>    window.bar = "this is an explicit global variable with a large of data";<br>}<br></span>

#另外,透過this建立意外的全域變數:

<span style="font-size: 14px;">function foo() {<br>    this.variable = "potential accidental global";<br>}<br><br>// 当在全局作用域中调用foo函数,此时this指向的是全局对象(window),而不是'undefined'<br>foo();<br></span>

#解決方法:

在JavaScript檔案中新增'use strict',開啟嚴格模式,可以有效地避免上述問題。

<span style="font-size: 14px;">function foo(arg) {<br>    "use strict" // 在foo函数作用域内开启严格模式<br>    bar = "this is an explicit global variable with a large of data";// 报错:因为bar还没有被声明<br>}<br></span>

如果需要在一個函數中使用全域變量,可以像如下程式碼所示,在window上明確宣告:

<span style="font-size: 14px;">function foo(arg) {<br>    window.bar = "this is a explicit global variable with a large of data";<br>}<br></span>

這樣不僅可讀性高,而且後期維護也方便

談到全域變量,需要注意那些用來暫時儲存大量資料的全域變量,確保在處理完這些數據後來將其設為null或重新賦值。全域變數也常用來做cache,一般cache都是為了效能最佳化才用到的,為了效能,最好對cache的大小做個上限限制。因為cache是​​不能被回收的,越高cache會導致越高的記憶體消耗。

console.log

console.log:向web開發控制台列印一則訊息,常用來在開發時偵錯分析。有時在開發時,需要列印一些物件訊息,但發佈時卻忘記去掉console.log語句,這可能造成記憶體外洩。

在傳遞給console.log的物件是不能被垃圾回收 ♻️,因為在程式碼運行之後需要在開發工具能查看物件資訊。所以最好不要在生產環境中console.log任何物件。

實例------>demos/log.html

<span style="font-size: 14px;">nbsp;html><br><br><br><br>  <meta>
<br>  <meta>
<br>  <meta>
<br>  <title>Leaker</title>
<br><br><br><br>  <input><br>  <script><br/>    !function () {<br/>      function Leaker() {<br/>        this.init();<br/>      };<br/>      Leaker.prototype = {<br/>        init: function () {<br/>          this.name = (Array(100000)).join(&#39;*&#39;);<br/>          console.log("Leaking an object %o: %o", (new Date()), this);// this对象不能被回收<br/>        },<br/><br/>        destroy: function () {<br/>          // do something....<br/>        }<br/>      };<br/>      document.querySelector(&#39;input&#39;).addEventListener(&#39;click&#39;, function () {<br/>        new Leaker();<br/>      }, false);<br/>    }()<br/>  </script><br><br><br><br></span>

這裡結合Chrome的Devtools–>Performance做一些分析,操作步驟如下:

⚠️註:最好在隱藏視窗中進行分析工作,避免瀏覽器外掛程式影響分析結果

  1. 開啟【Performance】項目的記錄

  2. #執行一次CG,建立基準參考線

  3. 連續點選【click】按鈕三次,新建三個Leaker物件

  4. 執行一次CG

  5. 停止記錄

常見的JavaScript記憶體洩露

#可以看出【JS Heap】線最後沒有降回到基準參考線的位置,顯然存在沒有被回收的記憶體。如果將程式碼修改為:

<span style="font-size: 14px;">    !function () {<br>      function Leaker() {<br>        this.init();<br>      };<br>      Leaker.prototype = {<br>        init: function () {<br>          this.name = (Array(100000)).join('*');<br>        },<br><br>        destroy: function () {<br>          // do something....<br>        }<br>      };<br>      document.querySelector('input').addEventListener('click', function () {<br>        new Leaker();<br>      }, false);<br>    }()<br></span>

去掉console.log("Leaking an object %o: %o", (new Date()), this);語句。重複上述的操作步驟,分析結果如下:常見的JavaScript記憶體洩露

####從比較分析結果可知,console.log印製的物件是不會被垃圾回收器回收的。因此最好不要在頁面中console.log任何大對象,這樣可能會影響頁面的整體效能,特別是在生產環境中。除了console.log外,另外還有console.dir、console.error、console.warn等都存在類似的問題,這些細節需要特別的關注。 ######

closures(闭包)

当一个函数A返回一个内联函数B,即使函数A执行完,函数B也能访问函数A作用域内的变量,这就是一个闭包——————本质上闭包是将函数内部和外部连接起来的一座桥梁。

<span style="font-size: 14px;">function foo(message) {<br>    function closure() {<br>        console.log(message)<br>    };<br>    return closure;<br>}<br><br>// 使用<br>var bar = foo("hello closure!");<br>bar()// 返回 'hello closure!'<br></span>

在函数foo内创建的函数closure对象是不能被回收掉的,因为它被全局变量bar引用,处于一直可访问状态。通过执行bar()可以打印出hello closure!。如果想释放掉可以将bar = null即可。

由于闭包会携带包含它的函数的作用域,因此会比其他函数占用更多的内存。过度使用闭包可能会导致内存占用过多。

实例------>demos/closures.html

<span style="font-size: 14px;">nbsp;html><br><br><br><br>  <meta>
<br>  <meta>
<br>  <meta>
<br>  <title>Closure</title>
<br><br><br><br>  <p>不断单击【click】按钮</p>
<br>  <button>Click</button><br>  <script><br/>    function f() {<br/>      var str = Array(10000).join(&#39;#&#39;);<br/>      var foo = {<br/>        name: &#39;foo&#39;<br/>      }<br/>      function unused() {<br/>        var message = &#39;it is only a test message&#39;;<br/>        str = &#39;unused: &#39; + str;<br/>      }<br/>      function getData() {<br/>        return &#39;data&#39;;<br/>      }<br/>      return getData;<br/>    }<br/><br/>    var list = [];<br/>    <br/>    document.querySelector(&#39;#click_button&#39;).addEventListener(&#39;click&#39;, function () {<br/>      list.push(f());<br/>    }, false);<br/>  </script><br><br><br><br></span>

这里结合Chrome的Devtools->Memory工具进行分析,操作步骤如下:

⚠️注:最好在隐藏窗口中进行分析工作,避免浏览器插件影响分析结果

  1. 选中【Record allocation timeline】选项

  2. 执行一次CG

  3. 单击【start】按钮开始记录堆分析

  4. 连续单击【click】按钮十多次

  5. 停止记录堆分析

常見的JavaScript記憶體洩露

上图中蓝色柱形条表示随着时间新分配的内存。选中其中某条蓝色柱形条,过滤出对应新分配的对象:

常見的JavaScript記憶體洩露

查看对象的详细信息:

常見的JavaScript記憶體洩露

从图可知,在返回的闭包作用链(Scopes)中携带有它所在函数的作用域,作用域中还包含一个str字段。而str字段并没有在返回getData()中使用过。为什么会存在在作用域中,按理应该被GC回收掉, why

原因是在相同作用域内创建的多个内部函数对象是共享同一个变量对象(variable object)。如果创建的内部函数没有被其他对象引用,不管内部函数是否引用外部函数的变量和函数,在外部函数执行完,对应变量对象便会被销毁。反之,如果内部函数中存在有对外部函数变量或函数的访问(可以不是被引用的内部函数),并且存在某个或多个内部函数被其他对象引用,那么就会形成闭包,外部函数的变量对象就会存在于闭包函数的作用域链中。这样确保了闭包函数有权访问外部函数的所有变量和函数。了解了问题产生的原因,便可以对症下药了。对代码做如下修改:

<span style="font-size: 14px;">    function f() {<br>      var str = Array(10000).join('#');<br>      var foo = {<br>        name: 'foo'<br>      }<br>      function unused() {<br>        var message = 'it is only a test message';<br>        // str = 'unused: ' + str; //删除该条语句<br>      }<br>      function getData() {<br>        return 'data';<br>      }<br>      return getData;<br>    }<br><br>    var list = [];<br>    <br>    document.querySelector('#click_button').addEventListener('click', function () {<br>      list.push(f());<br>    }, false);<br></span>

getData()和unused()内部函数共享f函数对应的变量对象,因为unused()内部函数访问了f作用域内str变量,所以str字段存在于f变量对象中。加上getData()内部函数被返回,被其他对象引用,形成了闭包,因此对应的f变量对象存在于闭包函数的作用域链中。这里只要将函数unused中str = 'unused: ' + str;语句删除便可解决问题。

常見的JavaScript記憶體洩露

查看一下闭包信息:

常見的JavaScript記憶體洩露

DOM泄露

在JavaScript中,DOM操作是非常耗时的。因为JavaScript/ECMAScript引擎独立于渲染引擎,而DOM是位于渲染引擎,相互访问需要消耗一定的资源。如Chrome浏览器中DOM位于WebCore,而JavaScript/ECMAScript位于V8中。假如将JavaScript/ECMAScript、DOM分别想象成两座孤岛,两岛之间通过一座收费桥连接,过桥需要交纳一定“过桥费”。JavaScript/ECMAScript每次访问DOM时,都需要交纳“过桥费”。因此访问DOM次数越多,费用越高,页面性能就会受到很大影响。了解更多ℹ️

常見的JavaScript記憶體洩露

为了减少DOM访问次数,一般情况下,当需要多次访问同一个DOM方法或属性时,会将DOM引用缓存到一个局部变量中。但如果在执行某些删除、更新操作后,可能会忘记释放掉代码中对应的DOM引用,这样会造成DOM内存泄露。

实例------>demos/dom.html

<span style="font-size: 14px;">nbsp;html><br><br><br>  <meta>
<br>  <meta>
<br>  <meta>
<br>  <title>Dom-Leakage</title>
<br><br><br>  <input><br>  <input><br><br>  <p><br>    </p>
<pre class="brush:php;toolbar:false">

  
  

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

AI Hentai Generator

AI Hentai Generator

免費產生 AI 無盡。

熱門文章

R.E.P.O.能量晶體解釋及其做什麼(黃色晶體)
3 週前By尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.最佳圖形設置
3 週前By尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.如果您聽不到任何人,如何修復音頻
3 週前By尊渡假赌尊渡假赌尊渡假赌

熱工具

記事本++7.3.1

記事本++7.3.1

好用且免費的程式碼編輯器

ZendStudio 13.5.1 Mac

ZendStudio 13.5.1 Mac

強大的PHP整合開發環境

SAP NetWeaver Server Adapter for Eclipse

SAP NetWeaver Server Adapter for Eclipse

將Eclipse與SAP NetWeaver應用伺服器整合。

mPDF

mPDF

mPDF是一個PHP庫,可以從UTF-8編碼的HTML產生PDF檔案。原作者Ian Back編寫mPDF以從他的網站上「即時」輸出PDF文件,並處理不同的語言。與原始腳本如HTML2FPDF相比,它的速度較慢,並且在使用Unicode字體時產生的檔案較大,但支援CSS樣式等,並進行了大量增強。支援幾乎所有語言,包括RTL(阿拉伯語和希伯來語)和CJK(中日韓)。支援嵌套的區塊級元素(如P、DIV),

SecLists

SecLists

SecLists是最終安全測試人員的伙伴。它是一個包含各種類型清單的集合,這些清單在安全評估過程中經常使用,而且都在一個地方。 SecLists透過方便地提供安全測試人員可能需要的所有列表,幫助提高安全測試的效率和生產力。清單類型包括使用者名稱、密碼、URL、模糊測試有效載荷、敏感資料模式、Web shell等等。測試人員只需將此儲存庫拉到新的測試機上,他就可以存取所需的每種類型的清單。