To create custom iterables (so that we can use it in for-of loops), we need to implement iterable protocol and iterator protocol. A protocol means a set of rules which we need to follow.
The iterable Protocol
According to this protocol:
In order to be iterable, an object must implement the iterator method.
This can be done by creating a member function named as Symbol.iterator (a built-in Symbol, check out tutorial). This function must be a zero arguments function that returns an object, conforming to the iterator protocol (next section).
An iterable looks like this:
let obj = {
// applying iterable protocol by defining iterator method
[Symbol.iterator](){
.........
}
}
In [Symbol.iterator], the square bracket encloses computed function name (tutorial);
The iterator Protocol
According to this protocol:
An iterator method implements
next() method which returns an object having two properties:
First property's name is
done of type boolean (its value should be 'false' if there are still items to be iterated, otherwise it should be 'true').
Second property's name is
value which returns the loop's current value until
done=true.
next() method implementation looks like this
let next = function(){
return {
done : true/false, //decide true/false dynamically,
value: val //evaluate value for current iteration until done=true
};
}
An object enclosing this function, must be returned from iterable's iterator method (iterable protocol)
Combining the two protocols
let next = function(){
return {
done : true/false, //decide true/false dynamically,
value: someValue //evaluate value for current iteration until done=true
};
};
let myObj = {
[Symbol.iterator](){//the iterator method
return { next };
}
};
Example:
In following example we will create a class which will be iterable and whose elements will be even numbers for the provided range of integers:
class EvenNumbers {
constructor(start, end) {
this._start = 2 * Math.round(start / 2) - 2;
this._end = end;
}
//implementing iterable protocol
[Symbol.iterator]() {
let [current, end] = [this._start, this._end];
return { //returns the iterator object
next() { //implementing next() method
if (current <= end) {
current += 2;
}
if (current > end) {
return { done: true }
} else {
return { value: current, done: false }
}
}
}
}
}
let evenNums = new EvenNumbers(5, 11);
//using for-of loop
for (let n of evenNums) {
console.log(n);
}
console.log("-----------");
console.log("using next() method:");
console.log("-----------");
let evenIterator = evenNums[Symbol.iterator]();
while (true) {
let obj = evenIterator.next();
console.log(obj);
if (obj.done) {
break;
}
}
In above example we used shorthand method syntax (tutorial) for next() method.
In the last tutorial we went through a lot of for-of examples, there we can apply next() methods as well. For example:
let fruits = ["apple", "banana", "orange"];
let fruitsItr = fruits[Symbol.iterator]();
console.log(fruitsItr.next());
console.log(fruitsItr.next());
console.log(fruitsItr.next());
console.log(fruitsItr.next());
console.log(fruitsItr.next());
Both iterable or iterator can be used in for-of loop
let nums = [1, 3, 5]; //array is an iterable
for (let n of nums) {
console.log(n);
}
console.log("------");
//getting nums iterator
let numsItr = nums[Symbol.iterator]();
//for-of can also used for numsItr
for (let n of numsItr) {
console.log(n);
}
|