17. for-of ループ
目次
この書籍のサポートをお願いします: 購入する (PDF, EPUB, MOBI) または 寄付する
(広告です。ブロックしないでください。)

17. for-of ループ



17.1 概要

for-of は ES6 の新しいループで、for-inforEach() の両方を置き換え、新しい反復プロトコルをサポートします。

反復可能なオブジェクト (配列、文字列、Map、Set など; 「反復可能オブジェクトとイテレーター」の章を参照) をループ処理するために使用します。

const iterable = ['a', 'b'];
for (const x of iterable) {
    console.log(x);
}

// Output:
// a
// b

breakcontinuefor-of ループ内で動作します。

for (const x of ['a', '', 'b']) {
    if (x.length === 0) break;
    console.log(x);
}

// Output:
// a

配列をループ処理しながら、要素とそのインデックスの両方にアクセスします (of の前の角かっこは、分割代入を使用していることを意味します)。

const arr = ['a', 'b'];
for (const [index, element] of arr.entries()) {
    console.log(`${index}. ${element}`);
}

// Output:
// 0. a
// 1. b

Map の [キー, 値] エントリをループ処理します (of の前の角かっこは、分割代入を使用していることを意味します)。

const map = new Map([
    [false, 'no'],
    [true, 'yes'],
]);
for (const [key, value] of map) {
    console.log(`${key} => ${value}`);
}

// Output:
// false => no
// true => yes

17.2 for-of ループの紹介

for-of を使用すると、反復可能なデータ構造 (配列、文字列、Map、Setなど) をループ処理できます。反復可能性がどのように機能するかは、「反復可能オブジェクトとイテレーター」の章で詳しく説明しています。ただし、for-of ループを使用する場合、詳細を知る必要はありません。

const iterable = ['a', 'b'];
for (const x of iterable) {
    console.log(x);
}

// Output:
// a
// b

for-ofiterable のアイテムを順番に処理し、ループ変数 x に一度に1つずつ割り当ててから、本体を実行します。x のスコープはループであり、ループ内でのみ存在します。

breakcontinue を使用できます。

for (const x of ['a', '', 'b']) {
    if (x.length === 0) break;
    console.log(x);
}

// Output:
// a

for-of は次の利点を兼ね備えています。

17.3 落とし穴: for-of は反復可能な値でのみ動作する

of 句のオペランドは、反復可能でなければなりません。つまり、プレーンオブジェクトを反復処理する場合は、ヘルパー関数が必要になります (「プレーンオブジェクトは反復可能ではない」を参照)。値が配列のような場合、Array.from() を使用して配列に変換できます。

// Array-like, but not iterable!
const arrayLike = { length: 2, 0: 'a', 1: 'b' };

for (const x of arrayLike) { // TypeError
    console.log(x);
}

for (const x of Array.from(arrayLike)) { // OK
    console.log(x);
}

17.4 反復変数: const 宣言と var 宣言の比較

反復変数を const で宣言すると、反復ごとに新しい束縛 (ストレージ領域) が作成されます。次のコードスニペットでは、アロー関数を介して、後のために elem の現在の束縛を保存しています。その後、アロー関数は elem の同じ束縛を共有せず、それぞれ異なる束縛を持っていることがわかります。

const arr = [];
for (const elem of [0, 1, 2]) {
    arr.push(() => elem); // save `elem` for later
}
console.log(arr.map(f => f())); // [0, 1, 2]

// `elem` only exists inside the loop:
console.log(elem); // ReferenceError: elem is not defined

let 宣言は const 宣言と同じように機能します (ただし、束縛は変更可能です)。

反復変数を var で宣言した場合、状況がどのように異なるかを確認することは有益です。これで、すべてのアロー関数が elem の同じ束縛を参照します。

const arr = [];
for (var elem of [0, 1, 2]) {
    arr.push(() => elem);
}
console.log(arr.map(f => f())); // [2, 2, 2]

// `elem` exists in the surrounding function:
console.log(elem); // 2

反復ごとに1つの束縛を持つことは、ループを介して関数を作成する場合 (たとえば、イベントリスナーを追加する場合) に非常に役立ちます。

for ループ (let を使用) および for-in ループ (const または let を使用) でも、反復ごとの束縛を取得します。詳細については、変数の章で説明します。

17.5 既存の変数、オブジェクトプロパティ、配列要素を使った反復

これまで、宣言された反復変数を持つ for-of のみを見てきました。ただし、他にもいくつかの形式があります。

既存の変数で反復処理できます。

let x;
for (x of ['a', 'b']) {
    console.log(x);
}

オブジェクトプロパティで反復処理することもできます。

const obj = {};
for (obj.prop of ['a', 'b']) {
    console.log(obj.prop);
}

そして、配列要素で反復処理することもできます。

const arr = [];
for (arr[0] of ['a', 'b']) {
    console.log(arr[0]);
}

17.6 分割代入パターンを使った反復

for-of と分割代入を組み合わせると、(配列としてエンコードされた) [キー, 値] ペアの反復処理に特に便利です。これは Map の場合です。

const map = new Map().set(false, 'no').set(true, 'yes');
for (const [k,v] of map) {
    console.log(`key = ${k}, value = ${v}`);
}
// Output:
// key = false, value = no
// key = true, value = yes

Array.prototype.entries() も、[キー, 値] ペアの反復可能オブジェクトを返します。

const arr = ['a', 'b', 'c'];
for (const [k,v] of arr.entries()) {
    console.log(`key = ${k}, value = ${v}`);
}
// Output:
// key = 0, value = a
// key = 1, value = b
// key = 2, value = c

したがって、entries() を使用すると、反復処理されたアイテムを位置に応じて異なる方法で扱うことができます。

/** Same as arr.join(', ') */
function toString(arr) {
    let result = '';
    for (const [i,elem] of arr.entries()) {
        if (i > 0) {
            result += ', ';
        }
        result += String(elem);
    }
    return result;
}

この関数は次のように使用します。

> toString(['eeny', 'meeny', 'miny', 'moe'])
'eeny, meeny, miny, moe'
次へ: 18. 新しい配列機能