web-dev-qa-db-ja.com

JavaScript ES6では、イテラブルとイテレーターの違いは何ですか?

イテラブルはイテレーターと同じですか、それとも異なりますか?

仕様から 、イテラブルはobjなどのオブジェクトであり、_obj[Symbol.iterator]_は関数を参照するため、呼び出されたときにオブジェクトが返されます_{value: ___, done: ___}_オブジェクトを返すことができるnextメソッドがあります:

_function foo() {
    let i = 0;
    const wah = {
        next: function() {
            if (i <= 2) return { value: (1 + 2 * i++), done: false }
            else return { value: undefined, done: true }
        }
    };
    return wah;     // wah is iterator
}

let bar = {}        // bar is iterable

bar[Symbol.iterator] = foo;

console.log([...bar]);             // [1, 3, 5]   
for (a of bar) console.log(a);     // 1 3 5 (in three lines)
_

したがって、上記のコードでは、barがイテラブルであり、wahがイテレーターであり、next()がイテレーターインターフェースです。

したがって、イテラブルとイテレータは別物です。

ただし、ジェネレータとイテレータの一般的な例を次に示します。

_function* gen1() {
    yield 1;
    yield 3;
    yield 5;
}

const iter1 = gen1();

console.log([...iter1]);                           // [1, 3, 5]
for (a of iter1) console.log(a);                   // nothing

const iter2 = gen1();
for (a of iter2) console.log(a);                   // 1 3 5 (in three lines)

console.log(iter1[Symbol.iterator]() === iter1);   // true
_

上記の場合、_gen1_はジェネレーターであり、_iter1_はイテレーターであり、iter1.next()は適切な処理を行います。しかし、_iter1[Symbol.iterator]_は、呼び出されたときに、反復子である_iter1_を返す関数を提供します。したがって、この場合、_iter1_は反復可能であり、反復子でもありますか?

さらに、_iter1_は反復可能ですが、_[1, 3, 5]_は反復可能ですが、_[...bar]_は、上記の例1とは異なります。ただし、毎回同じイテレータである自分自身を返すため、_iter1_は1回しか返されません。

つまり、反復可能なbarの場合、_[1, 3, 5]_が何回_[...bar]_の結果を__[1, 3, 5]_にできるかを言うことができます-その答えは、状況によって異なります。イテレータはイテレータと同じですか?そして答えは、それらは異なるものですが、イテラブルがそれ自体をイテレーターとして使用する場合、同じものにすることができます。あれは正しいですか?

14
nonopolarity

はい、iterablesiteratorsは異なりますが、ほとんどのイテレータ(JavaScript自体から取得するすべてのイテレータなど、 Array.prototypekeysまたはvaluesメソッドまたはジェネレーター関数のジェネレーターから) %IteratorPrototype%オブジェクト から継承し、Symbol.iteratorこのようなメソッド:

[Symbol.iterator]() {
    return this;
}

その結果、すべての標準イテレータも反復可能です。直接使用するか、for-ofループなどで使用できます(反復子ではなく反復可能であることを期待します)。

配列のkeysメソッドについて考えてみます。これは、配列のキー(そのインデックス、数値)を訪問する配列反復子を返します。 iteratorを返すことに注意してください。しかし、それの一般的な使用法は次のとおりです。

for (const index of someArray.keys()) {
    // ...
}

for-ofiteratorではなく、iterableを取るので、なぜそれが機能するのですか?

イテレータも反復可能であるため機能します。 Symbol.iteratorは単にthisを返します。

これが私の本の第6章で使用する例です。すべてのエントリをループしたいが最初のエントリはスキップしたいが、sliceを使用してサブセットをスライスしたくない場合は、イテレータを取得できます、最初の値を読み取り、次にfor-ofループに渡します。

const a = ["one", "two", "three", "four"];
const it = a[Symbol.iterator]();
// Skip the first one
it.next();
// Loop through the rest
for (const value of it) {
    console.log(value);
}

これはすべて標準イテレータであることに注意してください。時々人々はこのように手動でコード化されたイテレータの例を示します:

function range(start, end) {
    let value = start;
    let inc = start < end ? 1 : -1;
    return {
        next() {
            const done = value == end;
            const result = {done, value};
            if (!done) {
                value += inc;
            }
            return result;
        }
    };
}

// Works when used directly
const it = range(1, 5);
let result;
while (!(result = it.next()).done) {
    console.log(result.value);
}

// Fails when an iterable is expected
try {
    for (const value of range(1, 5)) {
        console.log(value);
    }
} catch (e) {
    console.error(e.message);
}

rangeによって返されるイテレータにはイテラブルがあるnotがあるため、for-ofで使用しようとすると失敗します。

反復可能にするには、次のいずれかを行う必要があります。

  1. 上記の回答の先頭にSymbol.iteratorメソッドを追加するか、
  2. そのメソッドをすでに持っている%IteratorPrototype%から継承する

悲しいことに、TC39は%IteratorPrototype%オブジェクトを直接取得する方法を提供しないことを決定しました。間接的な方法(配列からイテレータを取得してから、そのプロトタイプ(%IteratorPrototype%として定義されている)を取得する)がありますが、それは面倒です。

とにかく、そのように手動でイテレータを書く必要はありません。返されるジェネレータは反復可能であるため、ジェネレータ関数を使用するだけです。

function* range(start, end) {
    let value = start;
    let inc = start < end ? 1 : -1;
    while (value !== end) {
        yield value;
        value += inc;
    }
}

// Works when used directly
const it = range(1, 5);
let result;
while (!(result = it.next()).done) {
    console.log(result.value);
}

// Also works when an iterable is expected
for (const value of range(1, 5)) {
    console.log(value);
}

対照的に、すべての反復可能オブジェクトが反復子であるとは限りません。配列は反復可能ですが、反復子ではありません。文字列、マップ、セットも同様です。

10
T.J. Crowder

用語のより正確な定義がいくつかあり、これらはより決定的な答えです。

ES6仕様 および [〜#〜] mdn [〜#〜] によると:

持っているとき

_function* foo() {   // note the "*"
    yield 1;
    yield 3;
    yield 5;
}
_

fooはジェネレーターfunctionと呼ばれます。そして私たちが持っているとき

_let bar = foo();
_

barはジェネレーターオブジェクトです。そして ジェネレーターオブジェクトは反復可能なプロトコルと反復子プロトコルの両方に準拠しています

単純なバージョンは、単なる.next()メソッドである反復子インターフェースです。

反復可能なプロトコルは次のとおりです。オブジェクトobjの場合、_obj[Symbol.iterator]_は「反復子プロトコルに準拠した、オブジェクトを返すゼロ引数関数」を提供します。

MDNリンクのタイトル によって、ジェネレーターオブジェクトを「ジェネレーター」と呼ぶこともできるようです。

Nicolas Zakasの著書ECMAScript 6の理解 では、おそらく「ジェネレーター関数」を「ジェネレーター」、「ジェネレーターオブジェクト」を「イテレーター」と大まかに呼びました。要点は、これらは実際には両方とも「ジェネレータ」に関連している-1つはジェネレータ関数で、もう1つはジェネレータオブジェクトまたはジェネレータです。ジェネレーターオブジェクトは、反復可能なプロトコルと反復子プロトコルの両方に準拠しています。

iteratorプロトコルに準拠するオブジェクトのみの場合、あなたはできません _[...iter]_またはfor (a of iter)を使用します。 iterableプロトコルに準拠するオブジェクトである必要があります。

さらに、 新しいIteratorクラス、将来のJavaScript仕様ではまだドラフト段階です もあります。現在の配列インターフェイスのforEachmapreduceなどのメソッドを含む、より大きなインターフェイスと、およびtakeなどの新しいインターフェイスがあります。およびdrop。現在のイテレータは、nextインターフェースのみを持つオブジェクトを参照します。

元の質問に答える:イテレータとイテラブルの違いは何ですか、答えは次のとおりです:イテレータはインターフェース.next()を持つオブジェクトであり、イテラブルはオブジェクトobjなどですその_obj[Symbol.iterator]_は、呼び出されたときに反復子を返す引数のない関数を提供できます。

そして、ジェネレーターは、それに加えて、反復可能であり反復子でもあります。

0
nonopolarity