函数 属性 保存对文档片段的引用:何时使用 cloneNode?

Function property holding a reference to a document fragment: when to use cloneNode?

我无法理解对象的数据分配何时是引用以及何时创建对象的副本。我以为我明白了,但下面的例子不符合我对它的简单理解。

事件处理程序从以下一系列步骤开始。
Promise.allSettled( [ gather_write, get_build ] ).then( test );

承诺 gather_writeget_build 执行不相关的功能。第一个从 DOM 收集一些数据并将其写入数据库。第二个从数据库中检索数据并构建文档片段。如果两者都满足,则 DOM 中的节点将替换为片段。

这里显示的代码太多,但是 get_build,在成功从数据库中获取数据后,调用一个单独的函数来构建文档片段,并将其结果作为 属性 的解析对象。那很好用;然后我想尝试在 gather_write 拒绝和 get_build 完成的情况下向用户提供选项,这需要临时存储文档片段。因此,不是返回它并将其传递回 Promise.allSettled,而是存储在构建它的函数的 属性 中。

在同步函数build中代码设置为:

function build()
  {
    let f;
    try
      {
        f = document.createFragment();
        // ...build the fragment... 
        build.html = f;
      }
     catch(e)
      { }
     finally
      { f = null; }
   } // close build

函数 build 必须在承诺 get_build 可以解析之前完成,之后 Promise.allSettled 可以被评估;并且,如果两个承诺都满足,则可以调用用存储在 build.html 中的新建片段替换 DOM 节点的函数。我认为 build.html 将是对节点对象 f 的引用,并且由于 ffinally 块中设置为 null,因此这将发生在所有上面可以完成,当使用 build.html 的代码最终是 运行 时,它将指向空值而不是片段。所以,赋值语句应该是build.html = f.cloneNode(true)

但是,无论是否使用 f.cloneNode,该过程都可以正常工作。你能解释一下为什么吗?如果不需要,我不希望浏览器采取步骤克隆该片段,但在不理解为什么不进行克隆就可以工作的情况下犹豫是否将其排除。

谢谢。

您可以将每个变量名视为指向内存位置的指针。将变量名称设置为 null 不会改变变量过去包含的任何内容;它所做的只是改变变量名指向的内容,从先前的引用(这里是一个文档片段)到新的引用(null)。所以,当你这样做时

f = document.createFragment();
build.html = f;

那么,无论if/how以后f变量名被重新赋值,build.html都不会改变,因为它继续指向文档片段。改变 build.html 的唯一方法是片段被 突变 (例如在重新分配之前分配给 f 的 属性,或调用片段的方法之一)。

这是此行为的另一个最小示例:

function foo() {
  let f = { prop: 'val' };
  foo.f = f;
  f = null;
}

foo();

console.log(foo.f);

同样,在这里,尽管 f 被设置为 null,对象 { prop: 'val' } 保持完整且未发生变化,因此 foo.f 获得了对该对象的引用,并且即使在 f 被重新分配后仍继续保留该引用。

使用您的代码,由于内存中的片段保留在内存中,并且 build.html 即使在 f 被重新分配后仍保留对该片段的引用,克隆片段只是不必要的开销;随意省略它。

另一种可视化方式是您的代码:

function build() {
  let f;
  try {
    f = document.createFragment();
    // ...build the fragment... 
    build.html = f;
  } catch (e) {} finally {
    f = null;
  }
}

等同于

function build() {
  let f;
  try {
    // Create the object in memory:
    <MemRef#513513> = document.createFragment();
    // Point the variable `f` to that created object:
    f = <MemRef#513513>;
    // ...build the fragment... 
    build.html = <MemRef#513513>;
  } catch (e) {} finally {
    f = <MemRef#Null>;
  }
}