新しいJavaScript構文においては、ループ処理が強化されています。過去においてループ処理と言えば for
や while
を使ったものに限定されていましたが、現在は forEach
や filter
であったり、 for 〜 of
のような構文もあります。
この時の肝になるのがイテレーターとジェネレーターです。
この記事では、この2つの機能の使い方を解説します。
イテレーターについて
イテレーターは以下のような決まりを持った配列です。
next
メソッドで次の値を返す- 値は
{value: "値", done: "判定"}
という形で取得できる - まだ次の値がある場合(最後の配列要素ではない)は、
done
プロパティが false - 次の値がない場合(最後の配列要素)は、
done
プロパティが true
これを繰り返し呼ぶことによって、ループ処理を行います。
let result = it.next();
while (!result.done) {
console.log(result.value);
result = it.next();
}
ただし、このような配列を作るのは面倒であり、高度なプログラミングスキルが求められます。
イテレーターが実装されているクラス
イテレーターは自作もできますが、すでにイテレーターを実装しているクラスを使うことができます。それは以下の2つです。
- Array
- Map
例えばArrayは以下のように使えます。
const ary = [1, 2, 3, 4, 5];
for (const value of ary) {
console.log(value * 2);
}
// => 2, 4, 6, 8, 10
Mapは次のようになります。
const map = new Map;
map.set('one', 1);
map.set('two', 2);
map.set('three', 3);
map.set('four', 4);
map.set('five', 5);
for (const [key, value] of map) {
console.log(${key} => ${value}
);
}
上記の結果は次のようになります。
one => 1
two => 2
three => 3
four => 4
five => 5
MapはJavaScriptで一般的に使われるObjectとは別物です。
そのため、以下はエラーになります。
エラーメッセージは obj
はイテレータブルではない(つまりイテレーターに対応していない)という内容です。
const obj = {
one: 1,
two: 2,
three: 3,
four: 4,
five: 5,
};
for (const value of obj) {
console.log(value);
}
// => Uncaught TypeError: obj is not iterable
そこで使われるのが、「ジェネレーター」になります。
ジェネレーターについて
ジェネレーターは任意の処理をイテレーターにするための機能です。
function*
という構文を使います。
そして yield
という構文を使って、イテレータにデータを送ります。
簡単に書くと、次のようになります。
function* test() {
yield 1
yield 2
}
const i = test();
for (const value of i) {
console.log(value);
}
この結果は、以下のようになります。
つまり、ジェネレーターの結果 i
をイテレーターとして実行すると、 yield
で送っているデータが順番に value
に入るという仕組みです。
1
2
このジェネレーターは next
メソッドを呼び出す毎に、結果が得られます。
つまり、以下のように書けます。
value
なのは一番最初に説明したイテレーターと同じ構造だからです。
function* test() {
yield 1
yield 2
yield 3
}
const i = test();
console.log(i.next().value); // => 1
console.log(i.next().value); // => 2
console.log(i.next().value); // => 3
つまり、ジェネレーターは next
メソッドを呼び出すまで、処理を停止できる関数と言い換えることもできます。
ジェネレーターに引数を渡す
ジェネレーターを呼び出す際に、引数を渡せます。
ただし、この引数はジェネレーター側では1つ前の実行結果として受け取る変数になります。
つまり、一番最初の呼び出し時には指定できません。ちょっと分かりづらいですが、以下のような出力になります。
function* test() {
// message1には「こんにちは」と入る
const message1 = yield "はじまり"
// message2には「Monaca」と入る
const message2 = yield message1
yield ${message1} ${message2}
}
const i = test();
console.log(i.next().value); // => はじまり
console.log(i.next("こんにちは").value); // => こんにちは
console.log(i.next("Monaca").value); // => こんにちは Monaca
yieldをイテレータブルにする
yieldをイテレータブルにするために yield*
という書き方があります。この場合、返す値をイテレータブルにすれば、順番に渡すようになります。
function* test() {
yield* [0, 1, 2, 3]
}
const i = test();
console.log(i.next().value); // => 0
console.log(i.next().value); // => 1
console.log(i.next().value); // => 2
console.log(i.next().value); // => 3
ちなみに文字 列 もイテレータブルなので、以下のようになります。
function* test() {
yield* "Monaca"
}
const i = test();
console.log(i.next().value); // => M
console.log(i.next().value); // => o
console.log(i.next().value); // => n
console.log(i.next().value); // => a
console.log(i.next().value); // => c
console.log(i.next().value); // => a
まとめ
今回はイテレーターとジェネレーターについて解説しました。
イテレーターは配列やMapで使えるので、普段のプログラムの中でもよく使う機能になるでしょう。
ジェネレーターは若干特殊な操作にはなりますが、うまく使うと処理全体が分かりやすく書けるようになります。
ぜひ使い方をマスターして、可読性の高いコードを実現してください。