[2015-02-26] New version of this blog post: “
Iterables and iterators in ECMAScript 6”
ECMAScript.next’s for-of loop will provide a new and quirk-free way of iterating over all kinds of data. This blog post explains how it works and how you can try out a prototype implementation in Firefox 13 and later.
Iterating over data
The
for-of loop is convenient for iterating over arrays and objects.
Iterating over arrays
The standard
for-in loop has several quirks: It iterates over all enumerable properties of an object, including inherited ones. That means it is ill-suited for iterating over arrays, because it does not iterate over array elements. You cannot even use it to iterate over array indices, because non-index property names are always included. As an example, take the following array.
let arr = [ "blue", "green" ];
arr.notAnIndex = 123;
Array.prototype.protoProp = 456;
If you iterate over it with
for-in, you get:
> for(k in arr) console.log(k)
0
1
notAnIndex
protoProp
Hence, for ECMAScript 5, it is better to use
Array.prototype.forEach [1]. ECMAScript.next will give us the
for-of loop that also works as expected:
> for(k of arr) console.log(k)
blue
green
Iterating over objects
Objects are not iterable by default. The rationale for this decision is that people will mainly iterate over collections (instances of
Array, ECMAScript.next’s
Map, etc.). If you are currently iterating over an object, you are usually (ab)using it as a collection. But collections are also objects, so the different operations “iterate over a collection” and “iterate over an object” should not clash. Which is why you have to apply a function to an object if you want to iterate over its properties.
ECMAScript next will have a module
@iter with appropriate tool functions. The neat thing is that these functions don’t return arrays, they return an iterator (see below).
That means that they compute elements on demand, whenever an iteration mechanism requests the next one.
Current functions that inspect objects, such as
Object.keys() always compute the complete result right away, usually as an array.
Consult the
iterator proposal for details on
@iter. The functions for iterating over objects are:
- keys(obj): iterates over the names of all own (non-inherited) properties of obj.
- values(obj): iterates over the values of all own properties of obj.
- items(obj): iterates over all own properties, as [name, value] pairs.
- allKeys(obj): iterates over the names of all properties of obj (including inherited ones).
- allValues(obj): iterates over the values of all properties of obj.
- allItems(obj): iterates over all properties, as [name, value] pairs.
Thus, the following code prints out all own properties of an object:
import items from "@iter";
let obj = { first: "Jane", last: "Doe" };
for (let [k,v] of items(obj)) {
console.log(k+" = "+v);
}
Output:
first = Jane
last = Doe
Firefox does not yet have the module
@iter (or modules in general), but you can use the function
Iterator() as a work-around:
let obj = { first: "Jane", last: "Doe" };
for (let [k,v] of Iterator(obj)) {
console.log(k+" = "+v);
}
Iterators
ECMCAScript.next
iterators allow one to implement a custom iteration strategy for a data structure. To achieve the same under ECMAScript 5, one usually creates a new array and iterates over it via
Array.prototype.forEach(). For example,
Object.keys() can be seen as a custom iteration strategy for objects: It iterates over the enumerable own property names. However, each time it is invoked, it creates an array with the names. An iterator makes this simpler. The following is an example of an object that comes with custom iteration.
let obj = {
data: [ "hello", "world" ],
// Custom iteration:
__iterator__: function () {
let index = 0;
let that = this;
// Return iterator object
return {
next: function () {
if (index < that.data.length) {
return that.data[index++];
} else {
throw StopIteration;
}
}
}
}
}
The special method
__iterator__ returns an iterator object. Such an object has a method
next() that either returns the next element in the current iteration sequence or throws
StopIteration if there are no more elements. Firefox 13’s
for-of does not yet support the
__iterator__ method. Once it does, one will be able to iterate over
obj like this:
> for (x of obj) console.log(x);
hello
world
Note that the final version of
for-of will probably use a special mechanism to specify the name of the iterator method. That is, it won’t have the name
__iterator__.
Generators
Among other things,
generators help with implementing iterators. The above iterator could be implemented as follows via a generator:
let obj = {
data: [ "hello", "world" ],
// function* means: generator
__iterator__: function* generator() {
for(let index=0; index < this.data.length; index++) {
yield this.data[index];
}
}
}
Let’s use a generator to iterate over the [property name, property value] entries of an object.
function* items(obj) { // Firefox 13: function items...
for(let key in obj) {
if (Object.prototype.hasOwnProperty.call(obj, key)) {
yield [ key, obj[key] ];
}
}
}
The above code works in Firefox 13, but you have to omit the
* after
function. You use
items() as follows:
> let obj = { first: "Jane", last: "Doe" };
> for (x of items(obj)) console.log(x);
["first", "Jane"]
["last", "Doe"]
You can also destructure the array if you are interested in keys and values:
> for ([k,v] of items(obj)) console.log(k+" = "+v);
first = Jane
last = Doe
Related reading
- Iterating over arrays and objects in JavaScript
- Bug 699565 – Implement Harmony for-of loops
- for...of - MDN