JavaScriptのループと反復処理
基本のループ
for
繰り返し回数が事前に決まっている場合に適すループ。
for (let i = 0; i < 5; i++) {
console.log(i); // 0, 1, 2, 3, 4
}
注意点
- 条件式、更新式を誤ると無限ループになる。
while
条件式がtrueの間、繰り返し実行されるループ。
ループ開始前に条件をチェックするため、一度も実行されない場合がある。
let i = 0;
while (i < 5) {
console.log(i); // 0, 1, 2, 3, 4
i++;
}
注意点
- 条件式の更新を忘れると無限ループになる。
do...while
ブロック内のコードを少なくとも1回は実行し、その後で条件をチェックするループ。
ユーザー入力の検証など、最初の実行が必須な場合に適する。
let i = 0;
do {
console.log(i); // 0, 1, 2, 3, 4
i++;
} while (i < 5);
注意点
- 条件式の更新を忘れると無限ループになる。
オブジェクトのループ
for...in
オブジェクトのプロパティをループする。
const obj = { a: 1, b: 2 };
for (let key in obj) {
console.log(key, obj[key]); // a 1, b 2
}
注意点
-
順序が保証されないため、配列には不向き。
-
for...in
はプロトタイプチェーン上のプロパティも取得する。そのため自身のプロパティのみ取得するObject.keys()
やObject.entries()
を使用する方が安全にループできる。const obj = { a: 1, b: 2 }; Object.entries(obj).forEach(([key, value]) => { console.log(key, value); // a 1, b 2 });
反復可能オブジェクトのループ
for...of
配列、文字列、Map、Set などの反復可能オブジェクトをループする。
値そのものを直接取得でき、インデックス管理が不要。
const arr = [1, 2, 3];
for (let value of arr) {
console.log(value); // 1, 2, 3
}
注意点
-
for...of
はSymbol.iterator
を持つオブジェクトのみループ可能で、通常のオブジェクトには使用できない。通常のオブジェクトに使う場合はObject.keys()
などで変換が必要。
配列のループとデータ変換
forEach()
配列の要素ごとにコールバック関数を実行する。
[1, 2, 3].forEach(num => console.log(num)); // 1, 2, 3
注意点
-
forEach
ではbreak
,continue
は使えない。return
は各イテレーションのスコープ内でのみ影響し、forEach
自体を中断することはできない。
早期終了が必要な場合、代わりにsome()
やevery()
や、従来のfor
ループの利用を検討する。const numbers = [1, 2, 3, 4, 5]; numbers.forEach(num => { if (num === 3) { return; // 3の時だけスキップ(forEach全体は継続する) } console.log(num); // 1, 2, 4, 5 });
-
forEach()
は各反復の戻り値を考慮しないため、async
をつけてもawait
が機能しない。非同期処理で使用したい場合はfor...of
を使う。
map()
配列の各要素を変換し、新しい配列を生成する。(非破壊的処理で、元の配列は変更されない。)
const doubled = [1, 2, 3].map(num => num * 2);
console.log(doubled); // [2, 4, 6]
注意点
- 戻り値を使わない場合、
forEach()
の方が適切。 -
map()
は配列の全ての要素に対して、コールバック関数を適用する関数なので、 スキップや中断をする場合はmap()
ではなく、filter()
を使うのが適切。
filter()
条件に合致する要素だけを抽出した新しい配列を生成する。
const evens = [1, 2, 3, 4].filter(num => num % 2 === 0);
console.log(evens); // [2, 4]
注意点
- 元の配列を変更したい場合は
splice()
などと組み合わせる。
map()とfilter()の違い
-
map()
:return
の配列の長さが同じ -
filter()
:return
の配列の長さが違う
reduce()
配列を各要素を指定された関数で処理し、単一の値に集約する。(合計関数)
合計計算だけでなく、オブジェクトの集計やネストされた配列のフラット化にも応用できる。
フラットな配列の集約
const initialValue = 0;
const sum = [1, 2, 3, 4].reduce((acc, num) => acc + num, initialValue);
console.log(sum); // 10
ネストされた配列のフラット化
const nestedArray = [[1, 2], [3, 4], [5, 6]];
const flatArray = nestedArray.reduce((acc, curr) => acc.concat(curr), []);
console.log(flatArray); // [1, 2, 3, 4, 5, 6]
オブジェクトの集計
const users = [
{ name: "Alice", age: 25 },
{ name: "Bob", age: 30 },
{ name: "Charlie", age: 25 }
];
// 年齢ごとの人数を集計
const ageGroups = users.reduce((acc, user) => {
acc[user.age] = (acc[user.age] || 0) + 1;
return acc;
}, {});
console.log(ageGroups); // { 25: 2, 30: 1 }
flatMap()
map().flat(1)
の短縮系。
配列の各要素に対して、1階層のネストを解消しながら指定した関数を適用し、新しい配列を作成する。
const arr = [1, 2, 3];
// 各要素に対して、元の値とその2倍の値の配列を返し、それをフラット化する
const result = arr.flatMap(num => [num, num * 2]);
console.log(result); // [1, 2, 2, 4, 3, 6]
条件判定・検索系のメソッド
some()
1つでも条件を満たせば true を返す。(例:ユーザーが未成年か)
const users = [
{ name: "Alice", age: 25 },
{ name: "Bob", age: 17 },
{ name: "Charlie", age: 30 }
];
const hasMinor = users.some(user => user.age < 20);
console.log(hasMinor); // true(Bob が未成年)
every()
すべての要素が条件を満たせば true を返す。(例:全員が20歳以上か)
const users = [
{ name: "Alice", age: 25 },
{ name: "Bob", age: 17 },
{ name: "Charlie", age: 30 }
];
const allAdults = users.every(user => user.age >= 20);
console.log(allAdults); // false(Bob が未成年)
find()
条件に合致する最初の要素を返す。(例:IDが一致するユーザーを探す)
const users = [
{ id: 1, name: "Alice" },
{ id: 2, name: "Bob" },
];
const user = users.find(u => u.id === 2);
console.log(user); // { id: 2, name: "Bob" }
findIndex()
条件に合致する最初のインデックスを返す。見つからなければ-1が返る。
const users = [
{ id: 1, name: "Alice" },
{ id: 2, name: "Bob" },
{ id: 3, name: "Charlie" }
];
const index = users.findIndex(u => u.id === 2);
console.log(index); // 1(Bobのインデックス)
includes()
指定した値が配列内にあるか 判定する。
const fruits = ["apple", "banana", "grape"];
console.log(fruits.includes("banana")); // true
console.log(fruits.includes("orange")); // false
Discussion
今だと Array.fromAsync も使えますね。(ただし、配列を生成する map系処理な為、無限のジェネレータでは 現状は for ... of を使う方が良い
近未来としては
proposal async iterator helper
が実装されればAsyncIterator.from(arrayLike).forEach(asyncFunc)
で実行できる予定です。
ありがとうございます。用途によってめっちゃ使い分けができるんですね。。
proposal async iterator helper
使えるようになったら便利そうですね!書きながら理解深めていこうと思います。勉強になります!