新・JavaScript文法（3）：制御構文とループ処理の基本

[前回の記事はコチラ](https://qiita.com/azmnc/items/ef5969f745d45f582163) です。この記事では、JavaScriptの制御構文とループ処理について扱います。プログラムの流れを制御する基本的な構文と、効率的なループ処理の方法について書いていきます。

## 制御構文の基本

### if-else文による条件分岐

最も基本的な条件分岐です。

```javascript
const age = 20;

// 基本的なif文
if (age >= 18) {
    console.log("成人です");
}

// if-else文
if (age >= 18) {
    console.log("成人です");
} else {
    console.log("未成年です");
}

// if-else if-else文
if (age >= 65) {
    console.log("シニア割引が適用されます");
} else if (age >= 18) {
    console.log("通常料金です");
} else {
    console.log("学生割引が適用されます");
}
```

#### 条件式での注意点

1. 等価比較には `===` を使用する（`==` は型変換が行なわれるため予期せぬ動作の原因になる）
2. オブジェクトや配列の比較は、参照の比較となるため、深い比較が必要な場合は別途実装する必要がある
3. falsy値（`false`、`0`、`""`、`null`、`undefined`、`NaN`）の扱いに注意する

```javascript
// 良い例
if (value === 0) {
    console.log("値は0です");
}

// 悪い例（予期せぬ型変換が発生する可能性あり）
if (value == 0) {
    console.log("値は0です");
}
```

### switch文の使い方

switch文は1つの式に対して複数の値との一致を確認する場合に使用します。if-else文の連続よりも可読性を高められることがあります。

```javascript
const dayOfWeek = new Date().getDay();

switch (dayOfWeek) {
    case 0:
        console.log("日曜日です");
        break;
    case 6:
        console.log("土曜日です");
        break;
    default:
        console.log("平日です");
        break;
}
```

#### switch文使用時の注意点

1. 各 `case` の後には必ず `break` を書く（意図的な fall-through を除く）
2. `default` 句を適切に設定する
3. 複雑な条件分岐や範囲指定の場合は、`if` 文を使うほうがより柔軟に対応できる場合がある

## 基本的なループ処理

### for文の基本

for文は、決まった回数の繰り返し処理に適しています。

```javascript
// 基本的なfor文
for (let i = 0; i < 5; i++) {
    console.log(i); // 0, 1, 2, 3, 4
}

// 配列の要素を順番に処理
const fruits = ["りんご", "バナナ", "オレンジ"];
for (let i = 0; i < fruits.length; i++) {
    console.log(fruits[i]);
}
```

### while文とdo...while文

条件が真である間、繰り返し処理を行ないます。

```javascript
// while文
let count = 0;
while (count < 5) {
    console.log(count);
    count++;
}

// do...while文（必ず1回は実行される）
let input;
do {
    input = Math.random();
    console.log(input);
} while (input < 0.5);
```

#### while文使用時の注意点

1. 無限ループを避けるため、必ず終了条件を確認する
2. ループ変数の更新を忘れない
3. 条件式が複雑な場合は、可読性のため、変数に分離することを検討する

## モダンなループ処理

### for...ofによる反復処理

for...of文は、配列やイテラブルオブジェクトの要素を直接取り出して処理できます。従来のfor文より簡潔で可読性の高いコードが書けます。

なお、配列の処理については、目的に応じてArray methodsを使用することも検討してください。

```javascript
const numbers = [1, 2, 3, 4, 5];

// for...ofを使用する場合
let sum = 0;
for (const num of numbers) {
    sum += num;
}

// Array methodsを使用する場合
const sum2 = numbers.reduce((acc, cur) => acc + cur, 0);
const evenNumbers = numbers.filter(num => num % 2 === 0);
const doubled = numbers.map(num => num * 2);
```

以下のような場合は、for...of文の使用を検討してください。

* 処理が複数のステップを含む場合
* `break` や `continue` を使用する必要がある場合
* エラー処理が必要な場合

一方、以下のような場合は、Array methodsの使用を検討してください。

* データの変換（`map`）
* データのフィルタリング（`filter`）
* 集計処理（`reduce`）
* メソッドチェーンを使用する場合

### for...inによるプロパティ列挙

for...in文は、オブジェクトのプロパティを列挙する際に使用します。ただし、配列には使用を避けることをお勧めします。

```javascript
const person = {
    name: "山田太郎",
    age: 30,
    city: "東京"
};

// オブジェクトのプロパティを列挙
for (const key in person) {
    console.log(`${key}: ${person[key]}`);
}
```

#### for...in使用時の注意点

1. 配列にはfor...inの使用を避ける（配列のインデックスは文字列として扱われるため、意図せず追加したプロパティや、プロトタイプチェーン上のプロパティまで列挙されてしまう可能性がある）
2. プロトタイプチェーン上のプロパティも列挙される
3. オブジェクトのプロパティの列挙順序は、必ずしも定義順や挿入順に一致するとは限らない。とくに数値インデックス以外のプロパティは、JavaScriptエンジンによっては異なる順序になる場合があるため、依存しないようにする

```javascript
// 配列での使用は避ける（代わりにfor...ofを使用）
const arr = ["a", "b", "c"];

// 非推奨
for (const index in arr) {
    console.log(arr[index]);
}

// 推奨
for (const value of arr) {
    console.log(value);
}
```

## ループの制御

### breakとcontinue

`break` と `continue` を使用することで、ループの流れを制御できます。

```javascript
// breakの使用例（5が見つかったら終了）
for (let i = 0; i < 10; i++) {
    if (i === 5) {
        break;
    }
    console.log(i); // 0, 1, 2, 3, 4
}

// continueの使用例（偶数のみ表示）
for (let i = 0; i < 5; i++) {
    if (i % 2 !== 0) {
        continue;
    }
    console.log(i); // 0, 2, 4
}
```

### ラベル付きbreak/continue

ネストされたループで特定のループを対象とする場合に使用できます。ただし、コードの複雑性が増すため、必要な場合のみ使用することをお勧めします。

```javascript
outerLoop: for (let i = 0; i < 3; i++) {
    for (let j = 0; j < 3; j++) {
        if (i === 1 && j === 1) {
            break outerLoop;
        }
        console.log(`i: ${i}, j: ${j}`);
    }
}
```

## エラー処理とパフォーマンス

### エラー処理の基本

ループ処理時のエラー処理は重要です。以下は基本的なパターンです。

```javascript
const data = [1, 2, "3", 4, "five", 6];

// エラー処理を含むループ
for (const item of data) {
    try {
        const number = Number(item);
        if (isNaN(number)) {
            throw new Error(`Invalid number: ${item}`);
        }
        console.log(number * 2);
    } catch (error) {
        console.error(`エラーが発生しました: ${error.message}`); // エラーメッセージの出力
        console.error(error.stack); // スタックトレースの出力
        continue; // エラーをスキップして次の要素へ
    }
}
```

**エラー処理のポイント:**

* **try-catch:** 予期せぬエラーが発生する可能性のあるコードを `try` ブロックで囲み、エラーが発生した場合は `catch` ブロックで処理する
* **Errorオブジェクト:** `catch` ブロックでエラーオブジェクトを受け取ることで、エラーメッセージ（`error.message`）やスタックトレース（`error.stack`）などの情報を取得できる
* **エラーログ:** 発生したエラーをログに記録することで、問題の特定やデバッグが容易になる
* **エラーのスキップ:** エラーが発生した場合でも、ループ全体を中断せずに、次の要素の処理を続けることができる

### パフォーマンスの考慮

大きなデータセットを処理する場合は、以下の点に注意してください。

1. ループ内での配列/オブジェクトの生成を最小限に（できるだけループの外で初期化）
2. 条件チェックを最適化（頻繁に変更されない条件は外に出す）
3. 不要な処理をスキップするために、必要に応じて早期リターン（`break`）を活用
4. DOM操作を伴うループでは、リフローとリペイントを最小限で抑えるために、Document FragmentやrequestAnimationFrameの使用を検討する
5. 可能な限り、Array methods( `map`, `filter`, `reduce`など )を活用し、内部的に最適化された処理を活用する

```javascript
const bigArray = new Array(10000).fill(1);

// 非効率な例
for (const item of bigArray) {
    const temp = [];  // ループ内で毎回配列を生成
    temp.push(item);
    // ... 処理 ...
}

// 効率的な例
const temp = [];  // ループ外で一度だけ配列を生成
for (const item of bigArray) {
    temp.push(item);
    // ... 処理 ...
}
```

## 実践的なコード例

```javascript
// より実践的なユーザーデータ
const users = [
    { id: 1, name: "田中", age: 28, active: true, lastLogin: "2025-01-15", permissions: ["read", "write"] },
    { id: 2, name: "鈴木", age: 35, active: false, lastLogin: "2024-12-20", permissions: ["read"] },
    { id: 3, name: "佐藤", age: 42, active: true, lastLogin: "2025-01-17", permissions: ["read", "write", "admin"] },
    { id: 4, name: "山田", age: 31, active: true, lastLogin: "2025-01-16", permissions: ["read", "write"] }
];

// より複雑な条件での集計例
let adminCount = 0;
let recentActiveUsers = 0;
const today = new Date();

for (const user of users) {
    // 管理者権限を持つユーザーの数を集計
    if (user.permissions.includes("admin")) {
        adminCount++;
    }

    // アクティブで、最近（7日以内）ログインしたユーザーの数を集計
    if (user.active) {
        const lastLogin = new Date(user.lastLogin);
        const daysSinceLogin = (today - lastLogin) / (1000 * 60 * 60 * 24);

        if (daysSinceLogin <= 7) {
            recentActiveUsers++;
        }
    }
}

console.log(`管理者数: ${adminCount}`);
console.log(`最近アクティブなユーザー数: ${recentActiveUsers}`);
```

## 復習

### 基本問題

#### 1. 1から100までの数字のうち、3の倍数のみを出力するプログラムを作成する

#### 2. 以下の配列から、20以上の数値のみを新しい配列に格納するプログラムを作成する

```javascript
const numbers = [5, 23, 12, 34, 18, 9, 27, 16, 4];
```

#### 3. 以下のオブジェクトから、valueが数値型のプロパティのみを出力するプログラムを作成する

```javascript
const data = {
    name: "JavaScript",
    year: 1995,
    creator: "Brendan Eich",
    version: 2023,
    isPopular: true
};
```

### 実践問題

#### 4. 以下のような商品データの配列を使い、カテゴリーごとの合計金額を計算するプログラムを作成する

   ```javascript
   const products = [
       { name: "ノートPC", category: "電化製品", price: 85000 },
       { name: "デスク", category: "家具", price: 25000 },
       { name: "イヤホン", category: "電化製品", price: 15000 },
       { name: "チェア", category: "家具", price: 18000 },
       { name: "マウス", category: "電化製品", price: 7000 }
   ];
   ```

#### 5. 以下のようなログデータの配列を使い、エラーの種類ごとの発生回数を集計するプログラムを作成する

ただし、日付が2025年1月1日以降のログのみを対象としてください。

```javascript
const logs = [
    { date: "2024-12-25", type: "error", message: "接続エラー" },
    { date: "2025-01-05", type: "error", message: "認証エラー" },
    { date: "2025-01-10", type: "warning", message: "遅延検知" },
    { date: "2025-01-15", type: "error", message: "接続エラー" },
    { date: "2025-01-15", type: "info", message: "バックアップ完了" }
];
```

#### 6. 以下のような入れ子構造の配列（ツリー構造）を使い、すべてのnodeの値の合計を計算するプログラムを作成する

```javascript
   const tree = {
       node: 1,
       children: [
           {
               node: 2,
               children: [
                   { node: 4 },
                   { node: 5 }
               ]
           },
           {
               node: 3,
               children: [
                   { node: 6 }
               ]
           }
       ]
   };
   ```

### 解答例

#### 問題1

```javascript
for (let i = 1; i <= 100; i++) {
    if (i % 3 === 0) {
        console.log(i);
    }
}

// Array.fromを使用する方法
// { length: 100 } は、長さが100のオブジェクトを作成
// アンダースコアは、未使用の引数を表す慣習
Array.from({ length: 100 }, (_, i) => i + 1)
     .filter(num => num % 3 === 0)
     .forEach(num => console.log(num));
```

#### 問題2

```javascript
// for...ofを使用する方法
const numbers = [5, 23, 12, 34, 18, 9, 27, 16, 4];
const result = [];
for (const num of numbers) {
    if (num >= 20) {
        result.push(num);
    }
}
console.log(result);

// filterメソッドを使用する方法
const result2 = numbers.filter(num => num >= 20);
console.log(result2);
```

#### 問題3

```javascript
const data = {
    name: "JavaScript",
    year: 1995,
    creator: "Brendan Eich",
    version: 2023,
    isPopular: true
};

// for...inを使用する方法
for (const key in data) {
    if (typeof data[key] === 'number') {
        console.log(`${key}: ${data[key]}`);
    }
}

// Object.entries（オブジェクトのキーと値を配列に変換）を使用する方法
Object.entries(data)
    .filter(([_, value]) => typeof value === 'number')
    .forEach(([key, value]) => console.log(`${key}: ${value}`));
```

#### 問題4

```javascript
const products = [
    { name: "ノートPC", category: "電化製品", price: 85000 },
    { name: "デスク", category: "家具", price: 25000 },
    { name: "イヤホン", category: "電化製品", price: 15000 },
    { name: "チェア", category: "家具", price: 18000 },
    { name: "マウス", category: "電化製品", price: 7000 }
];

// for...ofを使用する方法
const totals = {};
for (const product of products) {
    if (!(product.category in totals)) {
        totals[product.category] = 0;
    }
    totals[product.category] += product.price;
}
console.log(totals);

// reduceを使用する方法
const totals2 = products.reduce((acc, product) => {
    acc[product.category] = (acc[product.category] || 0) + product.price;
    return acc;
}, {});
console.log(totals2);
```

#### 問題5

```javascript
const logs = [
    { date: "2024-12-25", type: "error", message: "接続エラー" },
    { date: "2025-01-05", type: "error", message: "認証エラー" },
    { date: "2025-01-10", type: "warning", message: "遅延検知" },
    { date: "2025-01-15", type: "error", message: "接続エラー" },
    { date: "2025-01-15", type: "info", message: "バックアップ完了" }
];

const targetDate = new Date('2025-01-01');
const errorCounts = {};

// for...ofを使用する方法
for (const log of logs) {
    const logDate = new Date(log.date);
    if (logDate >= targetDate) {
        errorCounts[log.type] = (errorCounts[log.type] || 0) + 1;
    }
}
console.log(errorCounts);

// filterとreduceを使用する方法
const errorCounts2 = logs
    .filter(log => new Date(log.date) >= targetDate)
    .reduce((acc, log) => {
        acc[log.type] = (acc[log.type] || 0) + 1;
        return acc;
    }, {});
console.log(errorCounts2);
```

#### 問題6

```javascript
// 再帰関数を使用する方法
function sumTreeNodes(node) {
    let sum = node.node;
    if (node.children) {
        for (const child of node.children) {
            sum += sumTreeNodes(child);
        }
    }
    return sum;
}

const total = sumTreeNodes(tree);
console.log(total); // 21

// 再帰関数とreduceを組み合わせた方法
function sumTreeNodes2(node) {
    return node.node + (node.children?.reduce((sum, child) =>
        sum + sumTreeNodes2(child), 0) || 0);
}

const total2 = sumTreeNodes2(tree);
console.log(total2); // 21
```

各問題に対して、基本的な方法（for文やfor...of）による解法とモダンな方法（Array methodsなど）による解法の両方を示しました。実務では状況に応じて適切な方法を選択することが重要です。

## まとめ

この記事では、JavaScriptの制御構文とループ処理について扱いました。ポイントをまとめると、以下のとおりです。

1. 条件分岐には主にif文とswitch文を使用
2. 基本的なループには従来のfor文とwhile文
3. モダンな配列処理にはfor...ofを活用
4. オブジェクトのプロパティ列挙にはfor...inを使用
5. 適切なエラー処理とパフォーマンスの考慮が重要
6. 目的に応じてループ処理とArray methodsを使い分け
7. エラー処理にはtry-catch文を使用し、エラーログを記録する
8. パフォーマンスを考慮して、ループ内で不要な処理を避ける

これらの制御構文とループ処理を適切に組み合わせることで、効率的で可読性の高いコードを書くことができます。

[次回](https://qiita.com/azmnc/items/810f1334d33fccfbf8c1) は、配列の基本について、その次の回では関数の基本とアロー関数について扱います。は、配列の基本について、その次の回では関数の基本とアロー関数について扱います。