Learning ES6: Loops & Iterators

This week, we learned about Iterable objects, three different kinds of loops in Javascript, and a new for of loop that ES6 gives us the best of all three worlds!

Loops

Loops are fundamental to programming and logic. Let's take a look at what kinds of loops we had in Javascript, their pros and cons, and the new for of loop in ES6 that combines the best features from all three.

Iterables, Iterators, and Generators, oh my!

Before we start digging into the loop functions, let's quickly go over Iterables, Iterators, and Generators!

What is an iterable? Iterables are a type of object which can be iterated over - that is, we can access that object in a sequence. Some examples of iterable objects are Arrays, Maps, and Sets. In Javascript, iterable and iterator protocols were added in ECMAScript 2015. Iterable is a protocol that allows us to write custom iteration behavior in any POJO, for example, defining what value of that object should be iterated over. In order for an object to be iterable, it must define an @@iterator method, a reference to its iterator.

An iterator is what we use to iterate over an iterable. In Javascript, the iterator protocol can be used define or customize an object's iteration behavior. It must define a next function, which determines how that iterator gets the next value in the sequence.

A generator can be used to create an iterator. Custom iterators are very useful, but can be difficult to program ourselves. Generators allow us to write our own iterative algorithms that can be used like custom iterators. A generator function is a factory for Generator objects, which are both Iterators and Iterable. We can define generator functions by using the function* syntax when defining it. Generators are really handy. There's lots of use cases, but we won't get too far in the details with this blog post.

If you want to learn more, check out the documentation from Mozilla: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Iterators_and_Generators https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols

The classics - for, forEach, for in

Now that we have an understanding of iterables and iterators, let's jump into the kinds of loops we had before ES6. For these examples, we'll look at an array of values: const fruits = ['Apple', 'Orange', 'Cherry', 'Pineapple']

The first loop is the ordinary for loop. This was the loop that I used for everything when I learned programming in Java! Here's an example:

for (let i=0; i < fruits.length; i++) {
    console.log(fruits[i]);
}

This prints out each of the fruits and we'll see an output with each of the fruits in the array logged out to the console. Although some of us may be very familiar with this kind of for loop, it's not very readable. Defining a variable i inside of the parenthesis and then using it in the loop can be confusing. It takes longer to read this code and understand what's going on. You only get an index value in this case, and you have to refer to the original collection, fruits to access the value within.

We also have the forEach loop that is an array method, meaning it can't be used on other objects. Below is an example of the forEach loop:

fruits.forEach(fruit => {
    console.log(fruit);
});

This will also print out each of the fruits in the array in the console. This is definitely more readable than the first loop, and you don't have to access the outside array. You can directly reference the value by the parameter you pass to it. It's also easier to avoid off-by-one errors using this loop because you aren't defining the indices. However, this loop isn't as flexible. We can't use break or continue in the loop without causing a code breaking error. If we wanted to have an early exit or a skip-over ability in the loop, we can't use the forEach method. The forEach loop is also a bit slower than a regular for loop, but it might be a trade off worth doing for better readability and maintainability. It's important to avoid premature optimization at the cost of readability.

Thirdly, we have the for in loop, which looks like this:

for (const fruit in fruits) {
    console.log(fruit);
}

Though we might expect this to also print out each of the fruits in the array in the console, this actually prints out the indices: 0, 1, 2, 3! In order to actually get the values, we would need to write it like this:

for (const index in fruits) {
    console.log(fruits[index]);
}

Now we get the output we expect. This is a pretty readable loop, but we still have to reach outside into the original collection and give it the index to get the values. That's not the biggest problem with this loop, though. Something interesting happens if you update the Array prototype. Let's say we added our own function to the Array prototype, whether a polyfill or something custom just because. Maybe we want to add a function that returns a random element from the Array. Some pseudocode would be:

Array.prototype.getRandom = function () {
    const num = getRandomValue(0, this.length);
    return this[num];
}

Let's take a look at the for in output after modifying the prototype. Now, if we use our second example where we log out each value of the fruits by using the index, we will see an output like this:

Apple
Orange
Cherry
Pineapple
function () {
    const num = getRandomValue(0, this.length);
    return this[num];
}

Woah! Our new function gets logged out now too! This is some strange behavior that happens with the for in loop. And although you might not personally be adding anything to the Array prototype, we often work with libraries that do. If we use the for in loop to access values, we'll get an output of every single added function. Similarly, since adding a function to the prototype is like adding a property, if you add any plain old property to the array, like this: fruits.location = 'produce section' you will also see 'produce section' printed out before the added functions.

New in ES6 - for of

In ES6, we have a new type of loop called the for of loop which combines the best things from the ones we looked at above. Here's an example:

for (const fruit of fruits) {
    console.log(fruit);
}

Even though we've added properties to the array, we only get the values outputted. We can also use break and continue inside the loop and it works as we expect it to. break takes you out of the loop entirely whereas continue will skip to the next iteration.

This is where the for of loop gets interesting, and ties back to the Iterators and Iterables! If we call .entries() on our array, we will get an ArrayIterator returned. If we then call .next() on that iterator, we'll get returned an object which holds a property called value. The value property is an array where the first index is the actual value from the array (like 'Apple') and the second index is that value's index in the array (for 'Apple' we would get 0). Go ahead, try it out in your console! We can manually go through (or iterate through) the array by calling .next() repeatedly until we see that the value property returns undefined.

How is this related to our for of loop? We don't need to manually iterate through the array since we have the loop! Well, if we need both the index and the value of the element in the array, instead of iterating through the array, we can use the ArrayIterator, like so:

for (const fruit of fruits.entries()) {
    console.log(`${fruit[0]} is the ${fruit[1]} item`);
}

Since we are now using the ArrayIterator, each fruit is the value property, or the array that has the value as its first element and the index as its second. The code above would output:

Apple is the 0 item
Orange is the 1 item
Cherry is the 2 item
Pineapple is the 3 item

With some destructuring that we learned in the last post - we can write this with even more syntactic sugar!

for (const [index, fruit] of fruits.entries()) {
    console.log(`${fruit} is the ${index} item`);
}

And get the same output. The for of loop is also useful for when you are working with Array-ish objects, which are like objects that have a length and look like an array, but aren't really arrays and don't have all the array methods. (More on this in the Arrays post!) For example, the arguments parameter which gives you a collection of all the parameters passed to a specific function. Since the for of loop doesn't necessarily need to use an array, we can easily iterate through the arguments which is useful for a function that needs to perform some action, but doesn't know how many parameters are being passed to it. We can also use the for of loop to iterate through Strings, one character at a time, so we don't have to manually convert a String to an array of its characters. Last but not least, we can also use the for of loop to iterate through nodes from the DOM!

What do all of these have in common? They are Iterables! A for of loop can go through any iterable. However, plain Javascript objects are not iterables. A for of loop cannot be used on a regular object like this:

const avocado = {
    color: 'Green',
    type: 'Haas',
    rating: '10/10',
    comments: 'Avocados are the BEST'
};

If we try to use this object in our loop and console log the values, we'll get a TypeError that says avocado[Symbol.iterator] is not a function. (More on Symbols in a future post!). So, how can we use the for of loop with objects?

One option is to use Object.entries() or Object.values(), however this would need to be polyfilled because these functions are still in the proposal stage and not available yet.

Another option is to use the Object.keys() function which will return you an array of the keys, in this case, [color, type, rating, comments]. This array we can loop over like this:

for (const prop of Object.keys(avocado)) {
    const value = avocado[prop];
    console.log(value, prop);
}

And that will output:

'Green' color
'Haas' type
'10/10' rating
'Avocados are the BEST' comments

We have to reach outside of the loop to the original object, but it works! This is almost the same as using a for in loop, so take your pick on which type of loop you wanna use.

Notes and Thoughts

As someone new to learning about Javascript, I thought all the different kinds of loops were interesting. I asked my group, who are a bit more veteran Javascript users, what kind of for loop they used most prevalently before ES6. I think most people answered that they primarily used the forEach loop. If they needed an index, they would use for in. The way to get around the random properties and modifications to the prototype was to use hasOwnProperty to make sure the property exists on directly on the instance and not inherited from the prototype chain. I thought that was pretty interesting! Check out this post on StackOverflow, about a snippet in Twitter's JS that uses this little trick: https://stackoverflow.com/questions/12735778/for-in-and-hasownproperty

Thanks for reading!


by Miranda Wang