To understand the concept of iterator in Javascript, it is important to memorize the following three protocal:
1. Mark an object iterable
To mark an object iterable, you need to add a Symbol.iterator
property to it:
var obj = {};
obj[Symbol.iterator] = function() { return iteratorObject; }
The Symbol.iterator
part looks really odd if you don't know what Symbol
is all about. One way to think about it is to see it as metadata that describes the hidden behaviors of an object. When an object is marked with the iterator symbol, it means you can loop through it like an array.
2. Return an iterator object
The code snippet above returns an iterator object. An iterator object needs to have a next
method which is a function that returns an iterator result object:
iteratorObject = {
next: function(){
return iteratorResultObj;
}
}
3. Return an iterator result object
Finally, I mean finally we can return some real values rather than wrappers that conforms to the iterable and iterator protocal.The iterator result object has to have two properties, one named value
and the other named done
.
iteratorResultObj = {
value: ...,
done: ... // either true or false
}
As long as an object has the three traits explained above, it is iterable and can be used in a for...of
loop or being spreaded [...myObj]
.
Putting it all together
var obj = {};
obj[Symbol.iterator] = function (){ // mark an object as iterable
var i = 1;
return { // this is the iterator object
next: function(){
return i < 5 ?
{ // this is the iterator result object
value: i++,
done: false
} : { done: true } // when the iterator reaches the end, value = undefined and done = true
}
}
}
var iter = obj[Symbol.iterator]();
console.log('value: ' + iter.next().value);
console.log('value: ' + iter.next().value);
console.log('value: ' + iter.next().value);
console.log('value: ' + iter.next().value);
console.log('done: ' + iter.next().done);
// the code above is equivalent of the following loop
for(var j of obj){
console.log(j);
}
Here is a jsbin link of the code above.
Bonus
To use ES6 syntax and define an iterator within a class:
class MyArray {
constructor(...items) {
this.items = items;
}
[Symbol.iterator]() {
let pos = 0;
let data = this.items; // the value of `this` changes to undefined inside the next() function below, thus, we need to store it in a variable first
return {
next: () => {
return pos < data.length?
{ value: data[pos++], done: false }
: { done: true }
}
}
}
}
let arr = new MyArray(1,2,3)
for(const i of arr){
console.log(i);
}