for ... of operator and Symbol.iterator
Tags: javascript, tutorial, webdev
Published: 2022-01-16 09:34:24

JavaScript's `for ... of` operator loops over iterable objects. This type of loop exists in many programming languages.
```javascript
// JavaScript
for (const user of users) {
  /* do something */
}
```
```java
// Java
for (User user : users) {
  /* do something */
}
```
```php
// PHP
foreach ($users as $user) {
  /* do something */
}
```
```python
# 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.
```javascript
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:
```javascript
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.
```javascript
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.
```javascript
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.
```javascript
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](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Generator) that allows for any object to be iterated with a `for ... of` loop and accept the [spread syntax](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax).
`Array.prototype` and other iterable interfaces, have that property defined,
```javascript
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.
```javascript
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).
```javascript
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.
```javascript
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`.
```javascript
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](https://flaviocopes.com/javascript-why-not-modify-object-prototype/). 
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.
```javascript
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](https://github.com/dagropp/js-dast/blob/master/src/linked-list/LinkedList.js)
```javascript
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`.
```javascript
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.
```javascript
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](https://caniuse.com/mdn-javascript_statements_for_of), 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...