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.
Top comments (0)