If you ever worked with ES6, you probably learned how to use the “for … of” loop and what’s the difference between this loop and the “for … in” one, but what you might not know is how this loop works (my apologize if you do :-)). However, to ensure that everything is crystal clear, let’s start from the basics.
The “for … in” case
I hope I don’t teach you anything when I say that it is possible to loop through an array using the “for … in” loop:
Problem here is that indexes of an array are nothing more than enumerable properties of the object. Meaning that in the “array” object, you have a property “0” that returns the first item of the array, a property “1” that returns the second one, etc… So if you add a property by simply extending the “Array” prototype like this:
You’ll end up with an unwanted behavior:
This comes from the fact that now, the “Array” prototype contains an enumerable “foo” property, so the “for … in” loop picks it up and returns it. You could easily avoid this issue by defining the “foo” function as non-enumerable:
This time, as the “foo” function is declared as non-enumerable, the “for … in” loop will no longer return it. However, how can you ensure that no one, in a library that you’re using, didn’t add an enumerable property to the “Array” prototype? To be guarded against that, you’d have to use the “hasOwnProperty” function like so:
This works because you ensure that the property “index” must be present in the current object and not inherited from the parent prototype. As said before, the “array” object has five properties that belong to its prototype: 0, 1, 2, 3, 4. It also has a inherited property called “foo” inherited from the “Array” prototype, meaning that “hasOwnProperty” for this property returns “false” as the property belong to the “Array” prototype and not the object itself.
This is (probably) because of all these caveats that the “for … of” loop as been introduced in ES6.
The “for … loop” to the rescue
All the above issue can be solved using the “for … loop”:
The internal mechanism of the “for … loop” (that we’ll see a bit later) allows it to loop over the values contained in the array instead of the array properties. Meaning that even though we added an enumerable property (“foo”) to the “Array” prototype, the “for … loop” won’t return it. But how does that work?
Here come iterator
To illustrate the concept, we’re going to implement a “Grid” class that can be constructed by passing a list of drivers. Then, we’re going to make this grid iterable to be able to loop over it to display the drivers. I agree with you, it’s kind of stupid and could be done differently, but it’ll help you understand iterators.
Start by implementing the two classes:
Then, create new objects:
The goal here, is to be able to do something like this:
However, doing it right now would throw the following error:
Chrome is complaining about the fact that the object “grid” is not iterable. Fortunately, the title of this blog post if “Make your ES6 classes iterable” so we are going to fix this.
To be able to loop through an object, the “for … loop” looks for a specific property in the object called… Well, actually, the name of the property is not a literal but a symbol. Symbols are a new primitive type introduced in ES6 (on which I might post an article) that basically, ensure a unique identifier that can be used as an object property name. This is a reductive explanation of what symbols are but it should be enough to understand this blog post.
Basically, your class has to implement a function identified by the symbol “Symbol.iterator”, so let’s add such a function in our “Grid” class:
Note that even though your class is not yet iterable, the error in the console is promising as we can see our “console.log” in it:
Now the error is actually about the fact that our function does not return an object. Indeed, an object has to be returned for the “for … loop” to be able to extract the information it needs. Basically, this object needs to contain a “next” function that is called by the loop and return an object with the following shape:
- “value” is the current value given to the loop.
- “done” is a boolean value determining whether the loop has reached the end or not.
You probably understood the idea behind this. The first execution of the “next” function must return the first driver, the second execution must return the second driver, etc… and when all the drivers have been returned, an object with the property “done” set to “true” must be returned to stop the loop.
Let’s implement this:
As the “for … loop” will call several time the “next” function, we need to use a variable to store the index of the current item used by the loop. Moreover, as this loop can be called multiple time on the same object, we need to set this index to 0 at the beginning of the “[System.iterator]” function to ensure that the loop starts with the first object.
Then, we return an object with a “next” function. This function starts by checking whether the “_index” value is equal to the length of the array or not. If it’s the case, the function returns an object with the property “done” set to “true” to ensure that the “for … loop” stops. Otherwise, the object returned has its property “done” set to “false” and its property “value” set to the driver located at the current index so the loop can use this value (“for value of …”).
You can now run your code to see that it works perfectly:
Is it me or…
This whole implementation is a bit stupid as the “Grid” class is just wrapping an array. Indeed, basically, looping through a “Grid” object is just the same as looping through its “_drivers” property. Therefore, our entire function could have been simplified like this: