12 Iteration - iruma-tea/dokushujs GitHub Wiki

12. 反復処理

ES6で追加された、for...of文の挙動に関わるイテレータ、ジェネレータを知ることで、
より柔軟な繰り返し処理の記述方法を知る。

12.1 イテレータ

イテレータを理解することで、for...of文などを使った反復処理を自由に定義できるようなる。

12.1.1 イテレータの記法

イテレータ とは、for...of文などを使ったオブジェクト(Map、Set、または独自定義したオブジェクトなど)の
反復処理の挙動を定義するときに使うオブジェクト です。

const iterator = {
    next() {
        return {
            done: [true | false],
            value: 
        }
    }
}

() [true | false]は、trueまたはfalseを設定するか、記述を省略できることを意味する。

イテレータには、次のような特徴がある。

  • イテレータ(オブジェクト)には、必ずnextメソッドが格納されている必要がある。
  • nextメソッドの戻り値は、value、doneを保持するオブジェクトである必要がある。
  • doneは、反復の終了をtrueまたはfalseでnextメソッドの実行元に知らせる。trueの場合、反復処理が終了したことを表す。
  • valueには、反復ごとに取得した値を設定する。

◆ 数値を返すイテレータ

// イテレータを生成する関数
function genIterator(max) {
    let value = 0;

    return {
        next() {
            if (value < max) {
                return {
                    done: false,
                    value: value++
                }
            } else {
                return {
                    done: true
                }
            }
        }
    };
}

const iterator = getIterator(3);

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

◆ 文字列を返すイテレータ

function alphabetIterator(start = "a", end = "z") {
    if (start > end) {
        throw "開始文字は終了文字より前のアルファベットを選んでください。";
    }

    const ALPHABET_ARRAY = "abcdefghigklmnopqrstuvwxyz".split("");

    const startIndex = ALPHABET_ARRAY.indeOf(start);
    const endIndex = ALPHABET_ARRAY.indeOf(end);
    const targetAlphabet = ALPHABET_ARRAY.slice(startIndex, endIndex + 1);

    return {
        next() {
            const alphabet = targetAlphabet.shift();
            return {
                value: alphabet,
                done: alphabet ? false : true,
            }
        }
    }
}

const it = alphabetIterator("c", "e");
let nextValue = it.next();
while(!nextValue.done) {
    console.log(nextValue);
    nextValue = it.next();
}
> {done:false, value: "c"}
> {done:false, value: "d"}
> {done:false, value: "e"}

12.1.2 反復可能なオブジェクト

反復可能なオブジェクトとは、イテレータを保持しているオブジェクトのこと
Map、Set、Array、あるいはStringなどの一部のオブジェクトはイテレータを内部に保持している。
そのため、for...of文などを使った反復処理に使用できる。
一方、オブジェクトリテラル{ }によって生成されるオブジェクトは、イテレータを内部に保持しない。そのため、オブジェクト{ }をfor...of文で
使用できない。反復不可のオブジェクトでも、イテレータを返す関数を特定のプロパティ(Symbol.iterator)に設定すれば、
反復可能オブジェクトにすることが可能。

12.1.3 オブジェクトにイテレータを設定する

反復可能なオブジェクトを作成するには、イテレータを返す関数をオブジェクトのSymbol.iteratorプロパティに対して設定する。

・オブジェクトに直接設定する場合
const iterableObject = {
    [Symbol.iterator]: function() {
        return イテレータ
    }
};

// ES6のメソッド省略記法
const iterableObjet = {
    [Symbol.iterator]() {
        return イテレータ
    }
};

・コンストラクタ関数に設定する場合(Arrayの場合)
Array.prototype[Symbol.iteraotr] = function() {
    return イテレータ
}

・クラスに設定する場合
class IterableClass {
    [Symbol.iterator]() {
        return イテレータ
    }
}

◆ 反復オブジェクトの作成

const iterableObject = {
    [Symbol.iterator]() {
        let value = 0;

        return {
            next() {
                if (value > 2) {
                    return { done: true };
                } else {
                    return {
                        done: false,
                        value: value++,
                    };
                }
            }
        }
    }
};

for (const value of iterableObject) {
    console.log(value);
}
> 0
> 1
> 2

◆ コンストラクタにイテレータを設定

function doubleIterator() {
    let index = 0;
    let arry = this;

    return {
        next() {
            if (index < arry.length) {
                return {
                    done: false,
                    value: arry[index++] * 2
                }
            } else {
                return {
                    done: true;
                }
            }
        }
    }
}

// イテレータ登録前の確認
for (let item of [1, 2, 3]) {
    console.log(item);
}
> 1
> 2
> 3

Array.prototype[Symbol.iterator] = doubleIterator;

for (let item of [1, 2, 3]) {
    console.log(item);
}
> 2
> 4
> 6

12.2 ジェネレータ

ジェネレータ(ジェネレータ関数) とは、イテレータと同様の機能を持つGeneratorオブジェクトを作成するための関数です。
ジェネレータを使うとイテレータと同様の反復処理をより簡潔に記述できる。

12.2.1 ジェネレータ関数の記法

ジェネレータ関数は、通常の関数宣言と異なり、ジェネレータ関数専用の表記法を使用する。

[構文] ジェネレータ関数の記法
・ジェネレータ関数の宣言
function* ジェネレータ関数() {
    yield ;
    return ;
}

・ジェネレータ関数の関数式
let ジェネレータ関数 = function* () {
    yield ;
    return ;
}

・ジェネレータ関数の実行
let Generatorオブジェクト = ジェネレータ関数();

function* : ジェネレータ関数を宣言するには、関数キーワード(function)の後ろに(*)を記述する。    
yield : ジェネレータ関数内のyield文は、イテレータのnextメソッドが呼び出されたときに{done:false, value:} に一致するオブジェクトを返す。
return : ジェネレータ関数内のreturn文は、イテレータのnextメソッドが呼び出されたときに{done: true, value:}に一致するオブジェクトを返す。
・数値の1~3を取得するジェネレータ
function* gen1to3() {
    let index = 1;
    yield index;
    index++;
    yield index;
    index++;
    return index;
}

const generator = gen1to3();
console.log(generator.next());
> {value: 1, done: false}
console.log(generator.next());
> {value: 2, done: false}
console.log(generator.next());
> {value: 3, done: true}

 1つずつインクリメントした値を返すジェネレータ
function* genIterator(max) {
    let value = 0;
    while(value < max) {
        yield value++;
    }
}

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

12.2.2 反復可能オブジェクトの作成

ジェネレータ関数をオブジェクトのSymbol.iteratorプロパティに設定すれば、反復可能オブジェクトを作成することができる。

[構文] ジェネレータを使った反復可能オブジェクトの記法

・オブジェクトに直接設定する場合
const iterableObject = {
    [Symbol.iterator]: function* () { }
};

・ES6のメソッドの省略記法
const iterableObject = {
    *[Symbol.iterator]() { }
};

・コンストラクタ関数に設定する場合(Arrayの場合)
Array.prototype[Symbol.iterator] = function* () { }

・クラスに設定する場合
class IterableClass {
    *[Symbol.iterator]() { }
}
オブジェクトのプロパティをすべて列挙するIterableクラスを作成

class Iterable {
    *[Symbol.iterator]() {
        for (let key in this) {
            yield [key, this[key]];
        }
    }
}

const fruits = new Iterable();
fruits.apple = "リンゴ";
fruits.banana = "バナナ";
for (const row of fruits) {
    console.log(row[0], row[1]);
}
> apple リンゴ
> banana バナナ

Iterableクラスを継承して反復可能オブジェクトの作成

class Person extends Iterable {
    constructor(name, age, gender) {
        super();
        this.name = name;
        this.age = age;
        this.gender = gender;
    }
}

const taro = new Person("太郎", 18, "男");
for (const row of taro) {
    console.log(row[0], row[1]);
}
> name 太郎
> age 18
> gender 


Generatorオブジェクトは反復可能なオブジェクトでもある
function* fruits() {
    yield "バナナ";
    yield "リンゴ";
    yield "メロン";
}

const iterableObject = fruits();
for (const fruit of iterableObject) {
    console.log(fruit);
}
> バナナ
> リンゴ
> メロン

12.3 スプレッド演算子

ES6で追加された**スプレッド演算子(...)**は、オブジェクトや配列の要素を展開したり、まとめたりするための演算子。
スプレッド演算子を使うことで次のような実装を行うことができる。

  • 関数実行時に、配列の要素の値を複数の引数に展開して設定する。
  • 関数実行時に、複数の引数をオブジェクトや配列の要素としてまとめる。
  • 配列やオブジェクトの複製(コピー)や結合を行う。
[構文] スプレッド演算子の記法
 関数宣言の使用
function 関数(...args) { } // 渡された引数を配列にまとめる。
function 関数(arg1, arg2, ...args3) { } // arg1, arg2以外を配列にまとめる

 関数実行時の使用
const params = [arg1, arg2];
function 関数(arg1, arg2) { }
関数(...params); // 配列を展開してパラメータとして渡す。

 配列の複製(コピー)に使用
const 複製された配列 = [...元の配列];

 配列の作成
const 結合された配列 = [...配列1, ...配列2]; // 配列の結合
const 配列 = ["要素1", ...配列1, "要素2"];  // 配列の任意の場所で使用可能

 オブジェクトでの使用(ES2018)
const 複製されたオブジェクト = {...元のオブジェクト}; // オブジェクトの複製
const 結合されたオブジェクト = {...オブジェクト1, ...オブジェクト2}; // オブジェクトの結合できる
const オブジェクト = { "プロパティ1": "値1", ...元のオブジェクト, "プロパティ2": "値2"}; // 任意の位置に挿入できる

12.3.1 関数宣言時の使用

[構文] 可変長引数の記法
function([arg1, arg2,]...args) { }

arg1,arg2: 任意の固定長引数を設定できる。このとき、arg1,arg2には1番目、2番目に渡された実引数の値がそれぞれ渡される。
arg1,arg2に渡された実引数は、argsの配列には含まれない。

args: arg1,arg2で受け取れなかった残りの引数が配列となったargsに渡される。スプレッド演算子によって処理される可変長引数は、
残余引数とも呼ばれる。


function fn(...args) {
    console.log(args);
}

fn(1, "こんにちは", false);
> [1, "こんにちは", false]

・残余引数で引数の合計値を算出する関数を作成
function sum(...vals) {
    let returnValue = 0;

    for (const val of vals) {
        returnValue += val;
    }

    return returnValue;
}

console.log(sum(1, 2));
> 3
console.log(sum(1, 2, 3));
> 6
console.log(sum(1, 2, 3, 4));
> 10

12.3.2 関数実行時の使用

関数の引数として渡す配列にスプレッド演算子を付与すると、配列の中身が展開されて引数として、
関数に渡されます。

 Math.maxで与えられた引数の最大値を確認
const maxValue = Math.max(10, 5, 18, 9);
console.log(maxValue);
> 18

 関数実行時のスプレッド演算子の利用
const numArray = [10, 5, 18, 9];
const maxValue = Math.max(...numArray);
console.log(maxValue);
> 18

12.3.3 配列の複製に使用

スプレッド演算子を使って、配列を複製した場合には、配列の1階層目の値が、新しい配列の要素として複製(コピー)される
ことになる。そのため、複製した配列の値を変更しても、元の配列には影響がない。

const original = ["元の値"];
const duplicated = [...original];
duplicated[0] = "変更後の値";
console.log(`original[${original[0]}] duplicated[${duplicaated[0]}]`);
> original[元の値] duplicated[変更後の値]

 配列を異なる変数に再代入
const original = ["元の値"];
const notDuplicated = original;
notDuplicated[0] = "変更後の値";
console.log(`original[${original[0]}] duplicated[${duplicated[0]}]`);]
> original[変更後の値] duplicated[変更後の値]

12.3.4 配列の作成

スプレッド演算子を使うと、配列をマージ(結合)したり、任意の位置に要素を追加した配列を簡単に作成できる。

配列の作成
const arry1 = [10, 20, 30];
const arry2 = [40, 50, 60];
console.log([...arry1, ...arry2]);
> [10, 20, 30, 40, 50, 60]

console.log([...arry2, ...arry1]);
> [40, 50, 60, 10, 20, 30]

console.log([0, ...arry2, 70, ...arry1]);
> [0, 40, 50, 60, 70, 10, 20, 30]

12.3.5 オブジェクトでの使用

スプレッド演算子は、基本的に反復可能なオブジェクトで使います。
ES2018以降ではオブジェクトに対しても使えるようになっています。

・スプレッド演算子を使ってオブジェクトを作成
const obj1 = {prop1: 10, prop2: 20};
const obj2 = {prop3: 30, prop4: 40};
console.log({...obj1, ...obj2}); // オブジェクトのマージ
> {prop1:10, prop2: 20, prop3: 30, prop4: 40}
console.log({prop0: 0, ...obj2, prop5: 50});
> {prop0: 0, prop3: 30, prop4: 40, prop5: 50}
console.log({prop1: 0, ...obj1});
> {prop1: 10, prop2: 20}

・Symbol.iteratorの変更後にスプレッド演算子で配列を複製

Array.prototype[Symbol.iterator] = function* () {
    yield "Hello",
    yield "World"
}

const arry = [1, 2, 3];
const newArry = [...arry];
console.log(newArry);
> ["Hello", "World"]
⚠️ **GitHub.com Fallback** ⚠️