JavaScript's for ... of
operator loops over iterable objects. This type of loop exists in many programming languages.
// JavaScript
for (const user of users) {
/* do something */
}
// Java
for (User user : users) {
/* do something */
}
// PHP
foreach ($users as $user) {
/* do something */
}
# Python
for user in users:
# do something
In JavaScript we also have the for ... in
operator that loops over an object's enumerable properties, which means its keys.
const john = {name: "John Lennon", age: 40, isAlive: false};
const beatles = ["John", "Paul", "George", "Ringo"];
for (const key in john) {
console.log(key); // "name", "age", "isAlive"
}
for (const key in beatles) {
console.log(key); // 0, 1, 2, 3
}
JavaScript arrays are basically special objects with indices as keys. We can imagine that a very simple implementation of a JavaScript array will look like this:
const arrayObject = {
0: "John",
1: "Paul",
2: "George",
3: "Ringo",
length: 4
}
that's why the for ... in
operator will loop over its indices.
Using a for ... of
operator on an array will loop over its entries.
for (const beatle of beatles) {
console.log(beatle); // "John", "Paul", "George", "Ringo"
}
But using the same operator on a plain object will throw an error.
for (const value of john) {
console.log(value); // Uncaught TypeError: john is not iterable
}
Our simple array implementation, will work using a regular for
loop, but will throw the same error when using the for ... of
operator.
for (let i = 0; i < arrayObject.length; i++) {
console.log(arrayObject[i]); // "John", "Paul", "George", "Ringo"
}
for (const beatle of arrayObject) {
console.log(beatle);
// Uncaught TypeError: arrayObject is not iterable
}
JavaScript arrays (and for that matter also Set
, Map
, NodeList
, etc.) are basically objects, So why on earth does a for ... of
work on an array and not on plain objects?
The reason is a property called Symbol.iterator
, which accepts a Generator function that allows for any object to be iterated with a for ... of
loop and accept the spread syntax.
Array.prototype
and other iterable interfaces, have that property defined,
Array.prototype[Symbol.iterator]; // ƒ values() { [native code] }
Set.prototype[Symbol.iterator]; // ƒ values() { [native code] }
Map.prototype[Symbol.iterator]; // ƒ entries() { [native code] }
NodeList.prototype[Symbol.iterator]; // ƒ values() { [native code] }
while other objects don't.
Object.prototype[Symbol.iterator]; // undefined
Date.prototype[Symbol.iterator]; // undefined
So, if for some reason we'd really like to use a for ... of
loop on an object, we could define it a Symbol.iterator
method. (Note that it's currently impossible to use arrow functions for a generator function).
john[Symbol.iterator] = function* () {
for (const key in john) {
yield john[key];
}
}
Now we can use a for ... of
loop on that object, and even use the spread syntax.
for (const value of john) {
console.log(value); // "John Lennon", 40, false
}
const values = [...john]; // ["John Lennon", 40, false]
But, setting a Symbol.iterator
property to every object instance is overtly complicated and unnecessary. Instead, we can add it to Object.prototype
.
Object.prototype[Symbol.iterator] = function* () {
for (const key in this) {
yield this[key];
}
}
And while this works like a charm, adding methods to built-in prototypes is discouraged. Think of the confusion it can cause with people unfamiliar with your code, and of future errors it could cause, read more about this here.
To iterate over the values of an object, we'd better use the Object.prototype
static method Object.values
, and then we could easily use a for ... of
loop, or any Array.prototype
methods.
for (const value of Object.values(john)) {
console.log(value); // "John Lennon", 40, false
}
So when should we define a Symbol.iterator
method? For instance when we implement our own data structure, and we'd like it to be iterable. Check out my linked list implementation on GitHub
class LinkedList {
/* Class implementation */
[Symbol.iterator]() {
return function* () {
let node = this.head;
while (node !== null) {
yield node.value;
node = node.next;
}
}
}
}
Also, we can create a class that handle array-like objects, that will add the ability to use for ... of
loops. That way we could use the object as is without using Object.prototype
static methods and without polluting Object.prototype
.
class IterableObject {
constructor(obj) {
Object.assign(this, obj);
}
* [Symbol.iterator]() {
for (const key in this) {
yield this[key];
}
}
}
const beatles = new IterableObject({
john: {name: "John Lennon", age: 40, isAlive: false},
paul: {name: "Paul McCartney", age: 79, isAlive: undefined},
george: {name: "George Harrison", age: 58, isAlive: false},
ringo: {name: "Ringo Starr", age: 81, isAlive: true}
});
Now we can use a for ... of
loop and run queries and tests on it using the spread syntax.
for (const beatle of beatles) {
console.log(beatle.name);
// "John Lennon", "Paul McCartney", "George Harrison", "Ringo Starr"
}
[...beatles].filter((beatle) => beatle.isAlive).length; // 1
One thing to keep in mind about for ... of
operator and Symbol.iterator
, is that according to caniuse.com, while widely supported in 95% of browsers, they are not supported in (you guessed it) Internet Explorer. If you care about IE (and I think you shouldn't) you should avoid using this operator, as there's no polyfill - you can't polyfill syntax...
Top comments (1)
Other options include
for-of
loops to use the iterator protocol's semantic sugar orObject.keys/values/entries
in combination withArray.prototype.forEach/map/reduce/etc
, though the latter will create arrays in memory to iterate over, which can result in degraded performance.