0
0

More than 1 year has passed since last update.

Javascriptのイテレータとジェネレーターを簡単に触ってみた

Posted at

イテレーター

イテレーターとは反復操作を行う際に使用するオブジェクトになります。イテレーターでは2つのプロパティを持つオブジェクトを返すnext()メソッドをもつことによってイテレータープロトコルを実装するオブジェクトになります。
言葉では理解しにくいと思うのでまずはコードで確認してみましょう。

JS
function genIterator() {
  // genIteratorのreturnで返すものがイテレーター
  return {
    next: function() {
      return {
        done: true/false, //ループ継続の可否
        value:  //返却されるもの
      }
    }
  }
}

コードを確認できたところでまずは簡単に0~3までを出力するイテレーターを作成してみましょう。

JS
function genIterator(max) {
  let i = 0; //iの初期化
  return {
    next: function() {
      if(i >= max) {
        return {
          done: true
          // valueは記述不要
        }
      } else {
        return {
          done: false,
          value: i++
        }
      }
    }
  }
}

const it = genIterator(4);
console.log(it.next()); //=>{done: false, value: 0}
console.log(it.next()); //=>{done: false, value: 1}
console.log(it.next()); //=>{done: false, value: 2}
console.log(it.next()); //=>{done: false, value: 3}
console.log(it.next()); //=>{done: true}

まずgenIteratorという関数を作成します。引数にはmaxを設定してその日数の値によって継続のfalse/trueをif文で分けていきます。genIteratorを実行すると期待されるdone,valueプロパティを保持したオブジェクトが返却されていることが確認できます。
これらを馬鹿正直に一つずつconsole.logで出力するのはスマートではありません。while文で記述しなおして数字だけを取り出してみましょう。

JS
function genIterator(max) {
  let i = 0; //iの初期化
  return {
    next: function() {
      if(i >= max) {
        return {
          done: true
          // valueは記述不要
        }
      } else {
        return {
          done: false,
          value: i++
        }
      }
    }
  }
}

const it = genIterator(4);
let a = it.next();
while(!a.done) {
  console.log(a.value);
  a = it.next();
}

オブジェクトを反復可能にする

オブジェクトを反復可能にするにはSymbol.iterator (このための特別な組み込みのシンボルです)という名前のメソッドをオブジェクトに追加する必要があります。

JS
function genIterator(max = 4) {
  let i = 0; //iの初期化
  return {
    next: function() {
      if(i >= max) {
        return {
          done: true
          // valueは記述不要
        }
      } else {
        return {
          done: false,
          value: i++
        }
      }
    }
  }
}

const obj = {} //空のオブジェクトを用意する。
obj[Symbol.iterator] = genIterator //objのSymbol.iteratorに先程生成したgenIteratorをセットする。このとき引数のデフォルト上限値を設定しないと無限ループになるので注意

for(let i of obj) {
  console.log(i);
}

ジェネレーター

ジェネレーターはイテレーターを生成するための特殊な関数でより簡略して記述することができます。

スクリーンショット 2021-10-30 22.30.40.png

functionに*をつけることで明示的にジェネレーター関数であることを宣言できます。ジェネレーター関数にはyeildというキーワードがありこれによりイテレーター部分を表現することができます。
またyeildではnextメソッドに続くdoneがfalseでその時のvalueの値がyeildのvalueと一致します。

スクリーンショット 2021-10-30 22.40.00.png

コードで確認してみましょう。

JS
function* foo(index) {
  while (index < 2) {
    yield index++;
  }
  return 2
}

const iterator = foo(0);

console.log(iterator.next()); //{value: 0, done: false}
console.log(iterator.next()); //{value: 1, done: false}
console.log(iterator.next()); //{value: 2, done: true}

const iterator = foo(0)とすることでジェネレーターからイテレーターを作成します。そして、イテレーターのnextメソッドで期待されるオブジェクトをconsoleで出力しています。ここで、最後にreturnで呼ばれているのでdoneがtureで返されていることが確認できます。

おまけ

おまけとしてスプレット演算子について軽く取り扱います。スプレット演算子とは反復可能や列挙可能オブジェクトの展開を行います。主に配列を展開し、新しい配列に代入するとき時などに利用します。
コードで確認してみましょう。

JS
const arry1 = [1,2,3,4,5];
const arry2 = [...arry1];
console.log(arry1); //(5) [1, 2, 3, 4, 5]
console.log(arry2); //(5) [1, 2, 3, 4, 5]
console.log(arry1 === arry2); //false

今arry1という配列を宣言しスプレット演算子(...)によって新しい配列としてarry2に代入しています。ここで注意が必要なのが、スプレット演算子で代入されたarry2はあくまで新しい配列なので等価性を見るとfalseの結果が返ってきます。
スプレット演算子を関数の仮引数に設定すると、渡ってくる引数が...に続く変数名の配列となります。こちらもコードで確認してみましょう。

JS
function sum(...args) {
  let ret = 0;
  console.log(args); //(4) [1, 2, 3, 4]
  for(let v of args) {
    console.log(v);
    ret += v;
  }
  return ret;
}

const result = sum(1,2,3,4)
console.log(result); //10

関数sum内でのargsは渡ってきた引数を配列にした形で処理されていることが確認できます。

参考

Udemy: 【JS】ガチで学びたい人のためのJavaScriptメカニズム
現代の JavaScript チュートリアル:https://ja.javascript.info/logical-operators

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0