こんにちは。はるぐちです。
最近JavaScriptのイテレータについて学習したのでまとめておきたいと思います。 勘違いや誤り等ありましたら、コメントで指摘していただけると助かります🙏
イテレータとは
反復処理などでよく見かけるイテレータ。 わかってるようでわかってないのでまずは言葉の意味を理解するところから。
まずはMDNを見てみます。 イテレーターとジェネレーター - JavaScript | MDN
JavaScript では、イテレーターはシーケンスおよび潜在的には終了時の戻り値を定義するオブジェクトです。
なるほど、よくわからん😅となりますね。
めちゃくちゃざっくりいうと反復処理において、次の値を示すvalue
プロパティと,反復処理の終了を表すdone
プロパティの2つのプロパティを返すnext
メソッドを実装しているオブジェクトをイテレータと呼びます。
- イテレータ
next
メソッドを持つ- 返り値:以下の2つのプロパティを持つオブジェクトを返す
value
: 反復処理における次の値dole
: 反復処理における最後の値が消費されたかどうかを表す。反復処理が終わっていたらtrue
になっている
- 返り値:以下の2つのプロパティを持つオブジェクトを返す
nextメソッドの戻り値を確認してみる
Array.prototype.values()
メソッドはイテレータを返すのでnext()
メソッドがどのようになっているか、nodeで確かめてみます。
Array.prototype.values() - JavaScript | MDN
> const arr = ['a', 'b', 'c', 'd']; undefined > arr.values(); // イテレータが返ってることがわかる Object [Array Iterator] {} > const it = arr.values() // イテレータを変数に代入 undefined > it.next(); // nextメソッドを呼び出すとオブジェクトが返ってくる { value: 'a', done: false } > it.next(); { value: 'b', done: false } > it.next(); { value: 'c', done: false } > it.next(); { value: 'd', done: false } > it.next(); { value: undefined, done: true } // 反復処理が終わったらdone: trueになる > it.next(); { value: undefined, done: true } // 反復処理が終わったら何回呼び出しても結果は同じ
イテレータはfor ... of
文で反復処理を行うことができる
配列からイテレータを取り出してfor ... of
文で反復処理を行なってみます。
> const arr = ['foo', 'bar', 'baz']; > const it = arr.values() // イテレータを代入 > for (const i of it) { // イテレータを反復処理する // 内部的にイテレータからvalueが取り出される console.log(i.toUpperCase()); } //→FOO //→BAR //→BAZ > it.next() { value: undefined, done: true } // 反復処理が終わったのでdone: true
このようにイテレータはfor ... of
文で反復処理を行うことができます。
(というか`for ... of文がイテレータを処理する実装になっていると言った方が正しいかもしれません。)
反復可能オブジェクト
次に反復可能オブジェクト(iterable object)についてみていきます。
安定のMDN反復処理プロトコル - JavaScript | MDN
反復可能プロトコルによって、 JavaScript のオブジェクトは反復動作を定義またはカスタマイズすることができます。例えば、 for...of 構造の中でどの値がループに使われるかです。一部の組み込み型は既定の反復動作を持つ組み込み反復可能オブジェクトで、これには Array や Map がありますが、他の型 (Object など) はそうではありません。
イテレータの説明よりはわかりやすいかな。😅
これもざっくり説明すると反復可能オブジェクトとはArray
やMap
やString
のようなオブジェクトのことで、イテレータを持っているオブジェクトのことを指します。
(さっきも配列からイテレータを取り出しましたよね)
もう少し厳密にいうと@@iterator
メソッドを実装しているオブジェクトのことで、このメソッドは返り値にイテレータを返します。
@@iterator
メソッドはSymbol.iterator
というプロパティに定義されているので、自作クラスで反復可能オブジェクトを作りたい場合は[Symbol.iterator]
メソッドを定義することになります。
- 反復可能オブジェクト
[Symbol.iterator]
メソッドを実装しているオブジェクト- 返り値はイテレータ(nextメソッドを実装してるオブジェクト)
反復可能オブジェクトはfor ... of
文で反復処理ができる
for ... of
文は反復可能オブジェクト(iterable object)に対して反復処理ができます。
(イテレータは反復可能オブジェクトでもあるので先ほどのイテレータの例も反復処理が可能だったと考えることができます。)
// 以下はStringの反復処理 const str = 'hello world'; for (const c of str) { console.log(c.toUpperCase()); } H E L L O W O R L D
自作クラスを反復可能にしてみる
最後に自作クラスを反復可能にしてみます。
やること
Set
オブジェクトを再発明したGroup
クラス(およびGroupIteratorクラス)を作ります。*1
Groupにはadd
, delete
, has
メソッドとfrom
という静的メソッドを用意します。
Setオブジェクトと挙動を合わせるため以下のような要件になっています。
- Groupクラス
作成してみた
class Group { constructor () { this.values = []; } static from(obj) { const group = new Group(); for (let element of obj) { group.add(element); } return group; } add(value) { if (!this.has(value)) { this.values.push(value); } } has(value) { return this.values.includes(value); } delete(value) { this.values = this.values.filter(function(ele){ return ele != value; }) } [Symbol.iterator]() { return new GroupIterator(this); } } class GroupIterator { constructor(group) { this.index = 0; this.group = group; } next() { if (this.index === this.group.values.length) return {done: true}; let result = {value: this.group.values[this.index], done: false}; this.index++; return result; } }
できました。
反復処理してみる
作ったGroupクラスからオブジェクトを作成し、反復処理してみます。
for (let value of Group.from(["a", "b", "c"])) { console.log(value); } // → a // → b // → c
できていますね。 今回で言うとGroupクラスは配列で内部データを表しているのでイテレータを取り出すのはもっと簡単なんですが、わざわざ自分で作ってみて理解が深まりました。
参考
- イテレーターとジェネレーター - JavaScript | MDN
- 反復処理プロトコル - JavaScript | MDN
- for...of - JavaScript | MDN
- Array.prototype.values() - JavaScript | MDN
- 書籍『初めてのJavaScript』
- Eloquent JavaScript
*1:『流麗なJAVASCRIPT』という書籍を参考にしています