Iteration
The for..in
loop iterates over the list of enumerable properties on an object (including its [[Prototype]]
chain). But what if you instead want to iterate over the values?
With numerically-indexed arrays, iterating over the values is typically done with a standard for
loop, like:
var myArray = [1, 2, 3];
for (var i = 0; i < myArray.length; i++) {
console.log( myArray[i] );
}
// 1 2 3
This isn’t iterating over the values, though, but iterating over the indices, where you then use the index to reference the value, as myArray[i]
.
ES5 also added several iteration helpers for arrays, including forEach(..)
, every(..)
, and some(..)
. Each of these helpers accepts a function callback to apply to each element in the array, differing only in how they respectively respond to a return value from the callback.
forEach(..)
will iterate over all values in the array, and ignores any callback return values. every(..)
keeps going until the end or the callback returns a false
(or “falsy”) value, whereas some(..)
keeps going until the end or the callback returns a true
(or “truthy”) value.
These special return values inside every(..)
and some(..)
act somewhat like a break
statement inside a normal for
loop, in that they stop the iteration early before it reaches the end.
If you iterate on an object with a for..in
loop, you’re also only getting at the values indirectly, because it’s actually iterating only over the enumerable properties of the object, leaving you to access the properties manually to get the values.
Note: As contrasted with iterating over an array’s indices in a numerically ordered way (for
loop or other iterators), the order of iteration over an object’s properties is not guaranteed and may vary between different JS engines. Do not rely on any observed ordering for anything that requires consistency among environments, as any observed agreement is unreliable.
But what if you want to iterate over the values directly instead of the array indices (or object properties)? Helpfully, ES6 adds a for..of
loop syntax for iterating over arrays (and objects, if the object defines its own custom iterator):
var myArray = [ 1, 2, 3 ];
for (var v of myArray) {
console.log( v );
}
// 1
// 2
// 3
The for..of
loop asks for an iterator object (from a default internal function known as @@iterator
in spec-speak) of the thing to be iterated, and the loop then iterates over the successive return values from calling that iterator object’s next()
method, once for each loop iteration.
Arrays have a built-in @@iterator
, so for..of
works easily on them, as shown. But let’s manually iterate the array, using the built-in @@iterator
, to see how it works:
var myArray = [ 1, 2, 3 ];
var it = myArray[Symbol.iterator]();
it.next(); // { value:1, done:false }
it.next(); // { value:2, done:false }
it.next(); // { value:3, done:false }
it.next(); // { done:true }
Note: We get at the @@iterator
internal property of an object using an ES6 Symbol
: Symbol.iterator
. We briefly mentioned Symbol
semantics earlier in the chapter (see “Computed Property Names”), so the same reasoning applies here. You’ll always want to reference such special properties by Symbol
name reference instead of by the special value it may hold. Also, despite the name’s implications, @@iterator
is not the iterator object itself, but a function that returns the iterator object — a subtle but important detail!
As the above snippet reveals, the return value from an iterator’s next()
call is an object of the form { value: .. , done: .. }
, where value
is the current iteration value, and done
is a boolean
that indicates if there’s more to iterate.
Notice the value 3
was returned with a done:false
, which seems strange at first glance. You have to call the next()
a fourth time (which the for..of
loop in the previous snippet automatically does) to get done:true
and know you’re truly done iterating. The reason for this quirk is beyond the scope of what we’ll discuss here, but it comes from the semantics of ES6 generator functions.
While arrays do automatically iterate in for..of
loops, regular objects do not have a built-in @@iterator
. The reasons for this intentional omission are more complex than we will examine here, but in general it was better to not include some implementation that could prove troublesome for future types of objects.
It is possible to define your own default @@iterator
for any object that you care to iterate over. For example:
var myObject = {
a: 2,
b: 3
};
Object.defineProperty( myObject, Symbol.iterator, {
enumerable: false,
writable: false,
configurable: true,
value: function() {
var o = this;
var idx = 0;
var ks = Object.keys( o );
return {
next: function() {
return {
value: o[ks[idx++]],
done: (idx > ks.length)
};
}
};
}
} );
// iterate `myObject` manually
var it = myObject[Symbol.iterator]();
it.next(); // { value:2, done:false }
it.next(); // { value:3, done:false }
it.next(); // { value:undefined, done:true }
// iterate `myObject` with `for..of`
for (var v of myObject) {
console.log( v );
}
// 2
// 3
Note: We used Object.defineProperty(..)
to define our custom @@iterator
(mostly so we could make it non-enumerable), but using the Symbol
as a computed property name (covered earlier in this chapter), we could have declared it directly, like var myObject = { a:2, b:3, [Symbol.iterator]: function(){ /* .. */ } }
.
Each time the for..of
loop calls next()
on myObject
‘s iterator object, the internal pointer will advance and return back the next value from the object’s properties list (see a previous note about iteration ordering on object properties/values).
The iteration we just demonstrated is a simple value-by-value iteration, but you can of course define arbitrarily complex iterations for your custom data structures, as you see fit. Custom iterators combined with ES6’s for..of
loop are a powerful new syntactic tool for manipulating user-defined objects.
For example, a list of Pixel
objects (with x
and y
coordinate values) could decide to order its iteration based on the linear distance from the (0,0)
origin, or filter out points that are “too far away”, etc. As long as your iterator returns the expected { value: .. }
return values from next()
calls, and a { done: true }
after the iteration is complete, ES6’s for..of
can iterate over it.
In fact, you can even generate “infinite” iterators which never “finish” and always return a new value (such as a random number, an incremented value, a unique identifier, etc), though you probably will not use such iterators with an unbounded for..of
loop, as it would never end and would hang your program.
var randoms = {
[Symbol.iterator]: function() {
return {
next: function() {
return { value: Math.random() };
}
};
}
};
var randoms_pool = [];
for (var n of randoms) {
randoms_pool.push( n );
// don't proceed unbounded!
if (randoms_pool.length === 100) break;
}
This iterator will generate random numbers “forever”, so we’re careful to only pull out 100 values so our program doesn’t hang.