JavaScriptのイテレータとイテレータの概要
JavaScriptは、配列などのオブジェクトをfor…ofやスプレッド演算子...
などの制御構造で使用して、データを順番にループできるプロトコルをサポートしています。 これはイテラブルと呼ばれ、この機能をサポートするデータ構造はイテラブルと呼ばれます。 JavaScriptは、最初から反復可能なプロパティを持つマップ、配列、およびセットを提供しますが、プレーンオブジェクトにはデフォルトでこれがありません。
Iterableは、他のデータコンシューマーがその要素に順次アクセスできるようにするメカニズムを提供するデータ構造です。 for...of
ループ内に配置されたときに、データを1つずつ順番にアンロードする自己パッケージ化されたデータ構造を想像してみてください。
反復可能プロトコルの概念は、 iterable (データ構造自体)と iterator (反復可能上を移動するポインターの一種)に分割できます。 たとえば、配列を考えてみましょう。配列がfor...of
ループで使用されている場合、iterator
を返すiterableプロパティが呼び出されます。 この反復可能なプロパティにはSymbol.iterator
という名前が付けられ、返されるオブジェクトは、すべてのループ制御構造で共有される共通のインターフェイスで使用できます。
ある意味で、Symbol.iterator
は、データ構造がループに配置されるたびにイテレーターを生成するイテレーターファクトリと比較できます。
イテレータがデータ構造を移動して要素を順番に提供すると、イテレータによって返されるオブジェクトにはvalue
プロパティとdone
プロパティが含まれます。
この値は、イテレータが指す現在のデータ値を示し、done
は、イテレータがデータ構造の最後の要素に到達したかどうかを示すブール値です。
この{value, done}
は、ループなどの構造によって消費されます。 では、イテレータメソッドはどのようにして次のオブジェクトを呼び出すのでしょうか。 Symbol.iterator()メソッド内で定義されているnext()
メソッドを使用します。
この時点で使用できるイテレータプロパティのより適切な定義は、コレクションの要素に1つずつアクセスする方法をが知っているプロパティであり、アクセスを停止する論理ルールも提供することです(例: 。 配列にこれ以上要素がない場合)。
オブジェクトとイテラブル
JavaScriptオブジェクトはかっこいいですが、なぜ反復可能ではないのでしょうか。 ええと、いくつかの理由が考えられます:
- オブジェクトの重要な機能の1つは、ユーザー定義であるということです。 したがって、サイレント
[Symbol.iterator]()
をオブジェクトに滑り込ませると、厄介な驚きが生じます。 - 上記の点は、すべてのオブジェクト構成が類似していない可能性があることを考慮して、ユーザーが手動で追加できることも意味します。 したがって、共通の反復可能なプロパティを持つことは、まったく意味がありません。
- オブジェクトの最上位要素をループする場合は、他の人、
for...in
ループを使用します。 - Mapsオブジェクトタイプの使用がより適切な場合があります。
最後のポイント(マップに移動するには通常のオブジェクトに慣れすぎていることを認めたくありません)を除く上記のすべてのポイントは、オブジェクトに反復可能を持たない正当な理由ですが、上司がJavaScriptオブジェクトに反復可能を持たせたい場合はどうでしょうか?
オブジェクトに対する単純な反復可能な実装は、次のようになります。
let Reptiles = { biomes: { water: ["Alligators", "Crocs"], land: ["Snakes", "Turtles"] }, [Symbol.iterator]() { let reptilesByBiome = Object.values(this.biomes); let reptileIndex = 0; let biomeIndex = 0; return { next() { if (reptileIndex >= reptilesByBiome[biomeIndex].length) { biomeIndex++; reptileIndex = 0; } if (biomeIndex >= reptilesByBiome.length) { return { value: undefined, done: true }; } return { value: reptilesByBiome[biomeIndex][reptileIndex++], done: false }; } }; } }; // let's now iterate over our new `Reptiles` iterable: for (let reptile of Reptiles) console.log(reptile);
出力は次のようになります。
Alligators Crocs Snakes Turtles
この例では、イテレータをオブジェクト内に実装できることがわかります。 Iterablesは、特定の状況を処理しながら使いやすくし、長いパス名を記述しないようにするオブジェクトの強力なプロパティになります。
イテレータにアクセスする
for...of
のようなループには、done
の値がtrueと評価されるまで反復可能オブジェクトを消費するメカニズムが組み込まれています。 組み込みのループなしで、自分でイテラブルを消費したい場合はどうなりますか? 簡単です。イテレータからイテレータを取得し、手動で next()を呼び出します。
上記と同じ例を考えると、次のようにSymbol.iterator
を呼び出すことにより、Reptiles
からイテレータを取得できます。
let reptileIterator = Reptiles[Symbol.iterator]();
次に、次のようにイテレータを使用できます。
console.log(reptileIterator.next()); // {value: "Alligators", done: false} console.log(reptileIterator.next()); // {value: "Crocs", done: false} console.log(reptileIterator.next()); // {value: "Snakes", done: false} console.log(reptileIterator.next()); // {value: "Turtles", done: false} console.log(reptileIterator.next()); // {value: undefined, done: true} console.log(reptileIterator.next()); // TypeError: Cannot read property 'length' of undefined
ご覧のとおり、イテレータにはnext()
メソッドがあり、イテレータの次の値を返します。 done
の値は、最後の値が返された後、次のnext()
呼び出しの後にのみ、true
と評価されるため、反復可能全体を調べるには、常にもう1つの呼び出しがあります。 next()
イテラブルにデータがあります。 イテレータがイテレータの最後に達した後でnext()
を再度呼び出すと、TypeError
がスローされます。
まとめ
この紹介が、オブジェクトなどのデータ構造のJavaScriptの内部についてもう少し理解する上で目を見張るものであったことを願っています。 これは表面を傷つけただけです。詳細を知りたい場合は、カイルシンプソンの優れたIterablesの章をお読みください。