遍历HTMLCollection时递增for循环出现异常行为的原因解析
HTMLCollection存在违反直觉的异常行为:尽管文档推荐使用递增for循环遍历,但当调用appendChild将集合内元素移动到外部元素时,循环表现异常。移除appendChild操作或将HTMLCollection转为数组后,输出恢复正常。
示例代码
<!DOCTYPE html> <html> <head> </head> <body> <div id="source"> <a src="" class="link">Link_0</a> <a src="" class="link">Link_1</a> <a src="" class="link">Link_2</a> <a src="" class="link">Link_3</a> <a src="" class="link">Link_4</a> <a src="" class="link">Link_5</a> <a src="" class="link">Link_6</a> <a src="" class="link">Link_7</a> <a src="" class="link">Link_8</a> </div> <div id="list"> </div> <script type="text/javascript"> var links = document.getElementsByClassName('link') var list = document.getElementById('list') var linksArray = Array.from(links) for (var i = 0; i < links.length; i++) { let current = links[i] // let current = linksArray[i] let number = document.createElement('span') number.innerHTML = '<br>' + i + ': innerText: ' + current.innerText + ' element: ' list.appendChild(number) list.appendChild(current) } </script> </body> </html>
异常输出
Link_1 Link_3 Link_5 Link_7 0: innerText: Link_0 element: Link_0 1: innerText: Link_2 element: 2: innerText: Link_4 element: Link_4 3: innerText: Link_6 element: 4: innerText: Link_8 element: Link_8 5: innerText: Link_2 element: 6: innerText: Link_6 element: Link_6 7: innerText: Link_2 element: 8: innerText: Link_2 element: Link_2
疑问解答
1. 为何文档推荐的for循环在操作元素时会导致集合异常?
文档推荐的递增for循环仅适用于不修改集合成员或DOM结构的场景。getElementsByClassName返回的HTMLCollection是动态实时更新的集合,每次访问links.length或links[i]时,都会重新查询当前DOM结构生成集合。当你在循环中移动元素,集合的成员顺序会实时变化,导致循环索引和目标元素的对应关系错乱——比如初始集合是[L0,L1,L2,...L8],移走L0后集合变为[L1,L2,...L8,L0],此时i递增到1会直接取到L2,跳过了L1,最终出现元素被跳过或重复处理的情况。
2. 移动元素为何会改变HTMLCollection?
HTMLCollection的核心特性是与DOM状态实时绑定,它的成员顺序完全按照元素在文档中的出现顺序排列。当你用appendChild移动元素时,元素会从原位置移除并插入到新位置,DOM结构发生变化,HTMLCollection会自动同步更新成员顺序,因此集合的结构会随之改变。
3. 为何请求不存在的元素时无报错?
HTMLCollection是类数组对象,当访问超出当前集合长度的索引时,会返回null而非抛出错误。但在你的场景中,并没有访问不存在的元素——所有被访问的元素都还在文档中且保留link类,只是因为集合实时更新,导致部分元素被重复访问。如果真的出现current为null的情况,访问current.innerText会抛出错误,但你的输出中没有报错,说明循环中访问的索引始终在集合长度范围内,只是元素重复了。
4. 输出顺序混乱的原因是什么?
输出混乱由两个核心原因导致:
- 动态集合的实时更新让循环索引与元素的对应关系错乱,直接跳过了部分元素(如L1、L3、L5、L7);
- 被移动的元素会被插入到文档的后续位置,因此会出现在集合的末尾,导致后续循环重复处理这些元素,最终造成输出顺序和元素插入顺序的混乱。
内容的提问来源于stack exchange,提问作者algo

