文章为在下以前开发时的一些记录与当时的思考, 学习之初的内容总会有所考虑不周, 如果出错还请多多指教.
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>
还有一段比较丑的逻辑:
- const myDiv = document.querySelector('#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 中删除, 也很有可能造成泄漏.
子节点呢?
如果删除一个节点, 这个节点没有被直接引用, 但里面的子节点被引用, 会怎么样?
- <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 带来的内存泄漏问题.
来源: https://juejin.im/post/5a9e9633f265da2374108033