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

时间:2022-03-21 10:56:22

This jsFiddle illustrates the problem. If I'm understanding what's going on correctly, as I iterate over and modify the NodeList in place, the counter variable i misses every other node.

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

In that fiddle, I have two lists, #one and #two and I'd like to move all the children of #one to #two--

在那个小提琴中,我有两个列表,#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>

with some minimal JavaScript

用一些最小的JavaScript

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

I know I can fix this by simply converting the NodeList to an Array and iterating over the Array instead, but I was wondering what the right way to iterate over a NodeList if you are modifying the NodeList itself (and not just the nodes) is?

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

3 个解决方案

#1


2  

Node lists are often implemented as node iterators with a filter. This means that getting a property like length is O(n), and iterating over the list by re-checking the length will be O(n^2).

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

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

It is better to do this instead:

最好这样做:

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

This works well for all collections and arrays as long as the array does not contain things that are treated as boolean false.

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

In cases where you are iterating over the childNodes you can also use the firstChild and nextSibling properties.

在迭代childNodes的情况下,您还可以使用firstChild和nextSibling属性。

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

#2


2  

I'm not sure there's a "right" way. Whatever is easiest for you to implement, is easy to understand, and works, should be what you use. A simple slice of the NodeList shouldn't be a problem, even if there's the tiniest performance hit for simply creating a new array and copying references to its contents.

我不确定是否有“正确”的方式。无论您最容易实现什么,易于理解和工作,都应该是您使用的。 NodeList的一个简单片段不应该是一个问题,即使只是创建一个新数组并复制对其内容的引用是最微小的性能影响。

If you truly don't want to copy an array, you can loop over the NodeList backwards, guaranteeing that any changes you make to that node in the list won't actually affect the list.

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

Here's an example of what I mean:

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

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/

As you can see, it's not a straightforward change in looping - the logic of where/how to insert the node has to change too, since everything is backwards. That's why I changed the second argument to insertBefore to be destination.firstChild. I'm sure there's other ways to handle the logic, but the principle is the same.

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


References:

#3


1  

Using an array is the correct way to do things like this. Just like if you ever have to modify an array while you loop over it, you probably want to separate the modification part and the loop part so one can't affect the other.

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

In this case, doing that is accomplished by converting the NodeList to an array.

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

As suggested by Benjamin Gruenbaum, you could also use querySelectorAll, which returns a frozen NodeList.

正如Benjamin Gruenbaum所建议的那样,您也可以使用querySelectorAll,它返回一个冻结的NodeList。

Demo here

#1


2  

Node lists are often implemented as node iterators with a filter. This means that getting a property like length is O(n), and iterating over the list by re-checking the length will be O(n^2).

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

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

It is better to do this instead:

最好这样做:

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

This works well for all collections and arrays as long as the array does not contain things that are treated as boolean false.

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

In cases where you are iterating over the childNodes you can also use the firstChild and nextSibling properties.

在迭代childNodes的情况下,您还可以使用firstChild和nextSibling属性。

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

#2


2  

I'm not sure there's a "right" way. Whatever is easiest for you to implement, is easy to understand, and works, should be what you use. A simple slice of the NodeList shouldn't be a problem, even if there's the tiniest performance hit for simply creating a new array and copying references to its contents.

我不确定是否有“正确”的方式。无论您最容易实现什么,易于理解和工作,都应该是您使用的。 NodeList的一个简单片段不应该是一个问题,即使只是创建一个新数组并复制对其内容的引用是最微小的性能影响。

If you truly don't want to copy an array, you can loop over the NodeList backwards, guaranteeing that any changes you make to that node in the list won't actually affect the list.

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

Here's an example of what I mean:

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

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/

As you can see, it's not a straightforward change in looping - the logic of where/how to insert the node has to change too, since everything is backwards. That's why I changed the second argument to insertBefore to be destination.firstChild. I'm sure there's other ways to handle the logic, but the principle is the same.

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


References:

#3


1  

Using an array is the correct way to do things like this. Just like if you ever have to modify an array while you loop over it, you probably want to separate the modification part and the loop part so one can't affect the other.

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

In this case, doing that is accomplished by converting the NodeList to an array.

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

As suggested by Benjamin Gruenbaum, you could also use querySelectorAll, which returns a frozen NodeList.

正如Benjamin Gruenbaum所建议的那样,您也可以使用querySelectorAll,它返回一个冻结的NodeList。

Demo here