ECMAScript 6(2015年6月に公開され、今もなお比較的新しい JavaScript)の大目玉である イテレータ と ジェネレータ。なかなかに複雑で巨大な仕組みになっていてややこしいです。
そこで今回は イテレータ を、順を追って理解できるように解説したいと思います。
また、実用的なサンプルを「3. 実用サンプル」に示しました。
初めにこちらを見て、何ができるのかを知ってから読み始めるのもオススメです。
(2017年3月現在、オープンなページでの使用はまだ避けたほうがいいかもしれませんが、実装は確実に進んでいます。ECMAScript 6 compatibility table)
1. ことばの定義
1.1. イテレータ (Iterator)、イテレータリザルト (Iterator Result) とは
**イテレータ とは、「順番にイテレータリザルトを取り出すことのできるオブジェクト」**のことです。
具体的に示すと、以下の2点を満たすオブジェクトを、イテレータ と言います。
-
.next()
メソッドを持つ こと -
.next()
を実行すると イテレータリザルト を返す こと
つまり、以下のコードにおいて、iterator
は イテレータ です。
var iterator = {}; // イテレータ
iterator.next = function(){
var iteratorResult = { value: 42, done: false }; // イテレータリザルト
return iteratorResult;
};
ここで、上のコードで iteratorResult
という イテレータリザルト が出てきました。
見ての通り、イテレータリザルト はオブジェクトであり、.value
プロパティと .done
プロパティを持っています。
それぞれのプロパティの役割は以下のようになっています。
-
.value
プロパティ は、イテレータから取り出した 値(アイテム) -
.done
プロパティ は、イテレータから値を順番に取り出し終えたかどうかの 真偽値
1.2. イテラブル (Iterable) とは
**「イテレータを持つオブジェクト」**のことです。
具体的に示すと、以下のことを満たすオブジェクトを、イテラブル であると言います。
-
[Symbol.iterator]()
メソッドを実行すると イテレータ を返す こと
つまり、以下のコードにおいて、obj
は イテラブル です。
var iterator = {}; // イテレータ
iterator.next = function(){
var iteratorResult = { value: 42, done: false }; // イテレータリザルト
return iteratorResult;
};
var obj = {}; // イテラブルなオブジェクト
obj[Symbol.iterator] = function(){
return iterator;
};
イテラブルであるオブジェクトのことを イテラブルなオブジェクト とも言います。
1.3.【まとめ】イテラブル、イテレータ、イテレータリザルト
名称 | 説明 | 持っているメソッド/プロパティ |
---|---|---|
イテラブル (Iterable) なオブジェクト | イテレータを持つオブジェクト |
[Symbol.iterator]()
|
イテレータ (Iterator) | 順番にイテレータリザルトを取り出すことのできるオブジェクト |
.next()
|
イテレータリザルト (Iterator Result) | 取り出した値や、取り出し終えたかどうかの真偽値を持つオブジェクト |
.value , .done
|
2. イテレータ を使う
定義が理解できたところで、実際に使ってみましょう。
2.1. まずは イテラブルなオブジェクト を作る
まずは簡単な例として、"1~10の数を順番に取り出せるイテレータを持つ、イテラブルなオブジェクト" を作成してみます。
var obj = {}; // イテラブルなオブジェクト
obj[Symbol.iterator] = function(){
var iterator = {}; // イテレータ
var count = 1;
iterator.next = function(){
var iteratorResult = (count <= 10)
? { value: count++, done: false }
: { value: undefined, done: true };
return iteratorResult; // イテレータリザルト
};
return iterator;
};
現在の値が変数 count
に保持されていて、.next()
を実行するごとにカウントアップして .value
で返す 仕組みになっています。
count
が10を超えたら、.done
を true
にし、値を順番に取り出し終えた ことを示します。
これで "1~10の数を順番に取り出せるイテレータを持つ、イテラブルなオブジェクト" が準備できました。
2.2. 次に イテレータ から順番に値を取り出す
先ほど作った イテレータ から順番に値を取り出して、コンソールに出力してみます。
.next()
を用いると イテレータリザルト が取り出せる 性質を利用します。
var obj = {}; // イテラブルなオブジェクト
obj[Symbol.iterator] = function(){
var iterator = {}; // イテレータ
var count = 1;
iterator.next = function(){
var iteratorResult = (count <= 10)
? { value: count++, done: false }
: { value: undefined, done: true };
return iteratorResult; // イテレータリザルト
};
return iterator;
};
var iterator = obj[Symbol.iterator](); // イテラブルなオブジェクトからイテレータを取得する
var iteratorResult;
while(true){
iteratorResult = iterator.next(); // 順番に値を取りだす
if(iteratorResult.done) break; // 取り出し終えたなら、break
console.log(iteratorResult.value); // 値をコンソールに出力
}
/*
1
2
3
...
10
*/
これで、イテレータ から順番に値を取り出すことができました。
しかし、このコードは ECMAScript 5 でも書けるコードであり、書き方も無駄にややこしく、あまりメリットを感じられません。
それではなぜ イテレータ が便利なのか、それは for(v of iterable)
という構文を使えば、もっと楽に値を取り出せるからです。
2.3. もっと楽に イテレータ から値を取り出す
イテレータ から値を取り出すのに用意されている便利な構文が、for(v of iterable)
です。
var obj = {}; // イテラブルなオブジェクト
obj[Symbol.iterator] = function(){
var iterator = {}; // イテレータ
var count = 1;
iterator.next = function(){
var iteratorResult = (count <= 10)
? { value: count++, done: false }
: { value: undefined, done: true };
return iteratorResult; // イテレータリザルト
};
return iterator;
};
for(var v of obj) console.log(v);
/*
1
2
3
...
10
*/
この for(v of iterable)
という構文は、以下のような処理を順に実行しています。
- まず
iterator = iterable[Symbol.iterator]()
を実行して、イテレータ を取得する - 次に
iteratorResult = iterator.next()
を実行して、イテレータリザルトを取り出す - もし
iteratorResult.done == true
なら、取り出し終えたので終了する。そうでないなら 4. に進む -
v = iteratorResult.value
を代入して、文(console.log(v)
)を実行する -
- に戻る
この処理、2.2. で書いたコードとほぼ同じ処理です。
つまり、2.2. の長々しいコードを for(v of iterable)
という短いコードだけで実現できるのです。
2.4. 初めから用意されている イテラブルなオブジェクト
2.3. で紹介した for(v of iterable)
を使うことで、だいぶスマートにコードを書くことができました。
しかし、イテラブルなオブジェクト を定義する部分が非常に長くなっています。
実は、自分で イテラブルなオブジェクト を定義せずとも、JavaScript ですでに用意してくれている イテラブルなオブジェクト があります。
2.4.1. 配列(Array)関連
まず、配列そのものが イテラブルなオブジェクト です。
var obj = ["A", "B", "C"]; // イテラブルなオブジェクト
for(var v of obj) console.log(v);
/*
"A"
"B"
"C"
*/
確認のため、配列が今まで上げてきたような性質を持つイテラブルなオブジェクトかどうか見てみます。
var obj = ["A", "B", "C"]; // イテラブルなオブジェクト
var iterator = obj[Symbol.iterator]();
console.log(typeof iterator); // "object"。確かにイテレータを取得できている
console.log(iterator.next()); // { value: "A", done: false }
また、配列には .keys()
メソッド があり、これは配列のキーを順番に取り出す イテレータ を取得できます。
var obj = ["A", "B", "C"]; // イテラブルなオブジェクト
for(var v of obj.keys()) console.log(v);
/*
0
1
2
*/
また、配列には .entries()
メソッド というものもあり、これは配列のキーと値がセットになった配列を順番に取り出す イテレータ を取得できます。
var obj = ["A", "B", "C"]; // イテラブルなオブジェクト
for(var v of obj.entries()) console.log(v);
/*
[0, "A"]
[1, "B"]
[2, "C"]
*/
2.4.2. 文字列(String)
文字列オブジェクトも イテラブルなオブジェクト です。
文字列の先頭から1文字ずつ文字を取り出すことができます。
var str = "あいう";
for(var v of str) console.log(v);
/*
"あ"
"い"
"う"
*/
確認のため、文字列も今まで上げてきたような性質を持つイテラブルなオブジェクトかどうか見てみます。
var str = "あいう";
var iterator = str[Symbol.iterator]();
console.log(typeof iterator); // "object"
console.log(iterator.next()); // { value: "あ", done: false }
2.4.3. イテレータ(Iterator)
実は、JavaScript で用意されている イテレータ は、それ自身がイテラブルなオブジェクトなのです。
ゆえに、[Symbol.iterator]()
メソッドを実行すると、自分自身を返します。
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
2.4.4. ジェネレータ
ジェネレータ関数から生成される ジェネレータ は、イテラブルなオブジェクトであり、イテレータ でもあります。
ジェネレータ はこれまた非常に盛りだくさんな仕組みになっていますので、別の記事で解説します。
Qiita: JavaScript の ジェネレータ を極める!
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
*/
2.4.5. その他もろもろ
その他もイテラブルなオブジェクトがいろいろあります。
Arguments
// Firefox 40 ではこのサンプルのみ動作しません
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
{}
[]
*/
2.5. もっとある!イテレータ の利用法
2.3. では、for(v of iterable)
という構文で イテレータ を利用しました。
実は、イテレータ を利用する方法はこの他にもたくさんあります。
2.5.1. 配列
[...iterable]
という構文です。
iterable
からは順番に値が取り出されて、個数分の要素が該当部分に入るような配列を作成できます。
var ary = [0, "A", false];
var str = "あいう";
var connectedAry = [...ary, ...str];
console.log(connectedAry);
/*
[0, "A", false, "あ", "い", "う"]
*/
また、Array.from(iterable)
という構文でも同様のことが可能です。
var str = "あいう";
var ary = Array.from(str);
console.log(ary); // ["あ", "い", "う"]
2.5.2. 引数渡し
func(...iterable)
という構文です。
iterable
からは順番に値が取り出されて、個数分の引数が該当部分に入るように関数を実行します。
var nums = [112, 105, 121, 111];
console.log( Math.max(...nums) ); // 121
console.log( String.fromCharCode(...nums) ); // "piyo"
2.5.3. 分割代入
[a, b, c] = iterable
という構文です。
iterable
からは順番に値が取り出されて、左辺の変数に順番に代入されます。
var [a, b, c] = "ひよこ";
console.log(c+b+a); // "こよひ"
2.5.4. Map, Set, WeakMap, WeakSet
new Map(iterable)
, new Set(iterable)
, new WeakMap(iterable)
, new WeakSet(iterable)
という構文です。
それぞれ iterable
からは順番に値が取り出されて、キーや値を指定することができます。
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"}
2.6.【まとめ】イテラブルなオブジェクト の種類、利用法
以上の内容をまとめるとともに、対応ブラウザバージョンを併記しました。
ただし、組み合わせによっては動かないものも稀にありますので、詳細は ECMAScript 6 compatibility table を参照してください。
2.6.1. イテラブルなオブジェクト の種類
2.4.1. で上げたように、配列 ["A", "B", "C"]
は イテラブルなオブジェクト です。
今まで紹介した イテラブルなオブジェクト をすべて下の表にまとめました。
コード例 | 記事参照 | IE | Fx | GC | 備考 | |
---|---|---|---|---|---|---|
自分で作る | 略 | 2.1. | Edge | ○ | ○ | |
配列 | ["A", "B", "C"] |
2.4.1. | ○ | ○ | ○ | |
配列の.keys(), .entries() | ["A", "B", "C"].entries() |
2.4.1. | Edge | ○ | ○ | それ自身がイテレータ |
文字列 | "あいう" |
2.4.2. | ○ | ○ | ○ | |
イテレータ | 略 | 2.4.3. | Edge | ○ | ○ | それ自身がイテレータ |
ジェネレータ | (function*(){})() |
2.4.4. | Edge13 | ○ | ○ | それ自身がイテレータ |
Arguments | (function(){arguments/*←コレ*/})() |
2.4.5. | ○ | ○ ※A | ○ | |
TypedArray | new Uint8Array([0, 1, -1]) |
2.4.5. | IE10 | ○ | ○ | |
Map | new Map() |
2.4.5. | Edge | ○ | ○ | |
Set | new Set() |
2.4.5. | Edge | ○ | ○ |
2017年3月現在の最新ブラウザ:Edge14, Fx52, GC56
※A: ただしイテラブルではありません → イテラブルになりました(Fx46~47?)
2.6.2. イテラブルなオブジェクト の利用法
2.3. で上げたように、for-of文は イテラブルなオブジェクト を利用する方法の一つです。
今まで紹介した イテラブルなオブジェクト を利用する方法を、すべて下の表にまとめました。
コード例 | 記事参照 | IE | Fx | GC | |
---|---|---|---|---|---|
コードを自分で書く | 略 | 2.2. | Edge | ○ | ○ |
for-of 文 | for(v of iterable) |
2.3. | Edge | ○ | ○ |
配列リテラル | [...iterable] |
2.5.1. | Edge | ○ | 46 |
Array.from | Array.from(iterable) |
2.5.1. | Edge | ○ | 45 |
引数渡し | func(...iterable) |
2.5.2. | Edge | ○ | 46 |
分割代入 | [a, b, c] = iterable |
2.5.3. | Edge14 | ○ | 49 |
Map | new Map(iterable) |
2.5.4. | Edge | ○ | ○ |
Set | new Set(iterable) |
2.5.4. | Edge | ○ | ○ |
WeakMap | new WeakMap(iterable) |
2.5.4. | Edge | ○ | ○ |
WeakSet | new WeakSet(iterable) |
2.5.4. | Edge | ○ | ○ |
2017年3月現在の最新ブラウザ:Edge14, Fx52, GC56
3. 実用サンプル
今までの内容を応用したものです。
var ary = [0, 5, 9, 2, 7];
for(var v of ary) console.log(v);
/*
0
5
9
2
7
*/
var ary0 = [1, 2, 3];
var ary1 = [...ary0];
console.log(ary0.join() === ary1.join()); // true
console.log(ary0 === ary1); // false
var ary = ["A", "B", "C"];
var [first] = ary;
console.log(first); // "A"
var str = "ABC";
var [first] = str;
console.log(first); // "A"
var ary = [0, 5, 9, 0, 2, 5];
var uniqueAry = [...new Set(ary)];
console.log(uniqueAry); // [ 0, 5, 9, 2 ]
var nums = [112, 105, 121, 111];
console.log( Math.max(...nums) ); // 121
console.log( String.fromCharCode(...nums) ); // "piyo"
var [all, part] = "abcde".match(/ab(.)de/)
console.log(all, part); // "abcde", "c"
4. 参考
ECMAScript 2015 Language Specification – ECMA-262 6th Edition
Iterators and generators - JavaScript | MDN
イテレータについて - JS.next
十六章第二回 イテレータ — JavaScript初級者から中級者になろう — uhyohyo.net
ECMAScript 6 compatibility table