イテラブルはイテレーターと同じですか、それとも異なりますか?
仕様から 、イテラブルは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]
_にできるかを言うことができます-その答えは、状況によって異なります。イテレータはイテレータと同じですか?そして答えは、それらは異なるものですが、イテラブルがそれ自体をイテレーターとして使用する場合、同じものにすることができます。あれは正しいですか?
はい、iterablesとiteratorsは異なりますが、ほとんどのイテレータ(JavaScript自体から取得するすべてのイテレータなど、 Array.prototype
のkeys
またはvalues
メソッドまたはジェネレーター関数のジェネレーターから) %IteratorPrototype%オブジェクト から継承し、Symbol.iterator
このようなメソッド:
[Symbol.iterator]() {
return this;
}
その結果、すべての標準イテレータも反復可能です。直接使用するか、for-of
ループなどで使用できます(反復子ではなく反復可能であることを期待します)。
配列のkeys
メソッドについて考えてみます。これは、配列のキー(そのインデックス、数値)を訪問する配列反復子を返します。 iteratorを返すことに注意してください。しかし、それの一般的な使用法は次のとおりです。
for (const index of someArray.keys()) {
// ...
}
for-of
はiteratorではなく、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
で使用しようとすると失敗します。
反復可能にするには、次のいずれかを行う必要があります。
Symbol.iterator
メソッドを追加するか、悲しいことに、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);
}
対照的に、すべての反復可能オブジェクトが反復子であるとは限りません。配列は反復可能ですが、反復子ではありません。文字列、マップ、セットも同様です。
用語のより正確な定義がいくつかあり、これらはより決定的な答えです。
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仕様ではまだドラフト段階です もあります。現在の配列インターフェイスのforEach
、map
、reduce
などのメソッドを含む、より大きなインターフェイスと、およびtake
などの新しいインターフェイスがあります。およびdrop
。現在のイテレータは、next
インターフェースのみを持つオブジェクトを参照します。
元の質問に答える:イテレータとイテラブルの違いは何ですか、答えは次のとおりです:イテレータはインターフェース.next()
を持つオブジェクトであり、イテラブルはオブジェクトobj
などですその_obj[Symbol.iterator]
_は、呼び出されたときに反復子を返す引数のない関数を提供できます。
そして、ジェネレーターは、それに加えて、反復可能であり反復子でもあります。