什么是迭代NodeList并移动其元素而不转换为数组的惯用方法?

时间:2014-03-11 16:41:34

标签: javascript html arrays dom idioms

This jsFiddle说明了这个问题。如果我正确理解正在发生的事情,当我迭代并修改NodeList时,计数器变量i会错过其他所有节点。

在那个小提琴中,我有两个列表#one#two,我想将#one的所有孩子移到#two -

<ol id='one'>
    <li class='move'>one</li>
    <li class='move'>two</li>
    <li class='move'>three</li>
    <li class='move'>four</li>
    <li class='move'>four</li>
</ol>

<ol id='two'>
</ol>

使用一些最小的JavaScript

var lisToMove = document.getElementsByClassName('move');
var destination = document.getElementById('two');
for (var i = 0; i < lisToMove.length; i++) {
    destination.insertBefore(lisToMove[i], null);
}

我知道我可以通过简单地将NodeList转换为Array并迭代Array来解决此问题,但我想知道迭代{{1}的正确方法是什么1}}如果要修改NodeList本身(而不仅仅是节点)是?

3 个答案:

答案 0 :(得分:2)

我不确定是否有&#34;对&#34;办法。无论您最容易实现什么,易于理解,并且有效,应该是您使用的。 slice的简单NodeList不应该是一个问题,即使只是创建一个新数组并复制对其内容的引用,这是最微小的性能影响。

如果您真的不想复制数组,可以向后循环NodeList,保证您对列表中该节点所做的任何更改实际上都不会影响列表

这是我的意思的一个例子:

var lisToMove = document.getElementsByClassName('move');
var destination = document.getElementById('two');
var i = lisToMove.length;
while (i--) {
    destination.insertBefore(lisToMove[i], destination.firstChild);
}

DEMO: http://jsfiddle.net/TYZPD/

正如您所看到的,它不是循环中的直接变化 - 插入节点的位置/方式的逻辑也必须改变,因为一切都是向后的。这就是为什么我将第二个参数更改为insertBeforedestination.firstChild的原因。我确定还有其他处理逻辑的方法,但原理是一样的。


<强>参考文献:

答案 1 :(得分:2)

节点列表通常实现为带有过滤器的节点迭代器。这意味着获取像length这样的属性是O(n),并通过重新检查长度迭代列表将是O(n ^ 2)。

var paragraphs = document.getElementsByTagName('p');
for (var i = 0; i < paragraphs.length; i++) {
  doSomething(paragraphs[i]);
}

最好这样做:

var paragraphs = document.getElementsByTagName('p');
for (var i = 0, paragraph; paragraph = paragraphs[i]; i++) {
   doSomething(paragraph);
} 

这适用于所有集合和数组,只要该数组不包含被视为boolean false的内容。

如果您正在迭代childNodes,您还可以使用firstChild和nextSibling属性。

var parentNode = document.getElementById('foo');
for (var child = parentNode.firstChild; child; child = child.nextSibling) {
  doSomething(child);
}

答案 2 :(得分:1)

使用数组 是执行此类操作的正确方法。就像你必须修改一个数组在它上面循环一样,你可能想要将修改部分和循环部分分开,这样就不会影响另一个。

在这种情况下,通过将NodeList转换为数组来完成此操作。

根据Benjamin Gruenbaum的建议,您还可以使用querySelectorAll,它会返回一个冻结的NodeList。

Demo here