JavaScriptの反復プロトコル


あなたがJavaScript開発者としてどのレベルであろうとも、あなたはそれを意識していないかもしれませんが、今までイテレータとiterablesを使用しています.しかし、正確に彼らは何ですか?

反復


実装する各オブジェクト@@iterator 方法[Symbol.iterator] ) はiterableです.これは、オブジェクトがどのような振る舞いをするかを定義しますfor...of 声明)組み込みのようなiterableがありますString , Map , Set , Array , TypedArray と他のあなた自身もビルドすることができます.
let runningStats = {
  Mike: 6,
  Emma: 9,
  Billy: 11,
};

// creates an iterable which will return custom objects
runningStats[Symbol.iterator] = () => {
  let i = 0;
  const pairs = Object.entries(runningStats);

  return {
    next: () => {
      // signal that iterating has been finished
      if (i === pairs.length) {
        return { value: undefined, done: true };
      }

      let currentPair = pairs[i++];

      return {
        value: { name: currentPair[0], kilometers: currentPair[1] },
        done: false,
      };
    }
  }
};

for (const personStats of runningStats) {
  console.log(personStats);
}
以下の出力を行います.
{ "name": "Mike", "kilometers": 6 }
{ "name": "Emma", "kilometers": 9 }
{ "name": "Billy", "kilometers": 11 }
したがって、iterableが上記のiterableなプロトコルに従う各々のオブジェクトであると言うことができる.いくつかの種類のインターフェイスとしてプロトコルを見ることができます.例えば、文字列と集合が既にiterableであるので、定義せずにそれらを繰り返すことができます[Symbol.iterator] メソッド:
const str = "word";

for (const char of str) {
  console.log(char);
}

const set = new Set([1, 1, 2, 2, 3, 3]);

for (const number of set) {
  console.log(number);
}
出力:
w
o
r
d
1
2
3
楽しい事実:Set そして、他のいろいろなiterablesはiterableを引数として受け入れます.あなたはそれを見ることができるでしょうSet 上記の例では、文字列またはマップを渡します.時々制限があります.Map 例えば、iterableのような配列のみを受け入れます.

イテレータ


上記のiterableの例を詳しく見てみると、next() メソッド.そのオブジェクトはイテレータです.もちろん、next() メソッドは反復子です.あなたのメソッドは、少なくとも次の2つのプロパティを含むオブジェクトを返す必要がありますvalue ( JavaScriptの値)done ( boolean )そうしないことは、結果としてTypeError メソッドが呼ばれるとき.これはイテレータプロトコルと呼ばれます.
我々が上で作ったiterableからイテレータを得る方法を見ましょう.
const iterator = runningStats[Symbol.iterator]();

console.log(iterator.next()); // { value: { "name": "Mike", "kilometers": 6 }, done: false }
console.log(iterator.next()); // { value: { "name": "Emma", "kilometers": 9 }, done: false }
console.log(iterator.next()); // { value: { "name": "Billy", "kilometers": 11 }, done: false }
console.log(iterator.next()); // { value: undefined, done: true }

// Any subsequent calls of the next() method will return the same result
console.log(iterator.next()); // { value: undefined, done: true } 
イテレータを直接このように使うのは、iterableをループするときに特定の要素をスキップしたいときに便利です.
const food = ["carrot", "apple", "banana", "plum", "peach"];

const iterator = food[Symbol.iterator]();
iterator.next(); // skip the first one

for (const fruit of iterator) {
  console.log(fruit);
} 
これにより、以下の出力が得られます.
apple
banana
plum
peach

無限反復子


イテレータの要素数に制限を課す必要はありません.時々、複数回使うことができる無限の反復子を持つのは役に立ちます.
const infiniteList = (start) => {
  let value = start;

  return {
    next: () => ({ value: value++, done: false }),
  };
}

const iterator = infiniteList(6);

for (const _ of new Array(100)) {
  iterator.next();
}

console.log(iterator.next().value); // 106
それでは、使いましょうfor...of このイテレータ上のループへのステートメント-最後に、それはよりエレガントですね?
const infiniteList = (start) => {
  let value = start;

  return {
    next: () => ({ value: value++, done: false }),
  };
}

const iterator = infiniteList(6);

for (const element of iterator) {
  console.log(element);
}
そして実行します.

おっ!エラーになったようですね.と言うiterator is not iterable . 何が起こっているのか

イテレータとiterableの違い


私たちはその例からfood イテレータが呼び出し元で使用可能な配列next() 方法と内部for...of 文.では、イテレータがなぜ動作しないのか?まあ、すべてのイテレータはiterableではないからです.
iterableプロトコルが我々が必要とすると言うのを思い出してください[Symbol.iterator] 我々のオブジェクトに対するメソッドは反復可能であるか?これは標準のイテレータが持っているものです.
[Symbol.iterator]() {
  return this;
}
とても便利ですね.つまり、イテレータにイテレータを追加するだけでよい.ああ、そして、私たちがそれをしている間、我々のタブが5月にdogecoinのようにクラッシュするのを避けるためにイテレータを有限に変えるようにしましょう.
// use non-arrow function syntax so that this won't return value of the outer scope
const finiteList = function(start, end) {
  let value = start;

  return {
    next: () => {
      if (value === end) {
        return { value: undefined, done: true };
      }

      return { value: value++, done: false };
    },
    [Symbol.iterator]() {
      return this;
    }
  };
}

const iterator = finiteList(6, 16);

for (const element of iterator) {
  console.log(element);
}
出力:
6
7
8
9
10
11
12
13
14
15
ベール!iteratorも作りました.
楽しい事実:そこから継承することによってイテレータiterableを作るもう一つの方法が、あります%IteratorPrototype% object , しかし、この方法は扱いにくい.
ありがたいことに、iterableイテレータを作成するより簡単な方法もあります.

発電機


ES 6は特殊な反復子を返す関数であるGenerator . Generator は、イテレータとiterableプロトコルの両方に固執します.あなたは簡単に自分の名前の前にアスタックス(*)記号でそれらを認識します.上からの有限および無限のリスト機能がどのように発電機機能として書かれるとき、どのように見えるか見てみましょう.
function* infiniteList(start) {
  let value = start;

  while (true) {
    yield value++;
  }
}

const infiniteIterator = infiniteList(6);

console.log(iterator.next().value); // 6
console.log(iterator.next().value); // 7
console.log(iterator.next().value); // 8
console.log(iterator.next().value); // 9

function* finiteList(start, end) {
  let value = start;
  while (value < end) {
    yield value++;
  }
  return value;
}

const finiteIterator = finiteList(6, 16);

// skip 4 steps
for (const _ of new Array(4)) {
  finiteIterator.next();
}

for (const num of finiteIterator) {
  console.log(num);
}
何が起こるかのステップバイステップの説明;
  • ジェネレータ関数を呼び出し、Generator オブジェクト
  • 呼び出しnext() メソッドはyield 発生する.
  • yield 返される値を定義します.一度yield が指定された場合、その時点での実行が停止し、全ての変数の割り当てが将来の呼び出しに保存される.
  • その後next() コールは最後の到達点から実行を続けます.
  • return ジェネレータ関数から、それがイテレータの最終的な値であると言います.
  • 別の、もっと簡単な例をあげましょう
    function* lilIterator() {
      let value = 0;
    
      yield value++;
      yield value++;
      yield value++;
    
      return value;
    }
    
    const iterator = lilIterator();
    
    // next() is called, execution is stopped at the first yield which returns 0, value is now 1
    console.log(lilIterator.next().value);
    
    // next() is called, execution is stopped at the second yield which returns 1, value is now 2
    console.log(lilIterator.next().value);
    
    // next() is called, execution is stopped at the third yield which returns 2, value is now 3
    console.log(lilIterator.next().value);
    
    // next() is called, at this point generator function has return which means that iterator will be finished with value 3
    console.log(lilIterator.next().value);
    
    // any subsequent next() calls will return { value: undefined, done: true }, so output here would be undefined
    console.log(lilIterator.next().value);
    
    我々が加えなかったならばreturn ジェネレータ関数の終了時のステートメントでは、イテレータはyield . そして、無限のリストの我々の例では、我々はそうしましたyield 内部while(true) {} ループは、無限に値を返すイテレータで終了しました.

    結論


    私はこの記事が反復プロトコルのより良い理解を得るのを助けたことを望みます.私が言及しなかったいくつかのものがありますyield* 彼らは記事の多くのポイントを追加しないため、別のジェネレータ機能を委任するため.私はあなた自身で実験し、あなたの空き時間にこれらの概念を練習することをお勧めします.私はいくつかの小さな例を示しました、しかし、イテレータはそれよりずっと強力です.
    要点を要約しましょう

  • iterableは反復可能なプロトコルを固守するオブジェクトです[Symbol.iterator] プロパティが反復子を返すメソッドです.

  • イテレータはイテレータプロトコルを遵守するオブジェクトですnext() 少なくともオブジェクトを返すメソッドvalue and done プロパティ.
  • イテレータはiterableである必要はありません.
  • iterableとiteratorプロトコルの両方を対象とするオブジェクトを作成するために、ジェネレータ関数を使用することができます.