[ Javascript ] 記憶體泄露以及循環參考解析

來源:互聯網
上載者:User

標籤:amp   UI   return   data   處理   roo   time   target   ack   

記憶體泄露

在javascript中,我們非常少去關注記憶體的管理。

我們建立變數,使用變數,瀏覽器關注這些底層的細節都顯得非常正常。

可是當應用程式變得越來越複雜而且ajax化之後,或者使用者在一個頁面停留過久,我們可能須要去注意一些問題。如一個瀏覽器花費了1G以上的記憶體,而且在不斷的添加。

這些問題經常都是由於記憶體泄露引起。

Javascript 記憶體泄露

這個javascript記憶體管理的核心概念就是具不具有可達性的概念。

1 一個明顯的對象集合將會被覺得是可達的:這些對象是被知道的像roots一樣。

包含那些全部的對象在堆棧中個地方被引用(包含,全部的局部變數。正在被調用的方法的中的參數),以及不論什麼的全域變數。

2 對象儲存在記憶體中,他們是能夠到達的從roots 對象,通過一個引用貨者一個引用鏈。


這裡有一個GC 記憶體回收行程在瀏覽器中。用來清楚沒實用的對象在記憶體中。


記憶體回收example

function Menu(title) {  this.title = title  this.elem = document.getElementById(‘id‘)}var menu = new Menu(‘My Menu‘)document.body.innerHTML = ‘‘  // (1)menu = new Menu(‘His menu‘) // (2)


來看一下記憶體結構:



在step(1) 中,Body.innerHTML 被清除掉,所以它的子節點也會被刪除,由於他們不再被關聯。

可是這個元素#id 是一個例外,他是是被 menu.elem 關聯著,所以該對象會一直存在記憶體中,當然 。假設你檢查他的parentNode, 將會得到一個null值。


注意:個別的Dom元素 能夠會儲存在記憶體中即使他們的parent 被移除了。

在step(2) 中,引用window.menu 被定義。所以之前的 menu由於不再被關聯。它將會自己主動被移除通過瀏覽器的GC。



循環參考集合

閉包常常會導致循環參考,比如:

function setHandler() {  var elem = document.getElementById(‘id‘)  elem.onclick = function() {    // ...  }}

在這裡,這個DOM 元素直接引用匿名function通過onclick。

而且這個function引用了elem元素通過外部的詞法環境。


( 這裡多說一點,關於[[Scope]]是function的內部屬性。在建立function的時候。會將外部函數的詞法環境增加到[[Scope]]中,這裡涉及到javascript的範圍問題。)

這種記憶體結構一樣會出現即使這個處理函數內部沒有不論什麼的代碼。特別的一些方法如addEventListener/attachEvent 也會在內部建立一個引用。


在這個處理函數中通常進行清除。當這個elem死亡的時候。

function cleanUp() {  var elem = document.getElementById(‘id‘)  elem.parentNode.removeChild(elem)}

調用clearUp刪除元素從Dom 中。

這裡依然存在一個引用,LexialEnvironment.elem ,可是這裡沒有了嵌套的functions,所以 LexialEnvironment 是能夠回收的。

在這之後。elem 變成沒有關聯的而且和他的handlers一起被回收。


記憶體泄露

記憶體泄露主要發生當一些瀏覽器因為一些問題不可以移除沒實用的對象從記憶體中。

這發生可能是因為一些原因,如瀏覽器的Bugs,瀏覽器的擴充問題,或多或少,我們自己的代碼錯誤。


IE 8 下面 DOM-JS 記憶體泄露

IE8 之前的瀏覽器不能對DOM和javascript之間的循環參考進行清理。

這個問題相對更加的嚴重在ie6 windows xp sp3 之前的版本號碼

由於記憶體沒法釋放在頁面卸載之前。


所以 setHandler 泄露在ie 8 之前的瀏覽器,elem 和這些閉包沒辦法清除。

function setHandler() {  var elem = document.getElementById(‘id‘)  elem.onclick = function() { /* ... */ }}


不不過DOM 元素。包含XMLHttpRequest 或者其他COM 物件。都會存在此現象。

在IE下用來打破循環參考的方法:




我們定義了elem = null,所以這個處理函數不再關聯到DOM 元素,這個迴圈自然打破。


XmlHttpRequest 記憶體管理和泄露

以下的代碼在i9以下瀏覽器記憶體泄露:

var xhr = new XMLHttpRequest() // or ActiveX in older IExhr.open(‘GET‘, ‘/server.url‘, true)xhr.onreadystatechange = function() {  if(xhr.readyState == 4 && xhr.status == 200) {                // ...  }}xhr.send(null)

看一下記憶體結構:



這個非同步xmlHttpRequest對象一直被瀏覽器追蹤,由於有一個內部的引用關聯到它。

當這個請求結束之後,這個引用就會被刪除,所以xhr 變成不可關聯對象。可是ie9下面的瀏覽器不是這麼做的。

幸運的是,要修複這個Bug非常easy。我們須要刪除這個xhr 從這個閉包中而且使用它用this在這個處理函數中。

例如以下:

var xhr = new XMLHttpRequest()  xhr.open(‘GET‘, ‘jquery.js‘, true)  xhr.onreadystatechange = function() {  if(this.readyState == 4 && this.status == 200) {                document.getElementById(‘test‘).innerHTML++  }}   xhr.send(null)xhr = null}, 50)


這樣就沒有了循環參考。


setInterval/setTimeout

在使用setTimeout/setInterval 也會存在內部引用而且被追蹤知道結束。然後clear up.


對於setInterval 這個結束髮生在 clearInterval中,這個可能會導致記憶體泄露當這種方法實際什麼也 沒做,可是這個interval卻沒有被清除。


記憶體泄露的大小

記憶體泄露的資料結構的size可能不大。

可是這個閉包會導致外部函數的全部的變數遺留下來,當這個內建函式是活動的時候。

所以,你能夠想象,你建立了一個function,並且當中一個變數包括了一個大的字串。

function f() {  var data = "Large piece of data, probably received from server"  /* do something using data */  function inner() {    // ...  }  return inner}

While the function inner function stays in memory, then the LexicalEnvironment with a large variable inside will hang in memory until the inner function is alive.

其實,這可能沒有泄露。很多的fucntions 可能會被建立由於一些合理的原因。比方,對於每個請求,並不清乾淨。由於他們是一些處理函數或者其他什麼。


假設這個data 只被使用在外部函數。我們能夠使它作廢在外部方法中。

function f() {  var data = "Large piece of data, probably received from server"  /* do something using data */  function inner() {    // ...  }data = null  return inner}


如今。

這個data 依然保留在記憶體中作為一個詞法環境的一個屬性。只是它不再須要去佔用太多的空間。


jQuery 記憶體泄露和避免方式

jQuery 使用 $.data 去避免ie 6 7 記憶體泄露。不幸運的是。它導致了一些新的 jQuery 特殊的記憶體泄露。

這個核心原理關於$.data是,不論什麼的javascript實體被限制去讀取一個元素使用例如以下的方式

// works on this site cause it‘s using jQuery$(document.body).data(‘prop‘, ‘val‘) // setalert( $(document.body).data(‘prop‘) ) // get

jQuery $(elem).data(prop,val) 依照例如以下步驟:

1 元素擷取一個唯一的標記假設它不存在的話:

elem[ jQuery.expando ] = id = ++jQuery.uuid  // from jQuery source

2 data 被設定到一個特殊的對象 jQuery.cache:

jQuery.cache[id][‘prop‘] = val

當這個date從一個元素中被讀取:

1 這個元素的唯一標示會被返回:id = elem[jQuery.expando]

2 這個data 會被讀取從jQuery.cache[id]


jQuery設定這個api的目的就是為了讓DOM元素不再直接引用Javascript元素。

它使用了一個數量,可是非常安全。

這個data 儲存在jQuery.cache中。

內部事件處理函數相同使用$.data API。

同一時候也造成了還有一方面的影響,一個元素不能被移除從DOM中使用 本地的調用。


例如以下代碼造成了記憶體泄露在全部的瀏覽器中:

$(‘<div/>‘)  .html(new Array(1000).join(‘text‘)) // div with a text, maybe AJAX-loaded  .click(function() { })  .appendTo(‘#data‘)document.getElementById(‘data‘).innerHTML = ‘‘

這個泄露的發生由於elem 被removeed 通過清除 parent 的innerHTML .可是這個data依然儲存在jQuery.cache中。

更重要的是,這個事件處理函數引用elem,所以這個事件處理函數和elem保留在記憶體中和整個閉包。


一個簡單的泄露範例

function go() {  $(‘<div/>‘)    .html(new Array(1000).join(‘text‘))     .click(function() { })}

這個範例的問題在於,這個元素被建立了。可是沒有使用。

所以在這個函數定義之後,這個引用就消失了。 可是這個jQuery.cache中依然是存在的。


----------------------------------------------------------------------------

原文地址:http://javascript.info/tutorial/memory-leaks

[ Javascript ] 記憶體泄露以及循環參考解析

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在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.