記事作成のきっかけ
Javascriptには様々な繰り返しの構文やメソッドがあり、
卒業制作で利用した繰り返しの構文、
メソッドについて整理をしたいと思い、
記事作成を行おうと思いました。
【for...inループ】
概要
オブジェクトのプロパティを列挙するためのループ構文
特徴
- オブジェクトの列挙可能なプロパティを全て反復処理をする
(truthyな値を保持するオブジェクトが全て) - プロトタイプチェーンを上るので、継承されたプロパティも含まれる
(Object.prototype.xxxx = "不要なプロパティ"
も反復対象に含まれる) - プロパティの処理順序で保証されない場合がある
- 配列に使用すると、インデックスが文字列として取得される
実装例
const obj = { a: 1, b: 2 };
Object.prototype.extraProp = "不要なプロパティ";
for (const key in obj) {
if (obj.hasOwnProperty(key)) {
console.log(key); // "a", "b"のみ出力(extraPropは出力されない)
}
}
Object.prototype.extraProp = "不要なプロパティ"
も反復処理の対象として含まれるため、
意図しないプロパティまで反復対象としてループ処理を処理されてしまいます。
対策としては、hasOwnPropertyを使用し、
継承プロパティを除外する処理を追加する必要がある。
想定する利用シーン
- 動的なプロパティ名を持つオブジェクトの処理
- 設定オブジェクトのすべてのキーにアクセスする必要がある場合
- オブジェクトのシリアライズ/デシリアライズ処理
- オブジェクトのバリデーションや内容の検査
for...inループの利用について
for...inループの代替手段
一般的には、for...inループを使わず代替手段として、
forループ・forEach()、for...ofループを使うことが
一般的な反復処理として使うように
MDNでも推奨されています。
【for...ofループ】
概要
イテラブル(反復可能)オブジェクトの値を
順番に処理するループ構文
特徴
- 配列、文字列、Map、Set、TypedArray、Generatorなど
イテラブルオブジェクトと互換性がある - 値自体を直接取得できるため、配列処理が直感的
- async/awaitと組み合わせて非同期イテレーションが可能
- 処理の途中でbreak/continue/returnによる制御が可能
実装例
// 制御構文との組み合わせ
const numbers = [1, 2, 3, 4, 5];
for (const num of numbers) {
if (num === 2) {
console.log('2をスキップします');
continue;
}
if (num === 4) {
console.log('4で終了します');
break;
}
console.log(num);
// コンソール出力結果
// 1
// 2をスキップします
// 3
// 4で終了します
}
// Mapの処理
const userMap = new Map([
['id1', { name: '田中', age: 28 }],
['id2', { name: '佐藤', age: 32 }],
['id3', { name: '鈴木', age: 24 }]
]);
for (const [id, user] of userMap) {
console.log(`ID: ${id}, 名前: ${user.name}, 年齢: ${user.age}`);
// コンソール出力結果
// ID: id1, 名前: 田中, 年齢: 28
// ID: id2, 名前: 佐藤, 年齢: 32
// ID: id3, 名前: 鈴木, 年齢: 24
}
想定する利用シーン
- 配列の要素を順番に処理する必要がある場合
- 処理の途中で条件に基づいて中断したい場合
- 非同期処理のシーケンシャルな実行
- ジェネレータ関数からデータを取得する場合
- MapやSetなどの特殊なコレクションの処理
for...ofループの利用について
オブジェクト型での利用について
下記のようにオブジェクトをそのままfor...ofループで
反復処理を行うことは出来ません。
// オブジェクト型ではfor...ofループの使用不可
const user = {
name: '山田太郎',
age: 30,
email: 'yamada@example.com'
};
オブジェクトの配列で利用することは可能
// 配列であればオブジェクト型でfor...ofループの使用不可
onst userArray = [
{ id: 'id1', name: '田中', age: 28 },
{ id: 'id2', name: '佐藤', age: 32 },
{ id: 'id3', name: '鈴木', age: 24 }
];
// for...ofループでユーザー配列を繰り返し処理
for (const user of userArray) {
console.log(`ID: ${user.id}, 名前: ${user.name}, 年齢: ${user.age}`);
}
【forEachメソッド】
概要
配列の各要素に対して関数を一度ずつ実行するメソッド
特徴
- 配列専用のメソッドで、コールバック関数を各要素に適用
- 要素、インデックス、配列全体の3つの引数をコールバックに渡せる
- 戻り値は常にundefinedで、チェーン不可
- 途中で中断する手段がなく(breakやcontinueが使えない)、
すべての要素を処理する - 実行中にコールバック内で配列を変更すると、
予期しない動作になる可能性がある
実装例
// オブジェクトの配列
const users = [
{ id: 1, name: '田中太郎', age: 25 },
{ id: 2, name: '鈴木花子', age: 30 },
{ id: 3, name: '佐藤次郎', age: 22 }
];
// オブジェクト配列に対してforEachを使用
users.forEach(user => {
console.log(`ID: ${user.id}, 名前: ${user.name}, 年齢: ${user.age}`);
// コンソール出力結果
// ID: 1, 名前: 田中太郎, 年齢: 25
// ID: 2, 名前: 鈴木花子, 年齢: 30
// ID: 3, 名前: 佐藤次郎, 年齢: 22
});
想定する利用シーン
- 配列の各要素に対して副作用のある操作を行う場合
(各要素に対して何かを実行する意図が明確になる) - DOM要素の更新やUIの描画
- サーバーへのリクエストなど、要素ごとの操作
(fetchなどで複数のAPIエンドポイントに
リクエスト送信したいケース)
(順次リクエストしたい場合はfor...ofループを使う) - 配列の処理順序が重要で、かつ中断の必要がない場合
(break、continueなどの制御が使えない) - コードの読みやすさを優先する場合
【map関数】
概要
mapは配列の各要素に関数を適用し、その結果からなる新しい配列を返すメソッド
特徴
- 元の配列と同じ長さの新しい配列を返却する
- 元の配列は変更されません(非破壊的操作)
- 各要素の変換に使用します
- 関数型プログラミングパターンと相性が良いです
- 他の配列メソッド(filter、reduceなど)と組み合わせて使用可能
実装例
// オブジェクトの配列
const users = [
{ id: 1, firstName: '太郎', lastName: '田中' },
{ id: 2, firstName: '花子', lastName: '鈴木' },
{ id: 3, firstName: '次郎', lastName: '佐藤' }
];
// オブジェクト配列の変換
const formmattedUsers = users.map(user => ({
id: user.id,
fullName: `${user.lastName} ${user.firstName}`
}));
console.log(formmattedUsers);
// コンソール出力結果
// [
// { id: 1, fullName: 田中 太郎 }
// { id: 2, fullName: 鈴木 花子 }
// { id: 3, fullName: 佐藤 次郎 }
// ]
想定する利用シーン
- 配列から新しい配列を作成する必要がある場合
- データの変換またはフォーマット変更が必要な場合
- 複数のメソッドをチェーンして複雑なデータ処理を行う場合
- 非同期処理を複数並列で実行する場合(Promise.allと組み合わせ)
- 関数型プログラミングスタイルを採用している場合
Reactでのmap関数利用について
map関数は、Reactのコンポーネントレンダリングで
良く使われているのを目にします。
その理由として、
- JSX(TSX)の中でmapを使うことで、データ配列と表示要素の関係を直接的に宣言出来る
{items.map(item => (
<Text key={item.id}>{item.name}</Text>
))}
- mapは元の配列を変更せず、
新しい要素の配列(reactではJSX要素)を生成するため、
Reactの「状態は直接変更せず、新しい値を生成する」という
原則に合致する
- Reactでリスト要素を表示する際は、
各要素に一意のkeyプロパティを与える必要があり、
mapはインデックスや要素のIDを使って
簡単にキーを割り当てられる
{users.map((user, index) => (
<UserCard key={user.id || index} user={user} />
))}
使い分けと比較
特徴 | for...in | for...of | forEach | map |
---|---|---|---|---|
対象データ | オブジェクト | イテラブル | 配列 | 配列 |
取得するもの | プロパティ名 | 値 | 値 | 値 |
戻り値 | なし | なし | undefined | 新しい配列 |
中断可能か | はい (break/continue) | はい (break/continue) | いいえ | いいえ |
非同期処理 | サポートなし | サポート (await) | サポートなし | Promise.all と組み合わせ可能 |
変換処理 | 不向き | 不向き | 不向き | 最適 |
副作用処理 | 可能 | 可能 | 最適 | 不向き |
実践的な使い分け
●for...in: オブジェクトのプロパティを列挙
- 設定オブジェクトの処理
- 動的なプロパティを持つオブジェクトの検査
- JSONデータの探索
●for...of: 値に順次アクセス
- 非同期処理の順次実行
- 中断が必要な繰り返し処理
- 複雑なイテラブルの処理(ジェネレータなど)
●forEach: 副作用を伴う処理
- DOM要素の更新
- ログ出力
- サーバーへのリクエスト送信
●map: データ変換
- API応答の整形
- 表示用データの準備
- 計算や値の変換
まだまだ理解度が浅いので、
こちらの振り返り記事を都度見直しながら、
繰り返し構文(メソッド)の理解度を深めて行きたいと思います。
もし不備な点などあればご指摘ください。