【问题标题】:Why is this while loop infinite? JavaScript appendChild为什么这个while循环是无限的? JavaScript appendChild
【发布时间】:2012-11-01 04:29:45
【问题描述】:

我发誓这几天前还运行良好...

elm = document.querySelectorAll(selector);
var frag = document.createDocumentFragment();
while (elm[0]){
    frag.appendChild(elm[0]);
}

对,所以,这应该从我们的elm 节点列表中附加每个节点。当第一个被附加时,第二个“移动”到节点列表中的第一个位置,因此下一个总是elm[0]。当elm nodeList 完全附加时,它应该停止。但是,这给了我一个无限循环。想法?

编辑 - 因为我多次得到相同的答案... nodeList 不是数组,它是实时引用。当一个节点被“移动”(此处为附加)时,它应该自动从节点列表中删除。答案都说“你一遍又一遍地附加相同的元素” - 这就是正在发生的事情,它不应该。 for 循环不应该工作,因为当第一个节点被追加时,下一个节点会使用它的索引。

第二次编辑

所以现在的问题是“为什么nodeList 表现得像一个数组?”。每次在某处追加节点时,节点列表应该更新。最奇特的。

解决方案(以防有人需要处理实时+非实时节点列表)

elm = (/*however you're getting a node list*/);
var frag = document.createDocumentFragment();
var elength = elm.length;
for (var b = 0; b<elength; b++){
    if (elm.length === elength){
        frag.appendChild(elm[b]);
    } else {
        frag.appendChild(elm[0].cloneNode());
    }
}

基本上,只是检查节点列表是否改变了长度。

【问题讨论】:

  • 您在任何时候都不要修改elm 节点列表,这意味着while 循环将继续追加列表中的第一个元素。
  • 我认为这里的主要问题是 JavaScript 不是 C。如果我没记错的话,数组不是就地修改的,而是复制的。
  • 所以现在的问题是“为什么 nodeList 表现得像一个数组?”。每次将节点附加到某个地方时,节点列表都应该更新。最奇特的。 谁教你的?这不是现场直播。请参阅下面我编辑的帖子,其中包含指向文档的链接。
  • @epascarello developer.mozilla.org/en-US/docs/DOM/NodeList 过去每次我使用节点列表。
  • 仅供参考,这是完全相反的问题,可以说是infinite-loop via prepend element in DOM,其中 live 列表导致了无限循环。

标签: javascript while-loop infinite-loop appendchild


【解决方案1】:

来自MDN Docs

Element.querySelectorAll

总结

返回一个 non-live NodeList,其中包含调用它的元素与指定的 CSS 选择器组匹配的所有元素。

语法

elementList = baseElement.querySelectorAll(selectors);

在哪里

  • elementList 是元素对象的非实时列表。
  • baseElement 是一个元素对象。
  • selectors 是一组要匹配的选择器。

从上面的文档中您可以看到,当您将其附加到另一个元素时,它不会自动删除它,因为它是非实时的。运行演示以展示该功能。

var selector = "div";
elm = document.querySelectorAll(selector);
var frag = document.createDocumentFragment();
console.log("before",elm.length);
frag.appendChild(elm[0]);
console.log("after",elm.length);

当上面的代码运行时,在你得到的控制台中。

before    3
after     3

如果你想做while循环,转换成一个数组并shift()关闭项目

var selector = "div";
var elmNodeLIst = document.querySelectorAll(selector);
var frag = document.createDocumentFragment();
var elems = Array.prototype.slice.call(elmNodeLIst );
while (elems.length) {
    frag.appendChild(elems.shift());
}
console.log(frag);

【讨论】:

  • 看来您的答案最有说服力。这是一个问题大声笑。我有时有其他检索节点列表的方法,这是更大系统的一部分。我将不得不为实时/非实时节点列表创建一个解决方法,这些节点列表被传递到同一函数以创建文档片段。不过信息很好。
【解决方案2】:

您一遍又一遍地追加节点列表中的第一项。您永远不会从数组中删除任何项目,而是始终将第一个项目添加到片段中。第一个总是一样的。

elm = document.querySelectorAll(selector);
var frag = document.createDocumentFragment();
while (elm.length){
    frag.appendChild(elm.shift());
}

这可能更接近您的意图。我们可以使用while (elm.length),因为随着项目从数组中移除,最终长度将为零,这是一个不稳定的值并且循环将停止。

我们使用elm.shift() 从数组中获取项目,因为该方法将返回索引为零的项目并将其从数组中删除,这为我们提供了我们需要的原始数组的突变。


我认为您认为这可能可行,因为节点只能有一个父节点。意味着添加某处会将其从前一个父级中删除。然而,elm 不是一个 DOM 片段。它只是一个包含对元素的引用的数组(或者可能是 NodeList)。该数组不是这些元素的父节点,它只是保存引用。

如果你有这样的循环,你的循环可能会起作用,因为你每次都在查询父节点的子节点,一个节点列表实际上会随着你的移动而改变:

elm = document.getElementById(id);
var frag = document.createDocumentFragment();
while (elm.children[0]){
    frag.appendChild(elm.children[0]);
}

【讨论】:

  • 这就是问题所在....如果 elm 是 nodeList,则选择 nodes(此处为 elements),当它们附加到 documentFragment 时,它们是从 dom 中删除,并且不应再存在于 nodeList 中。
【解决方案3】:

一开始我没想到它会起作用。

您的elm 数组已初始化,并且从未更新。即使运行 document.querySelectorAll(selector); 的结果会返回不同的结果,这也不会更改数组中的当前引用。

您要么需要重新运行选择器,要么在追加后手动删除数组中的第一个元素。

【讨论】:

  • 节点列表不是普通数组。节点列表应该与对节点所做的更改一起“实时更新”。所以当列表中的一个节点被追加时,当前节点列表应该被重写。也许对原始节点的引用仍然存在……有趣的想法。我会进一步研究它。
【解决方案4】:

elm[0] 在上面的代码中是静态不变的 修复如下

elm = document.querySelectorAll(".container");
var frag = document.createDocumentFragment();
console.log(elm);
var i=0;
while (elm[i]){
    frag.appendChild(elm[i++]);
}

【讨论】:

    【解决方案5】:

    我实际上并没有过多关注代码(如果它有意义 - 从 cmets 判断 - 与否);但如果这在几天前有效,那么问题出在您提供给代码选择器的输入中。

    这时单元测试就派上用场了。如果你能记住代码工作的输入,那么你可以让它再次工作并从那里开始调试。

    否则,你只是在欺骗自己。

    【讨论】:

      【解决方案6】:

      它现在是一个无限循环,因为elm[0] 总是引用同一个元素,并且该元素永远不会为空(任何非空/非零结果都是真的)。您也不会对元素本身做任何事情以使其遍历列表。您应该使用 for 循环而不是 while,或者至少使用某种索引器来尝试遍历集合。

      elm = document.querySelectorAll(selector);
      var frag = document.createDocumentFragment();
      for (i= 0; i < elm.length; i++)
      {
          frag.appendChild(elm[i]);
      }
      

      编辑:

      来自documentation

      “现场”收藏

      在大多数情况下,NodeList 是一个实时集合。这意味着 DOM 树上的更改 > 将反映在集合上。

      var links = document.getElementsByTagName('a'); // links.length === 2 例如

      document.body.appendChild(链接[0].cloneNode(true)); // 另一个链接被添加到文档
      // 'links' NodeList 会自动更新
      // links.length === 3 现在。如果 NodeList 是 document.querySelectorAll 的返回值,它是 NOT live。

      继续阅读本文档,您当前对该方法的使用表明您没有实时 NodeList。因此追加永远不会修改原始列表。您将需要在循环中修改您的用法以反映 .cloneNode(true) 的这种用法或手动迭代。

      【讨论】:

      • @RandyHall:是的,当我发布对我的问题的编辑时,我被叫走了,该编辑从文档中显示了我为什么提出我所做的答案,可能还有一种不同的方法来实现你的结果寻找。
      猜你喜欢
      • 2012-05-02
      • 2011-11-15
      • 2016-07-14
      • 1970-01-01
      • 2018-03-17
      • 2016-07-27
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多