Let us all take out a minute from our busy lives to look at past and think how many hours did we waste fixing that for loop
. If your memory returns an empty array have a look at this one. (Spoiler alert!, it sums the two consecutive elements of an array and for the first element it pairs it with the last element.)
for (var i = 0, len = grid.length, j = len - 1, p1, p2; i < len; j = i++) {
p1 = grid[i];
p2 = grid[j];
sum += p1 + p2;
}
The Problem with for loop
The biggest problem, in my opinion, is that they are too powerful. You can easily end up with a wrong for loop
configuration which might deceivingly work, only to explode later in your production stack.
In short, the surface area of
for loop
is so wide that it is a bug magnet.
Functional Alternatives
90% of the times you can always convert that good old for loop
into a nice good looking .map
/.reduce
. There are a very few places where things could go wrong with this approach.
for (var i = 0; i < array.length; i++) {
array[i] = array[i] * 2;
}
array = array.map(o => o * 2);
The Iterable Approach
A functional alternative should be your first choice in getting rid of for loop
s, but there are times when you want to stick to it:
- You feel performance is critical. For loop is still fastest but only by a very small margin.
- You want to
break;/continue;
out of loop easily. - Certain complicated objects (
Sets
,Map
,NodeList
) don't really have a straightforward way of looping through them.
The smart ECMAScript folks knew about these limitations and probably many more, so they came up with an iteration protocol in ES2015.
What is a protocol anyway?
Javascript doesnโt have formal protocols like other languages. Think of it as a convention, like how node programmers like to follow error first callback.
Introducing iterables
Iterables are everywhere in javascript, you have been using them unknowingly. Anything which has a Symbol.iterator
property is iterable.
Let us look at the simplest iterable, a string!
str = 'hello world';
iterator = str[Symbol.iterator](); // StringIterator {}
iterator.next(); // gives `h`
...
...
iterator.next(); // gives `d`
iterator.next(); // gives `undefined` as no more string left.
string type in javascript comes baked in with iteration protocol, which means we can now say strings are iterable.
What is Symbol and Symbol.iterator?
This topic is worthy of an article itself, but in short Symbol
solves the problem of sneaking in a property into an object which you do not want to clash with any existing property. Visit MDN web docs for more info
So no more putting
@@@@@\_my\_hidden\_property\_
in your objects!
Symbol.iterator
is a globally available constant for anyone to use and implement the iteration protocol. So you can use it to make your own object implement iteration.
How do I implement the iteration protocol for my custom objects?
class Rand {
[Symbol.iterator] () {
let count = 0;
return {
next: () => ({
value: count++,
done: count > 5
})
};
}
}
var rand = new Rand();
var iterator = rand[Symbol.iterator]();
iterator.next();// {value: 0, done: false}
iterator.next();// {value: 1, done: false}
// ..
iterator.next();// {value: 5, done: false}
iterator.next();// {value: undefined, done: true}
Don't let the syntax bog you down. Let us break this example down:
-
[Symbol.iterator] ()
This weird looking syntax is nothing but a new ES2015 way of initializing properties dynamically. (Look here more information.) - The
Symbol.iterator
method must return an object{ next }
(Don't forget this is all a convention/protocol). We call this objectiterator
. (More on this in the next section) - The
.next()
is simply incrementing the count every time it is called and it togglesdone
totrue
when thecount
exceeds5
.
What is the difference between iterable
& iterator
?
Repeat after me,
-
Iterable is an object which implements the iteration protocol.
string
,Array
,Set
,Map
are all iterables!
class Rand {
[Symbol.iterator] () { // Rand has `Symbol.iterator` method, hence it is an iterable!
let count = 0;
return { // The return value is called an `iterator`
next: () => ({
value: count++,
done: count > 5
})
};
}
}
-
Iterator is the thing which is returned by
[Symbol.iterator]()
of an iterable.- It contains useful state information about where the current iteration is and what value to provide next.
- Any iterator must have a
.next
method on it (remember this is all a convention?), which would be used to get the next value out of it. - The object returned by the
.next()
method must be{value, done}
, wherevalue
is the current value anddone
tells whether iteration has finished or not.
var iterator = rand[Symbol.iterator](); // I am an iterator
iterator.next(); // {value: 0, done: false}
iterator.next(); // {value: 1, done: false}
...
iterator.next(); // {value: 4, done: false}
iterator.next(); // {value: undefined, done: true}
What do I get out of this complicated protocol?
You get a lot of superpowers for free if you enable iteration in your custom object or use any of Javascript's inbuilt iterables like Array
, string
, Map
or Set
.
1. Super Power: Spread it
Remember the class Rand
that we just defined above? Since it is an iterable it inherits the spreading super powers. Visit MDN web docs for more info on spread.
var rand = new Rand();
var myArray = [...rand]; // [0, 1, 2, 3, 4]
// string can also be used since it is an iterable
[..."kushan"]; // ["k", "u", "s", "h", "a", "n"]
Note: We didn't do [...rand[Symbol.iterator]()]
, since ...
expects an iterable
and not iterator
.
2. Super Power: Use Array.from
Array.from(rand); // [0, 1, 2, 3, 4]
Array.from("kushan"); // ["k", "u", "s", "h", "a", "n"]
3. Super Power: for of loop
for of
is a new looping mechanism introduced in ES2015, which only understands iterables. It automagically calls Symbol.iterator
, stores the iterator behind the scenes and calls .next
for you. It also stops when the iterator returns {done:true}
.
for(const v of rand) {
console.log(v);
}
/*Output*/
// 0
// 1
// ..
// 4
Unlike
spread operator
,for of
loop can accept bothiterable
oriterator
.
var map = new Map([['a', 1], ['b', 2]]);
map[Symbol.iterator];// map is iterable because it has the `Symbol.iterator` key
// `for of` loop understands `iterable`
for (const [key, val] of map) {
console.log(key, val); // 'a', 1
}
// `for of` loop also understands iterators
var iterator = map[Symbol.iterator](); // returns an iterator
for (const [key, val] of iterator) {
console.log(key, val); // 'a', 1
}
// .keys() is a part of `Map` api
var keyIterator = map.keys(); // returns an iterator
for (const key of keyIterator) {
console.log(key); // 'a'
}
// .values() is a part of `Map` api
var valueIterator = map.values(); // returns an iterator
for (const val of valueIterator) {
console.log(val); // 1'
}
4. Super Power: Destructuring
This is one of my favourite for demoing super powers of iterables
. ES2015 introduced destructuring assignment, which is built on top of iterables. You can only destruct iterables!
// array is iterable
[a, b] = [10, 20]; // a=10, b=20
// our custom class rand is Destructable :P
[a, b, c] = rand; // a = 0, b = 1, c = 2
// you can do fancy things like
[a, ...b] = rand; // a = 0, b = [1, 2, 3, 4]
Summary
Please share any other super power that you get for free when using iterables
. I hope this article helped you understand iterables
& iterators
.
Don't forget to check out my previous articles.
If you โค๏ธ this article, please share this article to spread the words.
Top comments (6)
Thanks @kepta for the wonderful article.
I am able to see "why" iterators be so useful with those 4 "superpowers" ๐
Regarding
iterator
vsiterables
, the way I understood is that,iterables
are objects that can be iterated overiterable
objects return aniterator
object with which we can iterate.Did I understand it correctly?
You got that damn right !
Thanks Kushan ๐
Nice article Kushan
Thanks for the article....learned from it
Very helpful, thank you Kushan ๐