Vous êtes sur la page 1sur 7

TL;DR

 Don't use for-in unless you use it with safeguards or are at least aware of why it might bite you.
 Your best bets are usually
 a for-of loop (ES2015+ only),
 Array#forEach (spec | MDN) (or its relatives some and such) (ES5+ only),
 a simple old-fashioned for loop,
 or for-in with safeguards.
But there's lots more to explore, read on...

JavaScript has powerful semantics for looping through arrays and array-like objects. I've split the answer into
two parts: Options for genuine arrays, and options for things that are just array-like, such as
the arguments object, other iterable objects (ES2015+), DOM collections, and so on.
I'll quickly note that you can use the ES2015 options now, even on ES5 engines, by transpilingES2015 to ES5.
Search for "ES2015 transpiling" / "ES6 transpiling" for more...
Okay, let's look at our options:
For Actual Arrays
You have three options in ECMAScript 5 ("ES5"), the version most broadly supported at the moment, and will
soon have two more in ECMAScript 2015 ("ES2015", "ES6"), the latest version of JavaScript that vendors are
working on supporting:
1. Use forEach and related (ES5+)
2. Use a simple for loop
3. Use for-in correctly
4. Use for-of (use an iterator implicitly) (ES2015+)
5. Use an iterator explicitly (ES2015+)
Details:
1. Use forEach and related
If you're using an environment that supports the Array features of ES5 (directly or using a shim), you can use
the new forEach (spec | MDN):
var a = ["a", "b", "c"];
a.forEach(function(entry) {
console.log(entry);
});
forEach accepts an iterator function and, optionally, a value to use as this when calling that iterator function
(not used above). The iterator function is called for each entry in the array, in order, skipping non-existent
entries in sparse arrays. Although I only used one argument above, the iterator function is called with three:
The value of each entry, the index of that entry, and a reference to the array you're iterating over (in case
your function doesn't already have it handy).
Unless you're supporting obsolete browsers like IE8 (which NetApps shows at just over 4% market share as
of this writing in September 2016), you can happily use forEach in a general-purpose web page without a
shim. If you do need to support obsolete browsers, shimming/polyfilling forEach is easily done (search for
"es5 shim" for several options).
forEach has the benefit that you don't have to declare indexing and value variables in the containing scope,
as they're supplied as arguments to the iteration function, and so nicely scoped to just that iteration.
If you're worried about the runtime cost of making a function call for each array entry, don't be; details.
Additionally, forEach is the "loop through them all" function, but ES5 defined several other useful "work your
way through the array and do things" functions, including:
 every (stops looping the first time the iterator returns false or something falsey)
 some (stops looping the first time the iterator returns true or something truthy)
 filter (creates a new array including elements where the filter function returns true and omitting the
ones where it returns false)
 map (creates a new array from the values returned by the iterator function)
 reduce (builds up a value by repeated calling the iterator, passing in previous values; see the spec for the
details; useful for summing the contents of an array and many other things)
 reduceRight (like reduce, but works in descending rather than ascending order)
2. Use a simple for loop
Sometimes the old ways are the best:
var index;
var a = ["a", "b", "c"];
for (index = 0; index < a.length; ++index) {
console.log(a[index]);
}
If the length of the array won't change during the loop, and it's in performance-sensitive code (unlikely), a
slightly more complicated version grabbing the length up front might be a tiny bit faster:
var index, len;
var a = ["a", "b", "c"];
for (index = 0, len = a.length; index < len; ++index) {
console.log(a[index]);
}
And/or counting backward:
var index;
var a = ["a", "b", "c"];
for (index = a.length - 1; index >= 0; --index) {
console.log(a[index]);
}
But with modern JavaScript engines, it's rare you need to eke out that last bit of juice.
In ES2015 and higher, you can make your index and value variables local to the for loop:
let a = ["a", "b", "c"];
for (let index = 0; index < a.length; ++index) {
let value = a[index];
}
//console.log(index); // Would cause "ReferenceError: index is not defined"
//console.log(value); // Would cause "ReferenceError: value is not defined"
And when you do that, not just value but also index is recreated for each loop iteration, meaning closures
created in the loop body keep a reference to the index (and value) created for that specific iteration:
let divs = document.querySelectorAll("div");
for (let index = 0; index < divs.length; ++index) {
divs[index].addEventListener('click', e => {
alert("Index is: " + index);
});
}
If you had five divs, you'd get "Index is: 0" if you clicked the first and "Index is: 4" if you clicked the last. This
does not work if you use var instead of let.
3. Use for-in correctly
You'll get people telling you to use for-in, but that's not what for-in is for. for-in loops through the enumerable
properties of an object, not the indexes of an array. The order is not guaranteed, not even in ES2015 (ES6).
ES2015 does define an order to object properties (via [[OwnPropertyKeys]], [[Enumerate]], and things that use
them like Object.getOwnPropertyKeys), but it does not define that for-in will follow that order. (Details in this
other answer.)
Still, it can be useful, particularly for sparse arrays, if you use appropriate safeguards:
// `a` is a sparse array
var key;
var a = [];
a[0] = "a";
a[10] = "b";
a[10000] = "c";
for (key in a) {
if (a.hasOwnProperty(key) && // These are explained
/^0$|^[1-9]\d*$/.test(key) && // and then hidden
key <= 4294967294 // away below
){
console.log(a[key]);
}
}
Note the two checks:
1. That the object has its own property by that name (not one it inherits from its prototype), and
2. That the key is a base-10 numeric string in its normal string form and its value is <= 2^32 - 2 (which is
4,294,967,294). Where does that number come from? It's part of the definition of an array index in the
specification. Other numbers (non-integers, negative numbers, numbers greater than 2^32 - 2) are not
array indexes. The reason it's 2^32 - 2 is that that makes the greatest index value one lower than 2^32 -
1, which is the maximum value an array's lengthcan have. (E.g., an array's length fits in a 32-bit unsigned
integer.) (Props to RobG for pointing out in a comment on my blog post that my previous test wasn't
quite right.)
That's a tiny bit of added overhead per loop iteration on most arrays, but if you have a sparse array, it can be
a more efficient way to loop because it only loops for entries that actually exist. E.g., for the array above, we
loop a total of three times (for keys "0", "10", and "10000" — remember, they're strings), not 10,001 times.
Now, you won't want to write that every time, so you might put this in your toolkit:
function arrayHasOwnIndex(array, prop) {
return array.hasOwnProperty(prop) && /^0$|^[1-9]\d*$/.test(prop) && prop <= 4294967294; // 2^32 - 2
}
And then we'd use it like this:
for (key in a) {
if (arrayHasOwnIndex(a, key)) {
console.log(a[key]);
}
}
Or if you're interested in just a "good enough for most cases" test, you could use this, but while it's close, it's
not quite correct:
for (key in a) {
// "Good enough" for most cases
if (String(parseInt(key, 10)) === key && a.hasOwnProperty(key)) {
console.log(a[key]);
}
}
4. Use for-of (use an iterator implicitly) (ES2015+)
ES2015 adds iterators to JavaScript. The easiest way to use iterators is the new for-of statement. It looks like
this:
var val;
var a = ["a", "b", "c"];
for (val of a) {
console.log(val);
}
Output:
a
b
c
Under the covers, that gets an iterator from the array and loops through it, getting the values from it. This
doesn't have the issue that using for-in has, because it uses an iterator defined by the object (the array), and
arrays define that their iterators iterate through their entries (not their properties). Unlike for-in in ES5, the
order in which the entries are visited is the numeric order of their indexes.
5. Use an iterator explicitly (ES2015+)
Sometimes, you might want to use an iterator explicitly. You can do that, too, although it's a lot clunkier
than for-of. It looks like this:
var a = ["a", "b", "c"];
var it = a.values();
var entry;
while (!(entry = it.next()).done) {
console.log(entry.value);
}
The iterator is a function (specifically, a generator) that returns a new object each time you call next. The
object returned by the iterator has a property, done, telling us whether it's done, and a property value with
the value for that iteration.
The meaning of value varies depending on the iterator; arrays support (at least) three functions that return
iterators:
 values(): This is the one I used above. It returns an iterator where each value is the value for that
iteration.
 keys(): Returns an iterator where each value is the key for that iteration (so for our a above, that would
be "0", then "1", then "2").
 entries(): Returns an iterator where each value is an array in the form [key, value] for that iteration.
(As of this writing, Firefox 29 supports entries and keys but not values.)
For Array-Like Objects
Aside from true arrays, there are also array-like objects that have a length property and properties with
numeric names: NodeList instances, the arguments object, etc. How do we loop through their contents?
Use any of the options above for arrays
At least some, and possibly most or even all, of the array approaches above frequently apply equally well to
array-like objects:
1. Use forEach and related (ES5+)
The various functions on Array.prototype are "intentionally generic" and can usually be used on array-like
objects via Function#call or Function#apply. (See the Caveat for host-provided objects at the end of this
answer, but it's a rare issue.)
Suppose you wanted to use forEach on a Node's childNodes property. You'd do this:
Array.prototype.forEach.call(node.childNodes, function(child) {
// Do something with `child`
});
If you're going to do that a lot, you might want to grab a copy of the function reference into a variable
for reuse, e.g.:
// (This is all presumably in some scoping function)
var forEach = Array.prototype.forEach;

// Then later...
forEach.call(node.childNodes, function(child) {
// Do something with `child`
});
2. Use a simple for loop
Obviously, a simple for loop applies to array-like objects.
3. Use for-in correctly
for-in with the same safeguards as with an array should work with array-like objects as well; the caveat
for host-provided objects on #1 above may apply.
4. Use for-of (use an iterator implicitly) (ES2015+)
for-of will use the iterator provided by the object (if any); we'll have to see how this plays with the
various array-like objects, particularly host-provided ones.
5. Use an iterator explicitly (ES2015+)
See #4, we'll have to see how iterators play out.
Create a true array
Other times, you may want to convert an array-like object into a true array. Doing that is surprisingly easy:
1. Use the slice method of arrays
We can use the slice method of arrays, which like the other methods mentioned above is "intentionally
generic" and so can be used with array-like objects, like this:
var trueArray = Array.prototype.slice.call(arrayLikeObject);
So for instance, if we want to convert a NodeList into a true array, we could do this:
var divs = Array.prototype.slice.call(document.querySelectorAll("div"));
See the Caveat for host-provided objects below. In particular, note that this will fail in IE8 and earlier,
which don't let you use host-provided objects as this like that.
2. Use spread notation (...)
It's also possible to use ES2015's spread notation (MDN currently calls it an operator; it isn't one), with
JavaScript engines that support this feature:
var trueArray = [...iterableObject];
So for instance, if we want to convert a NodeList into a true array, with spread syntax this becomes quite
succinct:
var divs = [...document.querySelectorAll("div")];
3. Use Array.from (spec) | (MDN)
Array.from (ES2015, but shimmable) creates an array from an array-like object, optionally passing the
entries through a mapping function first. So:
var divs = Array.from(document.querySelectorAll("div"));
Or if you wanted to get an array of the tag names of the elements with a given class, you'd use the
mapping function:
// Arrow function (ES2015):
var divs = Array.from(document.querySelectorAll(".some-class"), element => element.tagName);

// Standard function (since `Array.from` can be shimmed):


var divs = Array.from(document.querySelectorAll(".some-class"), function(element) {
return element.tagName;
});
Caveat for host-provided objects
If you use Array.prototype functions with host-provided array-like objects (DOM lists and other things
provided by the browser rather than the JavaScript engine), you need to be sure to test in your target
environments to make sure the host-provided object behaves properly. Most do behave properly (now), but
it's important to test. The reason is that most of the Array.prototype methods you're likely to want to use rely
on the host-provided object giving an honest answer to the abstract [[HasProperty]] operation. As of this
writing, browsers do a very good job of this, but the ES5 spec did allow for the possibility a host-provided
object may not be honest; it's in §8.6.2 (several paragraphs below the big table near the beginning of that
section), where it says:
Host objects may implement these internal methods in any manner unless specified otherwise; for example,
one possibility is that [[Get]] and [[Put]] for a particular host object indeed fetch and store property values
but [[HasProperty]] always generates false.
(I couldn't find the equivalent verbiage in the ES2015 spec, but it's bound to still be the case.) Again, as of
this writing the common host-provided array-like objects in modern browsers (NodeListinstances, for
instance) do handle [[HasProperty]] correctly, but it's important to test.
Loop backwards
I think the reverse for loop deserves a mention here:
for (var i = array.length; i--; ) {
// process array[i]
}
Advantages:
 You do not need to declare a temporary len variable, or compare against array.length on each iteration,
either of which might be a minute optimisation.
 Removing siblings from the DOM in reverse order is usually more efficient. (The browser needs to do
less shifting of elements in its internal arrays.)
 If you modify the array while looping, at or after index i (for example you remove or insert an item
at array[i]), then a forward loop would skip the item that shifted left into position i, or re-process the ith
item that was shifted right. In a traditional for loop, you could update i to point to the next item that
needs processing - 1, but simply reversing the direction of iteration is often a simpler and more elegant
solution.
 Similarly, when modifying or removing nested DOM elements, processing in reverse can circumvent
errors. For example, consider modifying the innerHTML of a parent node before handling its children. By
the time the child node is reached it will be detached from the DOM, having been replaced by a newly
created child when the parent's innerHTML was written.
 It is shorter to type, and read, than some of the other options available. Although it loses
to forEach() and to ES6's for ... of.
Disadvantages:
 It processes the items in reverse order. If you were building a new array from the results, or printing
things on screen, naturally the output will be reversed with respect to the original order.
 Repeatedly inserting siblings into the DOM as a first child in order to retain their order is less efficient.
(The browser would keep having to shift things right.) To create DOM nodes efficiently and in order, just
loop forwards and append as normal (and also use a "document fragment").
 The reverse loop is confusing to junior developers. (You may consider that an advantage, depending on
your outlook.)

Should I always use it?


Some developers use the reverse for loop by default, unless there is a good reason to loop forwards.
Although the performance gains are usually insignificant, it sort of screams:
"Just do this to every item in the list, I don't care about the order!"
However in practice that is not actually a reliable indication of intent, since it is indistinguishable from those
occasions when you do care about the order, and really do need to loop in reverse. So in fact another
construct would be needed to accurately express the "don't care" intent, something currently unavailable in
most languages, including ECMAScript, but which could be called, for example, forEachUnordered().
If order doesn't matter, and efficiency is a concern (in the innermost loop of a game or animation engine),
then it may be acceptable to use the reverse for loop as your go-to pattern. Just remember that seeing a
reverse for loop in existing code does not necessarily mean that the order irrelevant!
It is better to use forEach()
In general for higher level code where clarity and safety are greater concerns, I would recommend
using Array::forEach as your default pattern:
 It is clear to read.
 It indicates that i is not going to be shifted within the block (which is always a possible surprise hiding in
long for and while loops.)
 It gives you a free scope for closures.
 It reduces leakage of local variables and accidental collision with (and mutation of) outer variables.
Then when you do see the reverse for loop in your code, that is a hint that it is reversed for a good reason
(perhaps one of the reasons described above). And seeing a traditional forward for loop may indicate that
shifting can take place.
(If the discussion of intent makes no sense to you, then you and your code may benefit from watching
Crockford's lecture on Programming Style & Your Brain.)

How does it work?


for (var i = 0; i < array.length; i++) { ... } // Forwards

for (var i = array.length; i--; ) { ... } // Reverse


You will notice that i-- is the middle clause (where we usually see a comparison) and the last clause is empty
(where we usually see i++). That means that i-- is also used as the condition for continuation. Crucially, it is
executed and checked before each iteration.
 How can it start at array.length without exploding?
Because i-- runs before each iteration, on the first iteration we will actually be accessing the item
at array.length - 1 which avoids any issues with Array-out-of-bounds undefined items.
 Why doesn't it stop iterating before index 0?
The loop will stop iterating when the condition i-- evaluates to a falsey value (when it yields 0).
The trick is that unlike --i, the trailing i-- operator decrements i but yields the value before the
decrement. Your console can demonstrate this:
> var i = 5; [i, i--, i];
[5, 5, 4]
So on the final iteration, i was previously 1 and the i-- expression changes it to 0 but actually
yields 1 (truthy), and so the condition passes. On the next iteration i-- changes i to -1 but
yields 0 (falsey), causing execution to immediately drop out of the bottom of the loop.
In the traditional forwards for loop, i++ and ++i are interchangeable (as Douglas Crockford points out).
However in the reverse for loop, because our decrement is also our condition expression, we must stick
with i-- if we want to process the item at index 0.

Trivia
Some people like to draw a little arrow in the reverse for loop, and end with a wink:
for (var i = array.length; i --> 0 ;) {
How about this for a reverse loop? var i= array.length; while(i--) { ... – Kabb5 May 15 '15 at 16:32

Vous aimerez peut-être aussi