Overview
Javascript has a traditional way of iterating on the objects with loops like for, while, which need to keep track of each item in the collection. But this approach creates many complexities when we have to do nested iterations. We need to initialize a variable for each loop and keep track of them.
To solve this ES6 has introduced iterators which makes working with a collection of items easier. Iterators, when called, returns the next object of the collection.
What are javascript iterator?
Iterators are just objects with a set of an interface designed for iteration. Each iterator object has a next()
methods that returns an object. Each returned object has two properties value
the next value and done
a boolean indicating that this is the last item.
The iterator keeps track of the current item in the collection and with each next()
call returns the appropriate next value.
function * numbers(){ yield 1; yield 2; yield 3; } let iterator = numbers(); console.log(iterator.next()); //{value: 1, done: false} console.log(iterator.next()); //{value: 2, done: false} console.log(iterator.next()); //{value: 3, done: false} console.log(iterator.next()); //{value: undefined, done: true}
Here we have used a generator to create the iterator.
What are generators?
A generator is a function that returns an iterator. They are indicated by *
and uses yield
keyword.
function * createIterator(){ yield "value"; yield 2; yield object = {}; }
The *
before createIterator() makes this function a generator. yield
keyword specifies what value the function should return when next()
method is called.
The generator function stops the execution after each yield
statement. yield
can only be used inside the generator functions.
function * createIterator(){ let friends = ['Pranav', 'Sachin', 'Panam', 'Yogesh', 'Abhilash']; for(let i = 0; i < friends.length; i++){ yield friends[i]; } } let iterator = createIterator(); console.log(iterator.next()); //{value: 'Pranav', done: false} console.log(iterator.next()); //{value: 'Sachin', done: false} console.log(iterator.next()); //{value: 'Panam', done: false} console.log(iterator.next()); //{value: 'Yogesh', done: false}
Generators are one of the most important features of ES6. As they are just functions they can be used anywhere.
Generator Return Statements
As generators are function we can specify return
statement both to exit early and specify a return value for the last call to the next()
method
function * createIterator(){ let friends = ['Pranav', 'Sachin', 'Panam', 'Yogesh', 'Abhilash']; for(let i = 0; i < friends.length; i++){ if(i == 2){ return 'arun'; } yield friends[i]; } } let iterator = createIterator(); console.log(iterator.next()); //{value: 'Pranav', done: false} console.log(iterator.next()); //{value: 'Sachin', done: false} console.log(iterator.next()); //{value: "arun", done: true}
Passing value to generators
When an argument is passed to the next()
method, that argument becomes the value of the yield statement inside a generator.
function *createIterator() { let first = yield 1; let second = yield first + 5; yield second + 6; } let iterator = createIterator(); console.log(iterator.next()); //"{ value: 1, done: false }" console.log(iterator.next(4)); // "{ value: 9, done: false }" console.log(iterator.next(5)); // "{ value: 11, done: false }" console.log(iterator.next()); {value: undefined, done: true}
Generator Objects
Generators can also be added to the objects.
let friends = { createIterator: function *(items) { for (let i = 0; i < items.length; i++) { yield items[i]; } }, *createIterator2(items){ for (let i = 0; i < items.length; i++) { yield items[i]; } } }; let iterator = friends.createIterator(['Pranav', 'Sachin', 'Panam', 'Yogesh', 'Abhilash']); console.log(iterator.next()); //{value: 'Pranav', done: false} console.log(iterator.next()); //{value: 'Sachin', done: false} let iterator2 = friends.createIterator2(['goku', 'krillin', 'vegeta']); console.log(iterator2.next()); //{value: 'goku', done: false} console.log(iterator2.next()); //{value: 'krillin', done: false}
Iteratable
An Iteratable is an object with a symbol.iterator
property. It is closely related to itertors. symbol.iterator
specifies a function that returns the iterator for the given object. All collection objects (arrays, sets, and maps) and strings are iterables in ECMAScript6 and so they have a default iterator specified. It is designed to be used with new for-of
.
For-of
ES6 has introduced a new loop called for-of
which is used to iterate over the values of the object. It can be used on iterators.
Default iterator for the array
let friends = ['Pranav', 'Sachin', 'Panam', 'Yogesh', 'Abhilash']; let iterator = friends[Symbol.iterator](); console.log(iterator.next()); //{value: "Pranav", done: false} console.log(iterator.next()); //{value: "Sachin", done: false} console.log(iterator.next()); //{value: "Panam", done: false}
A for-of
loop calls next()
on an iterable each time the loop executes and stores the value from the result object in a variable. The loop continues this process until the returned object’s done
property is true
.
For-of with Array
let friends = ['Pranav', 'Sachin', 'Panam', 'Yogesh', 'Abhilash']; for(let friend of friends){ console.log(friend); } //"Pranav" //"Sachin" //"Panam" //"Yogesh" //"Abhilash"
For-of with objects
We can also use the for-of
on the objects. There are different inbuilt methods which can be used to achieve the same.
Built In iterators
ES6 has three types of collection objects: arrays, maps, and sets. All three have the following built-in iterators to help you navigate their content:
- entries() - Returns an iterator whose values are a key-value pair
- values() - Returns an iterator whose values are the values of the collection
- keys() - Returns an iterator whose values are the keys contained in the collection
For-of with plain object
let car = { maker: "BMW", color: "red", year : "2010", } for (let prop of Object.keys(car)){ console.log(car[prop]); } // BMW maker // red color // 2010 year
For-of with Map
let data = new Map(); data.set("prashant", 1); data.set("pranav", 1); data.set("sachin", 1); data.set("panam", 1); for (let prop of data.entries()){ console.log(prop); } //["prashant", 1] //["pranav", 1] //["sachin", 1] //["panam", 1]
Destructuring the data
let data = new Map(); data.set("prashant", 1); data.set("pranav", 2); data.set("sachin", 3); data.set("panam", 4); for (let [key, value] of data.entries()){ console.log(`${key} at ${value}`); } //prashant at 1 //pranav at 2 //sachin at 3 //panam at 4
For-of with Set
let data = new Set(); data.add('prashant'); data.add('pranav'); data.add('sachin'); data.add('panam'); for (let prop of data.values()){ console.log(prop); } //prashant //pranav //sachin //panam
For-of with String
let data = 'prashant'; for (let char of data){ console.log(char); } /* p r a s h a n t */
For-of with generators
Iterators created by generators are also iterables, as generators assign the symbol.iterator
property by default.
let friends = { *createIterator(items){ for (let i = 0; i < items.length; i++) { yield items[i]; } } }; let iterator = friends.createIterator(['Pranav', 'Sachin', 'Panam', 'Yogesh', 'Abhilash']); for(let friend of iterator){ console.log(friend); } //Pranav //Sachin //Panam //Yogesh //Abhilash