イテレーターとは
イテレーターは簡単に言うと、何かを順番に返すオブジェクトです。
これは特別な構文やコンストラクタで作るものではなく、特定の条件を満たすオブジェクトをイテレーターと呼びます。
イテレーターはnext
メソッドを持ちます。
このメソッドは呼ばれるたびに以下のプロパティを持つオブジェクトを返す必要があります。
-
value
: 返したい値(何を入れてもOK) -
done
: 返される値はこれで全てかどうかを示すboolean
例: 0~9を返す
例えば、0~9までの値を順番に列挙するイテレーターを考えてみます。
まず、イテレーターはオブジェクトなため、新しくオブジェクトを定義します。
const numberIterator = {}
次に、イテレーターにはnext
メソッドが必要です。
next
メソッドはdone
とvalue
プロパティを持つオブジェクトを返す必要があります。
const numberIterator = {
+ next() {
+ return { done: false, value: 0 } // 仮
+ }
}
そして、next
メソッドは呼び出しごとにvalue
がインクリメントされている必要があります。
これにはインクリメント用の値を一時的に保管する場所が必要ですが、今回は特に工夫せずcurrent
というプロパティに置いておきます。
const numberIterator = {
+ current: 0,
next() {
+ return { done: false, value: this.current++ }
}
}
最後に、this.current
が10
以上になった後は{ done: true }
を返すようにします。
全体像はこちらになります。
const numberIterator = {
current: 0,
next() {
if(this.current >= 10) {
return { done: true } // valueは省略可能
}
return { done: false, value: this.current++ }
}
}
なお、このnext
メソッドは呼び出すと以下のように値が返ってきます。
numberIterator.next()
> { done: false, value: 0 }
numberIterator.next()
> { done: false, value: 1 }
numberIterator.next()
> { done: false, value: 2 }
// 略
numberIterator.next()
> { done: false, value: 9 }
numberIterator.next()
> { done: true }
配列でよくない?
何か連続したものを表現したいなら、配列で事足りると思うかもしれません。
実際大半のイテレーターは配列で表現できますが、表現できないものもあります。
それは、無限に長さがあるものの表現です。
すべてのイテレーターを配列として表現できるとは想像するのは容易ですが、これは真実ではありません。配列は完全に割り当てなければなりませんが、イテレーターは必要なだけで消費されるため、0 から Infinity までの整数の範囲など、無限のサイズのシーケンスを表現できます。- MDN
例
先ほどの引用にある、0
からInfinity
までの整数をイテレーターで表現してみます。
といっても特別な処理は必要なく、むしろ先ほどのコードから上限を削除するだけです。
const infinityIterator = {
current: 0,
next() {
return { done: false, value: this.current++ }
}
}
たったこれだけで、無限の長さのイテレーターを作れます。
また、イテレーターや後述のイテラブルは遅延評価の実装にも使えます。
こちらの記事で紹介されているメソッドを使ったり、専用の処理を自作するなどでできます。
イテラブル
イテラブルは[Symbol.iterator]
メソッドを持つオブジェクトです。
これは反復可能オブジェクトとも呼ばれます。
[Symbol.iterator]
メソッドはイテレーターを返す必要があります。
Symbol.iterator
はウェルノウンシンボルのひとつです。
ウェルノウンシンボルはSymbol
オブジェクトの静的プロパティに入っているSymbol
オブジェクトのことで、使うとJavaScriptの組み込み動作をカスタマイズすることができます。
例えば今回のSymbol.iterator
は、オブジェクトをイテラブルというプロトコルに従わせることで、後述の「イテラブルが使える構文」が使えるようになります。
なお、組み込みのイテラブルなオブジェクトもあります。
例えばこのようなものがあります。
Array
Map
Set
[Symbol.iterator]
の実装
例として1~10を示すイテラブルなオブジェクトを作ってみます。
こういったときにはクロージャのような実装ができます。
const iterable = {
[Symbol.iterator]() {
let current = 0 // 数値を一時保管する変数
return { // イテレーターを返す
next() {
if(current >= 10) { // 10以上かチェック
return { done: true }
}
return { done: false, value: current++ }
}
}
}
}
実装手順はこのような感じです。
-
[Symbol.iterator]
メソッドを実装する - メソッド内で数字を一時的に保管する変数(
current
)を宣言する -
[Symbol.iterator]
がイテレーター(next
メソッドを持つオブジェクト)を返すようにする - イテレーターの
next
メソッドでcurrent
を更新する -
current
が10
以上かをチェックし、適切なオブジェクトを返す
イテラブルが使える構文
オブジェクトをイテラブルにするメリットの一つとして、いくつかの構文が使えるようになることが挙げられます。
イテラブルなオブジェクトは以下の構文が使えます。
for...of
各値を使ってループしたいときに使えます。
構文はfor (const 変数名 of イテラブル) { 処理 }
です。
例えば、先ほどのiterable
を使ってループしてみます。
for (const value of iterable) {
// 処理
console.log(value)
}
// 1
// 2
// 略
// 9
// 19
for...of
は配列の要素を使ってループしたいときにも使えますが、これは配列がイテラブルだからです。
スプレッド構文
...
を使った構文です。
イテラブルなオブジェクトを配列(Array
)に変換する時に使えます。
const array = [...iterable]
array // [1, 2, 3, ..., 9, 10]
また、関数の呼び出し時に引数を展開することもできます。
詳細はMDNをご覧ください。
その他
ここで紹介した以外にも、以下のような構文や言語仕様があります。
参考