JavaScript ジェネレーター

目次
  1. イテレーターについて
  2. ジェネレーター
    1. オブジェクトを反復可能にする
    2. yieldのループ
    3. yieldとnext

イテレーターについて

「イテレーター」は反復操作のためのオブジェクトです
2つのプロパティ(value・done)を返すnext()メソッドを持ちます
valueプロパティ:次の値
doneプロパティ:最後の値が使われたら「true 」となります

const iterator = {
  next: function(){
   return { 
    done: false, //true
    value: 値,
   }
}; 

反復可能(iterable)オブジェクトとは
[Symbol.iterator]メソッドを実行するとイテレーターを返すオブジェクトのことです
*ビルドインオブジェクトでは「StringArrayMapSetNodeListなど」が反復可能(iterable)オブジェクトです

//objは反復可能(iterable)オブジェクト
const obj = {
[Symbol.iterator]:function(){
    return iterator;
}

「for of」ループ

  • 反復可能(iterable)オブジェクトに対してループ処理をします(値が出力されます)
    [Symbol.iterator]で実装されています

ジェネレーター

ジェネレーター関数を使うとイテレーターをシンプルに作成できます
ジェネレーター関数と呼び出し側とで双方向のコミュニケーションが可能です
*アロー関数は使用できません

ジェネレーター関数は通常の関数と異なり、呼ばれたても即コードは実行されません

  • ジェネレーター関数を呼ぶとまずはイテレーターが返されます
  • イテレーターのnext()メソッドを呼び出すたびに実行が進みます
  • ジェネレータ関数で「yield」を使用して実行を「一時停止」し「停止した場所」を記憶
  • next() メソッドで「停止した場所」から実行されます

*イテレーターのnext() メソッドを実行するとvalueプロパティとdoneプロパティを持つオブジェクトを返します
*yieldキーワードを検出するまで実行し、終了以降のnext()の呼び出しでは「valueはundefined・doneはture」になります

function* num() {
 yield 1; 
 yield 2; 
 yield 3; 
}
//ジェネレーター関数を実行
const genIterator = num(); 
//next()メソッドを呼び出して実行
console.log(genIterator.next())
console.log(genIterator.next()) 
console.log(genIterator.next()) 
console.log(genIterator.next()) //ジェネレーターが終了以降のnext()の呼び出し

// { value: 1, done: false }
// { value: 2, done: false }
// { value: 3, done: false }
// { value: undefined, done: true }

ジェネレーターで 「return 」文が実行されるとジェネレーターが終了します
値が返された場合valueプロパティとして設定されます

function* num() {
 yield 1; 
 return '終わり!'
}
const genIterator = num(); 
console.log(genIterator.next()) 
console.log(genIterator.next()) 

//{value: 1, done: false}
//{value: '終わり!', done: true}

ジェネレーター関数を実行して返されたイテレーターを使いfor ofループします

//ジェネレーター関数 numを作成
function* num() {
 yield 1; 
 yield 2; 
 yield 3; 
}
//ジェネレーター関数を実行して返されたイテレーターを使ってfor ofループ
for(i of num()){
  console.log(i)
}
//1
//2
//3

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

オブジェクトは反復可能(iterable)オブジェクトではないので通常「for of」ループはできません

ジェネレーター関数の引数にオブジェクトを渡し、ジェネレーター関数を呼び出してイテレーターを受け取り「for of」ループします

 const user = {  
   id: 1,
   name: "花子",
   email: "hoge@april.biz",
 }
function* userIterator(arg) {
 yield arg.id
 yield arg.name
 yield arg.email
}
//userIterator()を実行=イテレーターが返されます)
for(i of userIterator(user)){
  console.log(i)
}
//1
//花子
//hoge@april.biz

別のジェネレーターに委任する(yield*
もし「yield*」があればそのジェネレーターに委任します
下記のコードではオブジェクトを合体させて一度に「for of」ループしています

const obj1={
  name:'花子',
  age: 20
}
const obj2={
  email: 'hoge@april.biz',
  phone: '090-000-000'
}
//obj1のイテレーター
function* gen1(arg) {
  yield arg.name
  yield arg.age
}
//obj2とobj1を委任されたイテレーター
function* gen2(arg) {
  yield* gen1(obj1); 
  yield arg.email
  yield arg.phone 
}
for(i of gen2(obj2)){
  console.log(i)
}
//花子
//20
//hoge@april.biz
//090-000-000

[Symbol.iterator]を実装して反復可能(iterable)オブジェクトにします
よりシンプルになります
*オブジェクト自身なのでthisキーワードを使います
*配列扱いでスプレッド演算子が使えます

 const user = {  
   id: 1,
   name: "花子",
   email: "hoge@april.biz",
   [Symbol.iterator]:function* () {
     yield this.id
     yield this.name
     yield this.email
   }
 }
for(i of user){
  console.log(i)
}
//1
//花子
//hoge@april.biz

console.log([...user]) 
// [1, '花子', 'hoge@april.biz']
const obj1={
  name:'花子',
  age: 20,
 [Symbol.iterator]:function* (){
     yield this.name
     yield this.age
   }
}
const obj2={
  email: 'hoge@april.biz',
  phone: '090-000-000',
 [Symbol.iterator]:function* (){
     yield* obj1
     yield this.email
     yield this.phone
   }
}

for(i of obj2){
  console.log(i)
}
//花子
//20
//hoge@april.biz
//090-000-000

yieldのループ

ジェネレーター関数内でのループはforまたはwhileを使います

function* step(min, max, step) {
  for (let i = min; i <= max; i += step) {
    yield i;
  }
}
//または
function* step(min, max, step) { 
  let i = min 
  while (i <= max) {
   yield i 
   i += step
  }
}

for(let i of step(1, 10, 2)){
  console.log(i)
}
// 1
// 3
// 5
// 7
// 9
function* obj() {
  for (let i = 0; i <= 5; i++) {
    const data = {
      id: i,
    }
    yield data
  }
}
for (const data of obj()) {
 console.log(data)
}
// { id: 0 }
// { id: 1 }
// { id: 2 }
// { id: 3 }
// { id: 4 }
// { id: 5 }

*「yield*」は「もしあれば」なのでTree構造になっている反復可能オブジェクト(Array ・NodeListなど)の再帰に応用できると思います^^;

class Parent {
  constructor(text, children){
    this.text = text;
    this.children = children;
  }
//クラスのメソッドに[Symbol.iterator]
  *[Symbol.iterator]() {
  yield this.text;
  for (let child of this.children) {
  //もしあれば
    yield* child
   }
  }
}
const children = [
    new Parent('aa', []),
    new Parent('bb',
        [new Parent('ccc', [ new Parent('dddd', [])]) ],
    ),
];
const tree = new Parent('a', children)
console.dir(tree, { depth: null });
// Parent {
//   text: 'a',
//   children: [
//     Parent { text: 'aa', children: [] },
//     Parent {
//       text: 'bb',
//       children: [
//         Parent {
//           text: 'ccc',
//           children: [ Parent { text: 'dddd', children: [] } ]
//         }
//       ]
//     }
//   ]
// }
for(let v of tree){
  console.log(v)
}
// a
// aa
// bb
// ccc
// dddd

yieldとnext

ジェネレーター関数と呼び出し側との双方向のコミュニケーション

next()再開時の引数はyield式の結果として取得します
*最初の呼び出しのnext() は引数なしです

function* genFunc( args ){
	console.log(args); //引数
	const result1 = yield "a";
	console.log(result1); // "あ"
	const result2 = yield "b";
	console.log(result2); // "い"
	const result3 = yield "c";
	console.log(result3); // "う"
return [result1, result2, result3]
}
const genIt = genFunc('引数');

const v1 = genIt.next();
console.log(v1); // { value: 'a', done: false }
const v2 = genIt.next("あ");
console.log(v2); // { value: 'b', done: false }
const v3 = genIt.next("い");
console.log(v3); // { value: 'c', done: false }
const v4 = genIt.next("う");
console.log(v4); // { value: [ 'あ', 'い', 'う' ], done: true }

next()の引数をyieldの結果として取得して、それをもとに条件分岐

function* count() {
  let index = 1
  while (true) {
//next()引数をyield式の結果として取得
  const step = yield index
  if(step != null){
    index += step
   } else {
    index ++
   }
  }
}
const countGen = count()
console.log(countGen.next())
console.log(countGen.next(10))
console.log(countGen.next(100))
console.log(countGen.next())
//
// { value: 1, done: false }
// { value: 11, done: false }
// { value: 111, done: false }
// { value: 112, done: false }

return(値)メソッド:値を返してジェネレーター自身の処理を終了します

console.log(countGen.next())
console.log(countGen.return(1))
// { value: 1, done: false }
// { value: 1, done: true }