文章为在下以前开发时的一些记录与当时的思考, 学习之初的内容总会有所考虑不周, 如果出错还请多多指教.
TL;DR
对 DOM 节点的引用会使得节点一直在内存中存储而不会进行释放.
问题...
请各位考虑一下如下代码,这里有一个简易的 HTML:
My Div
- Hero
- Cows
- Bugs
Delete My Div
复制代码
还有一段比较丑的逻辑:
function deleteMyDiv (){
myDiv.parentElement.removeChild(myDiv)
}
复制代码
请问,点击 "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 中删除,也很有可能造成泄漏.
子节点呢?
如果删除一个节点,这个节点没有被直接引用,但里面的子节点被引用,会怎么样?
Delete My Div
复制代码// 加入 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 带来的内存泄漏问题.