2015-01-09 66 views
2

看來事件處理閉包會導致DOM節點在引用使用鍵控數據連接的d3.js選擇時泄漏。d3.js內存泄漏事件處理程序關閉中的鍵入連接

爲什麼會發生這種情況?這是d3.js的問題還是它被調用的方式?

此示例泄漏HTMLLIElement物體時step反覆調用(clickHandler不必被執行):

function getKeys(n) { 
    // returns a random array of n unique Strings, e.g. ["Alpha", "Quebec", "Charlie"] 
} 

function step() { 

    function clickHandler() { 
    // removing this reference removes the leak 
    // (note that the outer variable is pulled into closure scope regardless of whether this function is called). 
    listItems; 
    } 

    var keys = getKeys(3); 

    var listItems = d3.selectAll('li') 
    .data(keys, function(d) { return d }); 

    listItems.enter() 
    .append('li') 
    .text(function(d) { return '#' + d }) 
    .on('click', clickHandler) 

    listItems.exit() 
    .remove() 
} 

JSBin

DevTools-friendly version

此模式是可再現與D3.js 3.5 .3並在Chrome 39中可識別。

看來,當兩個條件都滿足DOM節點被泄露:

  1. 選擇具有鍵功能
  2. 的封閉,它是用來作爲一個事件處理程序中的選擇的一個節點,具有參考外部範圍選擇。關閉不必執行。

任何這些步驟阻止了內存泄漏:

  • 不使用在所述呼叫的鍵功能data
  • step
  • 末端添加listItems = null避免到外的參考在關閉中選擇
  • 在點擊處理關閉中添加listItems = null

後一點是特別有趣,因爲它釋放所有泄漏的節點,而不僅僅是當前listItems選擇。這意味着選擇是連接的,我沒有想到。

檢查在Chrome DevTools堆快照顯示,泄漏HTMLLIElement對象在其固定層次兩種不同listItems

DevTools screenshot of leaked nodes

這是預期的行爲?如果是這樣,是什麼原因造成的這是在我的代碼或d3.js內存泄漏?

+2

N.B.我發現這是我通過避免關閉引用而修復的邏輯錯誤的一部分。我不主張這種模式(因爲它可能導致內存泄漏),但我不明白在這個特定情況下的行爲,因此這個問題。 – joews

回答

3

在添加新元素的輸入階段,您綁定到每個新添加的'li'元素的onClick處理程序。

listItems.enter() 
    .append('li') 
    .text(function(d) { return '#' + d }) 
    .on('click', clickHandler); 

在退出階段,您將刪除不再需要的'li'元素。但是,在刪除'li'元素之前,您並未從onClick處理程序解除綁定。

在您發佈的探查器圖像中,請注意HTMLLIElement以紅色着色。Chrome的內存分析器告訴你HTMLIElement與DOM樹已斷開連接,但仍有javascript引用。在這種情況下,'li'元素的onClick處理程序會引用您的js代碼。

在D3的退出階段通過調用.on('click',null)來刪除點擊處理程序。

listItems.exit() 
    .on('click', null) 
    .remove(); 

將擺脫對你的clickHandler的引用。

+0

我接受了,因爲這消除了內存泄漏,但我不清楚_why_ - 確保點擊處理程序引用了'listItems',但只有引用_to_,而不是_from_點擊處理程序應該阻止它被垃圾收集。 – joews

+0

當一個DOM元素的事件處理函數引用一個javascript函數時,DOM元素即使從DOM樹中分離出來也不會被DOM垃圾回收器垃圾收集。因爲它看到一個JavaScript函數綁定到DOM元素。 http://nesj.net/blog/2012/04/javascript-memory-leaks/ – trn

+0

[在IE6和7中是這種情況](https://msdn.microsoft.com/en-us/library/bb250448% 28v = vs.85%29.aspx),它具有簡單的引用計數垃圾收集器,但[不能在現代瀏覽器中使用垃圾收集無法訪問的對象子圖](https://developer.chrome.com/devtools/docs /存儲器的分析 - 101#retaining_paths)。這就是爲什麼這個例子是異常的。 – joews