JavaScriptは、for…ofやspread演算子などの制御構造で配列などのオブジェクトを使用できるプロトコルをサポートしています。 ... データを順番にループします。 これはイテラブルと呼ばれ、この機能をサポートするデータ構造はイテラブルと呼ばれます。 JavaScriptは、最初から反復可能なプロパティを持つマップ、配列、およびセットを提供しますが、プレーンオブジェクトにはデフォルトでこれがありません。

Iterableは、他のデータコンシューマーがその要素に順次アクセスできるようにするメカニズムを提供するデータ構造です。 データを1つずつアンロードする自己パッケージ化されたデータ構造を想像してみてください。 for...of ループ。

反復可能プロトコルの概念は、 iterable (データ構造自体)と iterator (反復可能上を移動するポインターの一種)に分割できます。 たとえば、配列がで使用されている場合の配列を考えてみましょう。 for...of ループ、iterableプロパティが呼び出され、 iterator. この反復可能なプロパティは、次のように名前空間が付けられます。 Symbol.iterator そして、それが返すオブジェクトは、すべてのループ制御構造によって共有される共通のインターフェースで使用できます。

ある意味で、 Symbol.iterator データ構造がループに配置されるたびにイテレータを生成するイテレータファクトリと比較できます。

イテレータがデータ構造上を移動し、要素を順番に提供すると、イテレータによって返されるオブジェクトには、 valuedone 財産。

値は、イテレータが指す現在のデータ値を示し、 done は、イテレータがデータ構造の最後の要素に到達したかどうかを示すブール値です。

これ {value, done} ループなどの構造によって消費されます。 では、イテレータメソッドはどのようにして次のオブジェクトを呼び出すのでしょうか。 を使って next() Symbol.iterator()メソッド内で定義されたメソッド。

この時点で使用できるイテレータプロパティのより適切な定義は、コレクションの要素に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()を呼び出します。

上記と同じ例を考えると、次の式からイテレータを取得できます。 Reptiles そのを呼び出すことによって Symbol.iterator このような:

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() iterableの次の値を返すメソッド。 の値 done にのみ評価します true 次々と next() 最後の値が返されたら呼び出します。したがって、イテレータ全体を調べるには、常にもう1つの呼び出しがあります。 next() イテレータにデータがあるよりも。 呼び出し next() イテレータがイテレータの最後に達した後も、次のようになります。 TypeError 投げられる。

まとめ

この紹介が、オブジェクトなどのデータ構造のJavaScriptの内部についてもう少し理解する上で目を見張るものであったことを願っています。 これは表面を傷つけただけです。詳細を知りたい場合は、カイルシンプソンの優れたIterablesの章をお読みください。