Web 前端開發日誌(三):HTML 節點的記憶體洩露問題

LancerComet發表於2018-03-06

文章為在下以前開發時的一些記錄與當時的思考, 學習之初的內容總會有所考慮不周, 如果出錯還請多多指教.

TL;DR

DOM 節點的引用會使得節點一直在記憶體中儲存而不會進行釋放.

問題...

請各位考慮一下如下程式碼,這裡有一個簡易的 HTML:

<div id="my-div">
  <span>My Div</span>
  <ul>
    <li>Hero</li>
    <li>Cows</li>
    <li>Bugs</li>
  </ul>
</div>

<button onclick="deleteMyDiv()">Delete My Div</button>
複製程式碼

還有一段比較醜的邏輯:

<script>
const myDiv = document.querySelector('#my-div')

function deleteMyDiv () {
  myDiv.parentElement.removeChild(myDiv)
}
</script>
複製程式碼

請問,點選 "Delete My Div" 按鈕時,節點 #my-div 消失了麼?

用 Memory 抓一下 Heap 看一下

我們來用 Chrome 開發者工具中抓一下 Heap 觀察一下,看看是不是能找到 Detached DOM.

Detached DOM 代表一個 HTML 節點在 HTML 中被移除,但在記憶體中還保持引用的狀態,意思就是,沒刪乾淨側漏啦!

我們來實際抓一下看看,我們抓兩次快照,第一次是在點選刪除按鈕前的快照,如下圖:

Web 前端開發日誌(三):HTML 節點的記憶體洩露問題

我們在搜尋框中輸入 detached,看起來並沒有 Detached 節點.

我們點選頁面中的 "Delete My Div" 後來抓第二次:

Web 前端開發日誌(三):HTML 節點的記憶體洩露問題

嚯,這就有了!選中它看看:

Web 前端開發日誌(三):HTML 節點的記憶體洩露問題

看看這個 div#my-div,I'm angry!

稍微調整一下邏輯

我們來稍微調整一下邏輯:

function getMyDiv () {
  return document.querySelector('#my-div')
}

function deleteMyDiv () {
  const myDiv = getMyDiv()
  myDiv.parentElement.removeChild(myDiv)
}
複製程式碼

然後重新整理頁面,再抓一個新的快照:

Web 前端開發日誌(三):HTML 節點的記憶體洩露問題

這次沒了!

問題在哪裡?

最早的程式碼問題出現在了,deleteMyDivmyDiv 進行了引用,而且 deleteMyDiv 沒有進行回收,所以導致 myDiv 的一直存在.

我們再仔細看以下剛才擷取的快照:

Web 前端開發日誌(三):HTML 節點的記憶體洩露問題

就算是將 myDiv 從 HTML 中刪除,記憶體中也會一直儲存其資料不會釋放.

修改之後的程式碼沒有任何地方保持了對 myDiv 的引用,所以在刪除操作執行完畢後會立刻釋放 div#my-div 節點.

實際上這樣的情況在業務邏輯中非常容易出現,比如建立一個比較複雜的節點,這個節點中包含了很多細碎的節點,甚至這些節點是從別的業務服務中傳入進來的,這時就很難保證這些細碎的節點沒有被其建立函式或外部程式碼引用,所以就算在 HTML 中刪除,也很有可能造成洩漏.

子節點呢?

如果刪除一個節點,這個節點沒有被直接引用,但裡面的子節點被引用,會怎麼樣?

<div id="my-div">
  <div id="inner-div">Inner DIV</div>
</div>

<button onclick="deleteMyDiv()">Delete My Div</button>
複製程式碼
// 加入 innerDiv.
const innerDiv = document.querySelector('#inner-div')

function getInnerDiv () {
  return innerDiv
}

// 下邊還是之前的程式碼.

function getMyDiv () {
  return document.querySelector('#my-div')
}

function deleteMyDiv () {
  const myDiv = getMyDiv()
  myDiv.parentElement.removeChild(myDiv)
}
複製程式碼

擷取快照:

Web 前端開發日誌(三):HTML 節點的記憶體洩露問題

Web 前端開發日誌(三):HTML 節點的記憶體洩露問題

可以看到兩個都已經變成 Detached Dom,批判一番!??

總結

實際上在下對於這種情況在下目前並沒有非常好的預防實踐,只能說大家對敏感的 DOM 操作邏輯進行謹慎地編寫,並在出現問題的時候藉助工具快速排查,如果有更好的實踐還請大家多分享交流.

對於效能要求比較高的場景,使用原生程式碼建立節點還是請小心謹慎,避免 Detached Dom 帶來的記憶體洩漏問題.

相關文章