垃圾回收和 DocumentFragment

Garbage collections and DocumentFragment

我读过一篇关于内存泄漏的文章,其中垃圾收集器逻辑总结为:

  1. The garbage collector builds a list of "roots". Roots usually are global variables to which a reference is kept in code. In JavaScript, the "window" object is an example of a global variable that can act as a root. The window object is always present, so the garbage collector can consider it and all of its children to be always present (i.e. not garbage).
  2. All roots are inspected and marked as active (i.e. not garbage). All children are inspected recursively as well. Everything that can be reached from a root is not considered garbage.
  3. All pieces of memory not marked as active can now be considered garbage. The collector can now free that memory and return it to the OS.

此外,MDN 指出 DocumentFragment 不是 active DOM 树的一部分。

The DocumentFragment interface represents a minimal document object that has no parent. It is used as a lightweight version of Document that stores a segment of a document structure comprised of nodes just like a standard document. The key difference is that because the document fragment isn't part of the active document tree structure, changes made to the fragment don't affect the document, cause reflow, or incur any performance impact that can occur when changes are made.

一点一点地我开始意识到背后的逻辑,但如果有人能对我有所启发,我将不胜感激:),使用下面的例子,并解释原因:

1. 在使用后取消 DOM 引用被认为是一种很好的做法。
2. 是否需要取消对 DocumentFragment 和包含它的元素的引用。

function usefulFunction() {
  let existingNode = document.querySelector(`.existing`)
  
  let createdNode = document.createElement(`ul`)
  let fragment = document.createDocumentFragment();
  let browsers = ['Firefox', 'Chrome', 'Opera', 
      'Safari', 'Internet Explorer'];

  browsers.forEach(function(browser) {
      var li = document.createElement('li');
      li.textContent = browser;
      fragment.appendChild(li);
  });
    
  existingNode.appendChild(createdNode)
  createdNode.appendChild(fragment)
  
  fragment = null
  createdNode = null
  existingNode = null
}

usefulFunction()
<div class="existing"></div>

更新的代码段

let existingNode

function helperFunction(object) {
  let createdNode = document.createElement(`div`)
  createdNode.innerHTML = `Hello, I am a beautiful div`
  
  existingNode.appendChild(createdNode)
  existingNode = null 
}

function usefulFunction() {
  existingNode = document.querySelector(`.existing`)
  let fragment = document.createDocumentFragment();
  let browsers = ['Firefox', 'Chrome', 'Opera', 
      'Safari', 'Internet Explorer'];

  browsers.forEach(function(browser) {
      var li = document.createElement('li');
      li.textContent = browser;
      fragment.appendChild(li);
  });
      
  existingNode.appendChild(fragment)
  helperFunction()
}


usefulFunction()
<div class="existing"></div>

如果您正确使用局部变量,通常在使用它们之后几乎不需要将它们作废。当您离开函数的范围时,变量就会消失,并且它们引用的任何对象不是来自仍在范围内的某个变量的引用将成为垃圾。文档片段不从 DOM 中引用,仅从变量中引用,因此当变量被销毁时,片段可以被垃圾收集。

这是您应谨慎使用全局变量的原因之一。它们应该只用于需要随时间持续存在的数据,例如保持应用程序的状态。

请注意,在您的第一个示例代码中,使变量无效对垃圾回收没有影响,因为它们包含的所有节点和片段都附加到 DOM。但如果不是,这些对象将在函数结束后立即变成垃圾,因此无需在返回前使变量无效。

在第二个片段中,如果您从 DOM 中删除该元素,则应使 existingNode 无效。否则,全局变量将阻止节点被垃圾收集。但是,如果希望节点在应用程序的整个生命周期内保持不变,则无需担心变量。