DEV Community

off.tokyo
off.tokyo

Posted on

Understanding JavaScript iterators

source : Understanding JavaScript iterators

Iterators and generators are a big feature of ECMAScript 6 (released in June 2015 and still relatively new to JavaScript).

 

And It's a huge, complex system, and complicated.

 

So In this article, I'd like to explain iterators in a way that you can understand them step by step.

 

What is an Iterator Result?

 

An iterator is an object from which iterator results can be retrieved in order.

 

Specifically, an object that satisfies the following two points is called an iterator.

 

  • It must have a .next() method
  • When .next() is executed, it returns the iterator result.

 

In other words, in the following code, iterator is an iterator.

 

 

var iterator = {}; 
iterator.next = function(){
    var iteratorResult = { value: 42, done: false };
    return iteratorResult;
};

 

Here, in the code above, we see an iterator result called iteratorResult.

 

As you can see, the iterator result is an object and has the .value and .done properties.

 

The role of each property is as follows

 

  • The .value property is the value (item) retrieved from the iterator.
  • The .done property is a boolean value indicating whether or not the value has been sequentially extracted from the iterator.

 

What is Iterable?

 

An iterable is an object that has an iterator.

To be more specific, an object is said to be iterable if it satisfies the following

 

  • When the [Symbol.iterator]() method is executed, it should return an iterator.

 

In other words, in the following code, obj is iterable.

 

var iterator = {}; 
iterator.next = function(){
    var iteratorResult = { value: 42, done: false }; // イテレータリザルト
    return iteratorResult;
};

var obj = {}; 
obj[Symbol.iterator] = function(){
    return iterator;
};

 

An object that is iterable is also called an iterable object.

 

This relationship is shown in the figure below.

 

(Sorry for the Japanese, please just read and understand the English part.)

 

 

 

 

Create an iterable object.

 

As a simple example, let's create an iterable object with an iterator that can fetch the numbers 1 through 10 in order.

 

var obj = {}; // iterable object
obj[Symbol.iterator] = function(){
    var iterator = {}; // iterator
    var count = 1;
    iterator.next = function(){
        var iteratorResult = (count <= 10)
            ? { value: count++, done: false }
            : { value: undefined, done: true };
        return iteratorResult; // iterator result
    };
    return iterator;
};

 

The current value is stored in the variable count, and each time .next() is executed, the value is counted up and returned in .value.

 

When count exceeds 10, we set .done to true to indicate that we have finished retrieving the values in order.

 

Now we have an iterable object with an iterator that can fetch the numbers 1 to 10 in order.

 

Extract values from iterators in order

 

Let's extract the values from the iterator we just created in order and output them to the console.

 

We will use the property that .next() can be used to retrieve the iterator result.

 

var obj = {}; // iterable object
obj[Symbol.iterator] = function(){
    var iterator = {}; // iterator
    var count = 1;
    iterator.next = function(){
        var iteratorResult = (count <= 10)
            ? { value: count++, done: false }
            : { value: undefined, done: true };
        return iteratorResult; // iterator result
    };
    return iterator;
};

var iterator = obj[Symbol.iterator](); // get the iterator from an iterable object
var iteratorResult;
while(true){
    iteratorResult = iterator.next(); // retrieve the values in order
    if(iteratorResult.done) break; // break when finished fetching
    console.log(iteratorResult.value); // output the value to the console
}
/* 1
  1
  2
  3
  ...
  10
*/ ...

 

Now, we can extract the values from the iterator in order.

 

However, this is code that can be written in ECMAScript 5, and the way it is written is unnecessarily complicated, so You migthj don't see much benefit in it.

 

The reason why iterators are so useful is that you can use the for(v of iterable) syntax to get the values more easily.

 

Easier to extract values from iterators

 

A convenient syntax provided for extracting values from an iterator is for(v of iterable).

 

var obj = {}; // iterable object
obj[Symbol.iterator] = function(){
    var iterator = {}; // iterator
    var count = 1;
    iterator.next = function(){
        var iteratorResult = (count <= 10)
            ? { value: count++, done: false }
            : { value: undefined, done: true };
        return iteratorResult; // iterator result
    };
    return iterator;
};

for(var v of obj) console.log(v);
/*
  1
  2
  3
  ...
  10
*/ ...

 

This syntax, for(v of iterable), executes the following steps in order.

 

First, execute iterator = iterable[Symbol.iterator]() to get the iterator. ↓ Next, execute iteratorResult = iterator.next() to retrieve the iterator result. ↓ If iteratorResult.done == true, iteratorResult.done has been retrieved and quits. Otherwise, go to next. ↓ Assign v = iteratorResult.value and execute the statement (console.log(v)). ↓ Next, execute iteratorResult = iterator.next() to retrieve the iterator result.

 

Iterable objects prepared from official

 

By using for(v of iterable), It was able to write much smarter code.

 

However, the part that defines the iterable object is very long.

 

In fact, there is an iterable object that is already provided in JavaScript,

 

without having to define it yourself.

 

Related to Arrays

 

First, the array itself is an iterable object.

 

var obj = ["A", "B", "C"]; // イテラブルなオブジェクト
for(var v of obj) console.log(v);
/*
  "A"
  "B"
  "C"
*/

 

To check, let's see if the array is an iterable object with the properties we have raised so far.

 

var obj = ["A", "B", "C"]; // iterable object
var iterator = obj[Symbol.iterator]();
console.log(typeof iterator); // "object". We are indeed getting an iterator.
console.log(iterator.next()); // { value: "A", done: false }

 

Arrays also have a .keys() method that allows you to get an iterator to retrieve the array keys in order.

 

 

var obj = ["A", "B", "C"]; // iterable object
for(var v of obj.keys()) console.log(v);
/*
  0
  1
  2
/* 0 1 2

 

 

Arrays also have the .entries() method, which allows you to retrieve an iterator that fetches an array of keys and values in sequence.

 

var obj = ["A", "B", "C"]; // iterable object
for(var v of obj.entries()) console.log(v);
/*
  [0, "A"]
  [1, "B"]
  [2, "C"]
*/ [0, "A"] [1, "B"] [2, "C"].

 

String

 

A string object is also an iterable object.

It can be used to extract characters one by one from the beginning of a string.

 

var str = "あいう";
for(var v of str) console.log(v);
/*
  "あ"
  "い"
  "う"
*/

 

To check, let's see if the string is also an iterable object with the same properties as we have mentioned so far.

 

 

var str = "あいう";
var iterator = str[Symbol.iterator]();
console.log(typeof iterator); // "object"
console.log(iterator.next()); // { value: "あ", done: false }

 

Iterators

 

The iterators provided in JavaScript are actually iterable objects in their own right.

Therefore, executing the Symbol.iterator method will return itself.

 

var obj = ["A", "B", "C"]; // イテラブルなオブジェクト
var iterator = obj[Symbol.iterator](); // イテレータを取得する
for(var v of iterator) console.log(v); // for-of にイテレータを渡す
/*
  "A"
  "B"
  "C"
*/

console.log(iterator === iterator[Symbol.iterator]()); // true

 

Generators

 

Generators, which are created by generator functions, are both iterable objects and iterators.

Generators are also a very rich mechanism, which will be explained in another article.

 

function* gfn(n){
    while(n < 100){
        yield n;
        n *= 2;
    }
}
var gen = gfn(3);
for(var v of gen) console.log(v);
/*
  3
  6
  12
  24
  48
  96
*/

 

Other stuff

 

There are also various other iterable objects.

 

Arguments

// Firefox 40 dose not work
function func(){
    for(var v of arguments) console.log(v);
}
func(42, "あ", true);
/*
  42
  "あ"
  true
*/

 

TypedArray

var view = new Uint8Array([0, 1, -1]);
for(var v of view) console.log(v);
/*
  0
  1
  255
*/

 

Map

var map = new Map([[0, "Zero"], [{}, "Object"], [[], "Array"]]);
for(var v of map) console.log(v);
/*
  [0, "Zero"]
  [{}, "Object"]
  [[], "Array"]
*/

 

Set

var set = new Set([0, {}, []]);
for(var v of set) console.log(v);
/*
  0
  {}
  []
*/

 

There's more! How to use iterators

 

We used an iterator with the syntax for(v of iterable).

 

Actually, there are many other ways to use iterators.

 

Array

[... .iterable] syntax.

 

From iterable, you can create an array in which the values are extracted in order and the number of elements are placed in the corresponding part.

 

var ary = [0, "A", false];
var str = "あいう";
var connectedAry = [...ary, ...str];
console.log(connectedAry);
/*
  [0, "A", false, "あ", "い", "う"]
*/

 

You can also use the syntax Array.from(iterable) to do the same thing.

 

 

var str = "あいう";
var ary = Array.from(str);
console.log(ary); // ["あ", "い", "う"]

 

Argument passing

 

The syntax is func(. .iterable) syntax.

The values are taken out of iterable in order, and the function is executed so that the number of arguments are placed in the corresponding part.

 

var nums = [112, 105, 121, 111];
console.log( Math.max(...nums) ); // 121
console.log( String.fromCharCode(...nums) ); // "piyo"

 

Split Assignment

 

The syntax is [a, b, c] = iterable.

The values are taken out of iterable in order and assigned to the variables on the left side in order.

 

var [a, b, c] = "ひよこ";
console.log(c+b+a); // "こよひ"

 

Map, Set, WeakMap, WeakSet

 

The syntax is new Map(iterable), new Set(iterable), new WeakMap(iterable), new WeakSet(iterable).
Each iterable takes a value in turn, and the keys and values can be specified.

 

var set = new Set("あいうあお");
console.log(set); // Set {"あ", "い", "う", "お"}

var map = new Map(["A", "B", "C"].entries());
console.log(map); // Map {0 => "A", 1 => "B", 2 => "C"}

 

Practical sample

 

This is an application of the previous content.

 

Output array elements one by one to the console.

var ary = [0, 5, 9, 2, 7];
for(var v of ary) console.log(v);
/*
  0
  5
  9
  2
  7
*/

 

Copying an array

var ary0 = [1, 2, 3];
var ary1 = [...ary0];
console.log(ary0.join() === ary1.join()); // true
console.log(ary0 === ary1); // false

 

Assigning the first element of an array

var ary = ["A", "B", "C"];
var [first] = ary;
console.log(first); // "A"

 

Assign the first character of a string

var str = "ABC";
var [first] = str;
console.log(first); // "A"

 

Remove duplicate values from an array

var ary = [0, 5, 9, 0, 2, 5];
var uniqueAry = [...new Set(ary)];
console.log(uniqueAry); // [ 0, 5, 9, 2 ]

 

Pass variable length arguments to functions without using .apply()

var nums = [112, 105, 121, 111];
console.log( Math.max(...nums) ); // 121
console.log( String.fromCharCode(...nums) ); // "piyo"

 

 Assign the matched and partially matched characters all at once.

var [all, part] = "abcde".match(/ab(.)de/)
console.log(all, part); // "abcde", "c"

 

*This article is based on the Japanese article below and has been translated into English for easier understanding.

https://qiita.com/kura07/items/cf168a7ea20e8c2554c6

Top comments (0)