62

在过去和我目前的大多数项目中,我倾向于使用这样的 for 循环:

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

我听说使用“反向while”循环更快,但我没有真正的方法来确认这一点:

var elements = document.getElementsByTagName('div'), 
    length = elements.length;

while(length--) {
    doSomething(elements[length]);
}

在循环遍历 JavaScript 中的元素或任何数组时,什么被认为是最佳实践?

4

14 回答 14

61

这是我经常使用的一种很好的循环形式。您从 for 语句创建迭代变量,并且不需要检查长度属性,这在迭代 NodeList 时可能特别昂贵。但是,您必须小心如果数组中的任何值可能是 "falsy" ,则不能使用它。在实践中,我只在迭代不包含空值的对象数组(如 NodeList)时使用它。但我喜欢它的语法糖。

var list = [{a:1,b:2}, {a:3,b:5}, {a:8,b:2}, {a:4,b:1}, {a:0,b:8}];

for (var i=0, item; item = list[i]; i++) {
  // Look no need to do list[i] in the body of the loop
  console.log("Looping: index ", i, "item" + item);
}

请注意,这也可以用于向后循环。

var list = [{a:1,b:2}, {a:3,b:5}, {a:8,b:2}, {a:4,b:1}, {a:0,b:8}];
    
for (var i = list.length - 1, item; item = list[i]; i--) {
  console.log("Looping: index ", i, "item", item);
}

ES6 更新

for...of给你名字而不是索引,从ES6开始可用

for (const item of list) {
    console.log("Looping: index ", "Sorry!!!", "item" + item);
}
于 2011-01-06T21:57:28.350 回答
27

请注意,在某些情况下,您需要以相反的顺序循环(但您也可以使用 i-- )。

例如,有人想使用新getElementsByClassName函数循环给定类的元素并更改该类。他发现只有二分之一的元素被改变了(在FF3中)。
这是因为该函数返回一个活动的 NodeList,从而反映了 Dom 树中的变化。以相反的顺序遍历列表可以避免这个问题。

var menus = document.getElementsByClassName("style2");
for (var i = menus.length - 1; i >= 0; i--)
{
  menus[i].className = "style1";
}

在增加索引进程中,当我们询问索引 1 时,FF 检查 Dom 并跳过第一个具有 style2 的项,即原始 Dom 的第 2 个,因此它返回第 3 个初始项!

于 2008-10-01T12:23:51.197 回答
9

我喜欢这样做:

 
var menu = document.getElementsByTagName('div');
for (var i = 0; menu[i]; i++) {
     ...
}

每次迭代都不会调用数组的长度。

于 2008-10-01T13:04:13.360 回答
7

冒着被骂的风险,我会得到一个像jquery原型这样的 javascript 帮助程序库,它们将逻辑封装在很好的方法中 - 两者都有一个 .each 方法/迭代器来做到这一点 - 他们都努力使其跨浏览器兼容

编辑:这个答案发布于 2008 年。今天存在更好的结构。这种特殊情况可以用.forEach.

于 2008-10-01T11:59:52.500 回答
7

我之前遇到过与 document.getElementsByClassName() 非常相似的问题。我当时不知道节点列表是什么。

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

我的问题是我希望元素是一个数组,但事实并非如此。返回的节点列表 Document.getElementsByTagName() 是可迭代的,但您不能在其上调用 array.prototype 方法。

但是,您可以使用如下节点列表元素填充数组:

var myElements = [];
for (var i=0; i<myNodeList.length; i++) {                               
    var element = myNodeList[i];
    myElements.push(element);
};

之后,您可以随意在数组元素上调用 .innerHTML 或 .style 或其他内容。

于 2016-03-16T01:05:17.433 回答
6

我也建议使用简单的方法(亲吻!-)

-- 但是可以找到一些优化,即不要多次测试数组的长度:

var elements = document.getElementsByTagName('div');
for (var i=0, im=elements.length; im>i; i++) {
    doSomething(elements[i]);
}
于 2008-10-01T12:08:28.300 回答
6

我认为使用第一种形式可能是要走的路,因为它可能是迄今为止已知宇宙中最常见的循环结构,而且我不相信反向循环在现实中可以节省您的任何时间(仍在做增量/每次迭代的递减和比较)。

其他人可识别和可读的代码绝对是一件好事。

于 2008-10-01T12:00:24.090 回答
4

另请参阅我对 Andrew Hedges 测试的评论...

我只是尝试运行一个测试来比较一个简单的迭代、我引入的优化和反向 do/while,其中数组中的元素在每个循环中都进行了测试。

唉,毫不奇怪,我测试的三个浏览器的结果非常不同,尽管优化后的简单迭代是最快的!-)

测试:

在实际测试之外构建一个包含 500,000 个元素的数组,每次迭代都会显示特定数组元素的值。

试运行 10 次。

IE6:

结果:

简单:984,922,937,984,891,907,906,891,906,906

平均:923.40 毫秒。

优化:766,766,844,797,750,750,765,765,766,766

平均:773.50 毫秒。

反向do/while:3375,1328,1516,1344,1375,1406,1688,1344,1297,1265

平均:1593.80 毫秒。(注意一个特别尴尬的结果)

歌剧 9.52:

结果:

简单:344,343,344,359,343,359,344,359,359,359

平均:351.30 毫秒。

优化:281,297,297,297,297,281,281,297,281,281

平均:289.00 毫秒

反向执行/同时:391,407,391,391,500,407,407,406,406,406

平均:411.20 毫秒。

火狐 3.0.1:

结果:

简单:278,251,259,245,243,242,259,246,247,256

平均:252.60 毫秒。

优化:267,222,223,226,223,230,221,231,224,230

平均:229.70 毫秒。

反向do/while:414,381,389,383,388,389,381,387,400,379

平均:389.10 毫秒。

于 2008-10-02T10:22:23.663 回答
3

Juan Mendez提供的循环形式非常有用且实用,我对其进行了一些更改,现在它也适用于 - false、null、零和空字符串。

var items = [
    true,
    false,
    null,
    0,
    ""
];

for(var i = 0, item; (item = items[i]) !== undefined; i++)
{
    console.log("Index: " + i + "; Value: " + item);
}
于 2015-01-04T07:53:26.370 回答
2

我知道你不想听到这个,但是:我认为在这种情况下最佳实践是最易读的。只要循环不是从这里到月球,性能增益就不够。

于 2008-10-01T12:02:11.357 回答
2

我知道这个问题很老 - 但这是另一个非常简单的解决方案......

var elements = Array.from(document.querySelectorAll("div"));

然后它可以像任何标准数组一样使用。

于 2018-03-12T19:55:03.180 回答
0

我更喜欢 for 循环,因为它更具可读性。从长度循环到 0 比从 0 循环到长度更有效。正如你所说,使用反向 while 循环比 foo 循环更有效。我不再有指向具有比较结果的页面的链接,但我记得不同浏览器的差异有所不同。对于某些浏览器,反向 while 循环的速度是原来的两倍。但是,如果您循环“小”数组,则没有区别。在您的示例中,元素的长度将是“小”

于 2008-10-01T12:12:26.697 回答
0

我认为你有两种选择。对于 dom 元素,如 jQuery 和类似框架,为您提供了一种很好的迭代方法。第二种方法是 for 循环。

于 2008-10-02T10:32:20.923 回答
-1

如果元素集是根节点的子节点,我喜欢使用TreeWalker 。

于 2014-10-07T08:53:15.660 回答