64

In the past and with most my current projects I tend to use a for loop like this:

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

I've heard that using a "reverse while" loop is quicker but I have no real way to confirm this:

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

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

What is considered as best practice when it comes to looping though elements in JavaScript, or any array for that matter?

James
  • 109,676
  • 31
  • 162
  • 175
  • 7
    It would be great if you would mark one of the answers as answered. That is after all one of the major points of SO :) – cllpse Oct 08 '08 at 22:28
  • 1
    It would also be great if you re-picked an accepted answer now that the accepted one is worthless >:) . . . then again, this is really old so I don't really care. – B Robster Jun 06 '12 at 13:18

14 Answers14

65

Here's a nice form of a loop I often use. You create the iterated variable from the for statement and you don't need to check the length property, which can be expensive specially when iterating through a NodeList. However, you must be careful, you can't use it if any of the values in array could be "falsy". In practice, I only use it when iterating over an array of objects that does not contain nulls (like a NodeList). But I love its syntactic sugar.

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);
}

Note that this can also be used to loop backwards.

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 Update

for...of can be used for a cleaner syntax. Available since ES6

const a = ["a", "b", "c"];

for (const [index, element] of a.entries()) {
  console.log({index, element});
}

// If you don't need the index
for (const element of a) {
  console.log({element});
}
 
Ruan Mendes
  • 90,375
  • 31
  • 153
  • 217
  • I love this so much. At first I wondered how the loop would ever exit then I remembered that the "middle" of the declaration is basically a while and will exit when evaluated to false. Assigning a variable an array index that doesn't exist == false! Quite clever. – Joel Mellon Oct 18 '12 at 18:09
  • 5
    @sudopeople Just to be 100% accurate, when the item doesn't exist, it returns `undefined` which is falsy. – Ruan Mendes Oct 18 '12 at 18:54
  • 1
    Drats, didn't see this one! Sorry for the dupe! – syockit Nov 28 '13 at 05:39
29

Note that in some cases, you need to loop in reverse order (but then you can use i-- too).

For example somebody wanted to use the new getElementsByClassName function to loop on elements of a given class and change this class. He found that only one out of two elements was changed (in FF3).
That's because the function returns a live NodeList, which thus reflects the changes in the Dom tree. Walking the list in reverse order avoided this issue.

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

In increasing index progression, when we ask the index 1, FF inspects the Dom and skips the first item with style2, which is the 2nd of the original Dom, thus it returns the 3rd initial item!

PhiLho
  • 40,535
  • 6
  • 96
  • 134
  • 2
    A good point, though I wouldn't advise to change the className on the fly, as it forces the browser to recalculate the rendering of the entire document ... – roenving Oct 01 '08 at 12:27
  • @roenving - You could theoretically copy the whole document to memory, change anything you want, and replace the whole document with the new, modified document. but it depends on the situation. – vsync Apr 26 '15 at 22:36
11

I like doing:

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

There is no call to the length of the array on every iteration.

  • A very quick test (directly in my editors browser) shows that a thousand iterations isn't possible to tell time for, with your scheme it says 15 ms, with the original testing the length for each iteration it's 31 ms ... – roenving Oct 01 '08 at 13:22
  • 5
    Just make sure you don't start using this on arrays where null,undefined,false,0,"" are valid elements! – Ali Oct 01 '08 at 13:33
7

At the risk of getting yelled at, i would get a javascript helper library like jquery or prototype they encapsulate the logic in nice methods - both have an .each method/iterator to do it - and they both strive to make it cross-browser compatible

EDIT: This answer was posted in 2008. Today much better constructs exist. This particular case could be solved with a .forEach.

Per Hornshøj-Schierbeck
  • 15,097
  • 21
  • 80
  • 101
  • @Hojou - Obviously I use the respective javaScript libraries for many projects - they are time-savers and don't cause me as many headaches but with some smaller projects I find it's better to write my own methods. jQuery is definitely my favourite though due its fantastic simplicity! – James Oct 01 '08 at 12:04
  • 7
    Why employ a library to perform a simple task ?-) – roenving Oct 01 '08 at 12:05
  • 2
    I know, i know. I just like including jquery to minimize the risk of introducing a cross-browser imcompatibility issue. You don't need a lot of code in your 'doSomething' to posibly introduce one of those errors. – Per Hornshøj-Schierbeck Oct 01 '08 at 12:12
  • 7
    Honestly, jQuery just to deal with loops? Insanity. – Tim Down Oct 03 '09 at 23:16
  • 1
    @Tim: In reality I think there are very few sites i would create using javascript, where adding jquery would be insanity? What are the complications of using jquery compared not to? – Per Hornshøj-Schierbeck Oct 05 '09 at 14:49
  • Sorry, I was perhaps a little harsh. In brief, using jQuery means the user's browser has to download 50K of JavaScript, which may bring a significant performance hit (on a mobile device, for example). For that price, jQuery (or any library) should be delivering significant benefit to the user. I don't think providing an iterator function qualifies, particularly when there's no browser incompatibilities with loops to be worked round. – Tim Down Oct 06 '09 at 09:00
  • You are right about the performance hit - especially regarding such devices as mobile :) I guess it depends a lot on the target audience and size of the website generally. I totally agree that adding jquery would be crazy if you're creating a 'simple' website. I'm just colored from my world (classifieds/newspaper/administration websites). Depends on the context, ofcourse... – Per Hornshøj-Schierbeck Oct 08 '09 at 08:25
  • No need to include a lib for this, `Array.forEach` is built-in and it's easy to implement it for browsers that don't support it https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/forEach. Beware that using functions like `Array.each, jQuery.forEach` to loop if you're in a tight loop will be a performance hit. However, it's very convenient and the difference is not noticeable in loops with fewer iterations. – Ruan Mendes Jun 08 '12 at 17:03
  • 2
    This question may have been OK when it was written. It's pretty much obsolete now since most browsers (IE9+) support [Array.prototype.forEach](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/forEach#AutoCompatibilityTable) – Ruan Mendes May 28 '14 at 15:59
  • I abhor debugging a `forEach` loop. Step in, step out. It's so much easier to just debug a `for` loop; – Ruan Mendes Jan 28 '15 at 20:47
7

I had a very similar problem earlier with document.getElementsByClassName(). I didn't know what a nodelist was at the time.

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

My issue was that I expected that elements would be an array, but it isn't. The nodelist Document.getElementsByTagName() returns is iterable, but you can't call array.prototype methods on it.

You can however populate an array with nodelist elements like this:

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

After that you can feel free to call .innerHTML or .style or something on the elements of your array.

6

I think using the first form is probably the way to go, since it's probably by far the most common loop structure in the known universe, and since I don't believe the reverse loop saves you any time in reality (still doing an increment/decrement and a comparison on each iteration).

Code that is recognizable and readable to others is definitely a good thing.

Adam Bellaire
  • 108,003
  • 19
  • 148
  • 163
6

I too advise to use the simple way (KISS !-)

-- but some optimization could be found, namely not to test the length of an array more than once:

var elements = document.getElementsByTagName('div');
for (var i=0, im=elements.length; im>i; i++) {
    doSomething(elements[i]);
}
roenving
  • 2,560
  • 14
  • 14
  • A nice improvement actually... Definitely going to use this on all my "for" loops from now on! thank you! – James Oct 01 '08 at 12:41
  • Unless what happens within doSomething() could change the total number of elements in the array. – Ken Ray Oct 01 '08 at 12:43
4

Also see my comment on Andrew Hedges' test ...

I just tried to run a test to compare a simple iteration, the optimization I introduced and the reverse do/while, where the elements in an array was tested in every loop.

And alas, no surprise, the three browsers I tested had very different results, though the optimized simple iteration was fastest in all !-)

Test:

An array with 500,000 elements build outside the real test, for every iteration the value of the specific array-element is revealed.

Test run 10 times.

IE6:

Results:

Simple: 984,922,937,984,891,907,906,891,906,906

Average: 923.40 ms.

Optimized: 766,766,844,797,750,750,765,765,766,766

Average: 773.50 ms.

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

Average: 1593.80 ms. (Note one especially awkward result)

Opera 9.52:

Results:

Simple: 344,343,344,359,343,359,344,359,359,359

Average: 351.30 ms.

Optimized: 281,297,297,297,297,281,281,297,281,281

Average: 289.00 ms

Reverse do/while: 391,407,391,391,500,407,407,406,406,406

Average: 411.20 ms.

FireFox 3.0.1:

Results:

Simple: 278,251,259,245,243,242,259,246,247,256

Average: 252.60 ms.

Optimized: 267,222,223,226,223,230,221,231,224,230

Average: 229.70 ms.

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

Average: 389.10 ms.

roenving
  • 2,560
  • 14
  • 14
3

Form of loop provided by Juan Mendez is very useful and practical, I changed it a little bit, so that now it works with - false, null, zero and empty strings too.

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

for(var i = 0, item; (item = items[i]) !== undefined; i++)
{
    console.log("Index: " + i + "; Value: " + item);
}
2

I know that you don't want to hear that, but: I consider the best practice is the most readable in this case. As long as the loop is not counting from here to the moon, the performance-gain will not be uhge enough.

Georgi
  • 4,402
  • 4
  • 24
  • 20
  • Agreed. If you're counting backwards, write a comment that says "its merely a speed issue", then anyone who wants to edit your code won't be confused by it. – Ali Oct 01 '08 at 13:35
2

I know this question is old -- but here's another, extremely simple solution ...

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

// Loop.
elements.forEach(function(value) {
  yourFunction(value);
});
Dustin Halstead
  • 741
  • 6
  • 19
0

I prefer the for loop as it's more readable. Looping from length to 0 would be more efficient than looping from 0 to length. And using a reversed while loop is more efficient than a foor loop as you said. I don't have the link to the page with comparison results anymore but I remember that the difference varied on different browsers. For some browser the reversed while loop was twice as fast. However it makes no difference if you're looping "small" arrays. In your example case the length of elements will be "small"

Gene
  • 1,517
  • 1
  • 14
  • 15
  • Why does looping from length to 0 would be more efficient than looping from 0 to length ? – Mehdi Apr 25 '19 at 20:23
0

I think you have two alternatives. For dom elements such as jQuery and like frameworks give you a good method of iteration. The second approach is the for loop.

David Robbins
  • 9,996
  • 7
  • 51
  • 82
-1

I like to use a TreeWalker if the set of elements are children of a root node.

GijsjanB
  • 9,944
  • 4
  • 23
  • 27