In JavaScript, an object is iterable if it implements a [Symbol.iterator]() method that returns an iterator object. I have written about these iteration protocols before, but the basic idea is that iterables can be consumed by language features such as spread syntax (...) and the for...of statement. Some built-in types, such as Array, are built-in iterables with a default iteration behaviour. Others, such as Object, are not. In this post, we’ll learn how to make Object instances iterable.

[Symbol.iterator]()

For the sake of this post, let’s work with an object that represents a car. It has .make, .model, and .year properties that we want to iterate over. To make the object iterable, we need to implement a [Symbol.iterator]() method that returns an iterator object.

const car = {
make: "Dodge",
model: "Charger",
year: 1973,
[Symbol.iterator]() {
// We need to return an iterator object...
},
};

Language features such as spread syntax (...) and the for...of statement will call the [Symbol.iterator]() method automatically. They will use the returned iterator object to produce the sequence of values to iterate over.

Object.entries()

Before we make the car object iterable, we need to know about the Object.entries() static method. It returns a two-dimensional array of an object’s key-value pairs and is a common way to iterate over an Object instance. Because arrays are built-in iterables, we can iterate over the method’s return value using a for...of statement. Destructuring assignment makes it easy to separate the keys from the values.

const car = {
make: "Dodge",
model: "Charger",
year: 1973,
};

const entries = Object.entries(car);
console.log(entries);

// [["make", "Dodge"], ["model", "Charger"], ["year", 1973]]

for (const [key, val] of entries) {
console.log(`${key}: ${val}`);
}

// "make: Dodge"
// "model: Charger"
// "year: 1973"

What if we could make this the iteration behaviour of the car object? Then we could iterate directly over the object without an explicit call to the Object.entries() method. To do this, we would need to:

  1. Get the iterator object for the array returned by the Object.entries() method.
  2. Return the iterator object from the car object’s [Symbol.iterator]() method.

Array.prototype.values()

Although the array returned by the Object.entries() method is iterable, we cannot return it directly from the car object’s [Symbol.iterator]() method, because it is not an iterator. Remember, an object is iterable if it implements a [Symbol.iterator]() method that returns an iterator. The returned iterator object is used to produce the sequence of values to iterate over. This does not mean the object that implements the [Symbol.iterator]() method is an iterator.

// Because the `car` object's `[Symbol.iterator]()` method does not return an
// iterator object, the `car` object is a non-well-formed iterable.
const car = {
make: "Dodge",
model: "Charger",
year: 1973,
[Symbol.iterator]() {
return Object.entries(this);
},
};

for (const [key, val] of car) {
console.log(`${key}: ${val}`);
}

// Uncaught TypeError: car[Symbol.iterator]().next is not a function

Fortunately, we can invoke the Array.prototype[Symbol.iterator]() method manually. It returns an iterator object that we can return from the car object’s [Symbol.iterator]() method.

const car = {
make: "Dodge",
model: "Charger",
year: 1973,
[Symbol.iterator]() {
return Object.entries(this)[Symbol.iterator]();
},
};

However, it’s easier to use the Array.prototype.values() method. This is actually the default implementation of the Array.prototype[Symbol.iterator]() method. They refer to the same function in memory, meaning Array.prototype.values === Array.prototype[Symbol.iterator].

const car = {
make: "Dodge",
model: "Charger",
year: 1973,
[Symbol.iterator]() {
return Object.entries(this).values();
},
};

Either way, the car object is now iterable! For example, we can iterate over it with the for...of statement.

for (const [key, val] of car) {
console.log(`${key}: ${val}`);
}

// "make: Dodge"
// "model: Charger"
// "year: 1973"

An IterableObject class

If we use this technique often, it can be cumbersome to keep adding the [Symbol.iterator]() method to each object as an own property. Here’s a simple class we can use instead. The constructor uses the Object.assign() static method to assign the properties of obj to the object under construction. Each instance of the class inherits the [Symbol.iterator]() method from IterableObject.prototype. This is more memory-efficient than adding the method to each object as an own property.

class IterableObject {
constructor(obj) {
Object.assign(this, obj);
}

[Symbol.iterator]() {
return Object.entries(this).values();
}
}

Now we can use the class to create as many iterable objects as we want. Here’s the car object again, along with a separate pizza object.

const car = new IterableObject({
make: "Dodge",
model: "Charger",
year: 1973,
});

for (const [key, val] of car) {
console.log(`${key}: ${val}`);
}

// "make: Dodge"
// "model: Charger"
// "year: 1973"

const pizza = new IterableObject({
name: "Farmhouse",
inches: 12,
toppings: ["Ham", "Mushroom", "Onion"],
});

for (const [key, val] of pizza) {
console.log(`${key}: ${val}`);
}

// "name: Farmhouse"
// "inches: 12"
// "toppings: Ham,Mushroom,Onion"

The benefit of this approach is that we’re effectively creating an iterable copy of the object we pass to the constructor. Avoiding mutations is usually a good idea. But what if we want to mutate the object?

Before moving on, it’s important to beware of object references. Although we are creating an iterable copy of an object, properties assigned to non-primitive values (i.e. objects) are still just references to the same objects in memory. They are not copies.

const pizza = {
name: "Farmhouse",
inches: 12,
toppings: ["Ham", "Mushroom", "Onion"],
};

const iterablePizza = new IterableObject(pizza);

console.log(pizza === iterablePizza); // false, different objects
console.log(pizza.toppings === iterablePizza.toppings); // true, same object

A makeIterable() function

Instead of making an iterable copy of an object, we can simply assign a new property to the original object.

const car = {
make: "Dodge",
model: "Charger",
year: 1973,
};

// Now the object is iterable
car[Symbol.iterator] = function () {
return Object.entries(this).values();
};

It might be nice to declare a function that does this for us, and only assigns the property if it isn’t already in the object’s prototype chain (if we want to shadow/override the property, we can skip this check).

function makeIterable(obj) {
if (!(Symbol.iterator in obj)) {
obj[Symbol.iterator] = function () {
return Object.entries(this).values();
};
}

return obj;
}

The nice thing about this is that we can either make the object iterable as soon as we initialize it or do it later.

// Make an object iterable as soon as we initialize it
const car = makeIterable({
make: "Dodge",
model: "Charger",
year: 1973,
});

const pizza = {
name: "Farmhouse",
inches: 12,
toppings: ["Ham", "Mushroom", "Onion"],
};

// Make an object iterable later
makeIterable(pizza);

To maintain the memory efficiency of a single function—rather than each object having its own copy—we could declare a function called getIterator(). Then the makeIterable() function could reuse the getIterator() function as the value of the passed-in object’s [Symbol.iterator] property.

How does this work? Contrary to popular belief, the value of a function’s this keyword does not depend on where the function is declared. Rather, it is dynamic based on the context in which the function is invoked. When the getIterator() function is invoked as a property of an object, that object is the context, and it becomes the value of the function’s this keyword. For more on this, check out You Don’t Know JS: this & Object Prototypes by Kyle Simpson.

function getIterator() {
return Object.entries(this).values();
}

function makeIterable(obj) {
if (!(Symbol.iterator in obj)) {
obj[Symbol.iterator] = getIterator;
}

return obj;
}

If we wanted to keep the getIterator() function out of the global scope, we could use a closure. Using a closure in this way is sometimes known as the [revealing] module pattern, but I don’t know if it’s still called that when we’re returning a function instead of an object literal. It’s essentially the same thing as an immediately-invoked function expression (IIFE), except we’re assigning the IIFE’s return value to a constant. In this case, the IIFE’s return value is an anonymous function. The anonymous function has access to the getIterator() function because both functions are in the lexical scope of the IIFE, but only the anonymous function is returned from the IIFE.

const makeIterable = (function () {
function getIterator() {
return Object.entries(this).values();
}

return function (obj) {
if (!(Symbol.iterator in obj)) {
obj[Symbol.iterator] = getIterator;
}

return obj;
};
})();

Summary

  • An object is iterable if it implements a [Symbol.iterator]() method that returns an iterator object.
  • Iterables can be consumed by language features such as spread syntax (...) and the for...of statement.
  • The Object.entries() method returns a two-dimensional array of an object’s key-value pairs.
  • The Array.prototype.values() method returns an iterator object for an array.
  • We can make an object iterable by returning Object.entries(this).values() from its [Symbol.iterator]() method.