文章為在下以前開發時的一些記錄與當時的思考, 學習之初的內容總會有所考慮不周, 如果出錯還請多多指教.
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 中被移除,但在記憶體中還保持引用的狀態,意思就是,沒刪乾淨側漏啦!
我們來實際抓一下看看,我們抓兩次快照,第一次是在點選刪除按鈕前的快照,如下圖:
我們在搜尋框中輸入 detached
,看起來並沒有 Detached 節點.
我們點選頁面中的 "Delete My Div" 後來抓第二次:
嚯,這就有了!選中它看看:
看看這個 div#my-div
,I'm angry!
稍微調整一下邏輯
我們來稍微調整一下邏輯:
function getMyDiv () {
return document.querySelector('#my-div')
}
function deleteMyDiv () {
const myDiv = getMyDiv()
myDiv.parentElement.removeChild(myDiv)
}
複製程式碼
然後重新整理頁面,再抓一個新的快照:
這次沒了!
問題在哪裡?
最早的程式碼問題出現在了,deleteMyDiv
對 myDiv
進行了引用,而且 deleteMyDiv
沒有進行回收,所以導致 myDiv
的一直存在.
我們再仔細看以下剛才擷取的快照:
就算是將 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)
}
複製程式碼
擷取快照:
可以看到兩個都已經變成 Detached Dom
,批判一番!??
總結
實際上在下對於這種情況在下目前並沒有非常好的預防實踐,只能說大家對敏感的 DOM 操作邏輯進行謹慎地編寫,並在出現問題的時候藉助工具快速排查,如果有更好的實踐還請大家多分享交流.
對於效能要求比較高的場景,使用原生程式碼建立節點還是請小心謹慎,避免 Detached Dom
帶來的記憶體洩漏問題.