1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

JavaScript ループ構文の使い分け完全ガイド

Posted at

目次


推奨する基本方針

選択の優先順位

1. まず for...of を検討する(最優先)

  • 早期終了(break)とスキップ(continue)が可能
  • 非同期処理で正しく動作
  • 様々なイテラブルオブジェクトで使用可能
  • 意図が明確で可読性が高い

2. forEach は限定的に使用

  • 単純な全要素処理で条件分岐がない場合のみ
  • 関数型プログラミングスタイルを重視する場合
  • 重要:早期終了やスキップが必要になった時の変更コストが高い

3. for...in は避ける

  • Object.entries(), Object.keys(), Object.values() + for...of を優先
  • どうしても必要な場合は hasOwnProperty チェックを必須とする

4. パフォーマンスが重要な場合

  • 従来の for ループを検討
  • ただし、まず可読性とメンテナンス性を優先し、実際に問題になってから最適化

実践的な推奨パターン

// ✅ 推奨:配列処理
for (const item of items) {
    // 処理
}

// ✅ 推奨:オブジェクト処理
for (const [key, value] of Object.entries(obj)) {
    // 処理
}

// ✅ 推奨:配列のインデックスが必要な場合(itemは配列)
for (const [index, item] of items.entries()) {
    // 処理
}

// ✅ 推奨:非同期処理
for (const item of items) {
    await processItem(item);
}

各ループ構文の基本概要

forEach

// 配列の各要素に対して関数を実行
const numbers = [1, 2, 3, 4, 5];
numbers.forEach((value, index) => {
    console.log(`Index ${index}: ${value}`);
});

for...of

// イテラブルオブジェクトの値を順次取得
const numbers = [1, 2, 3, 4, 5];
for (const value of numbers) {
    console.log(value);
}

for...in

// オブジェクトのプロパティ名(キー)を順次取得
const user = { name: 'Alice', age: 25, city: 'Tokyo' };
for (const key in user) {
    console.log(`${key}: ${user[key]}`);
}

forEach の特徴と制約

forEachの利点

// 関数型プログラミングスタイル
const numbers = [1, 2, 3, 4, 5];

// 読みやすく、意図が明確
numbers.forEach(num => console.log(num * 2));

// インデックスも簡単に取得可能
numbers.forEach((value, index, array) => {
    console.log(`${index}: ${value} (配列長: ${array.length})`);
});

forEachの重大な制約

1. 早期終了ができない

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

// ❌ break は使えない(SyntaxError)
numbers.forEach(num => {
    if (num === 3) {
        break; // エラー: Illegal break statement
    }
    console.log(num);
});

// ❌ return は早期終了ではなく、その回のみスキップ
numbers.forEach(num => {
    if (num === 3) {
        return; // この回だけスキップ、ループは継続
    }
    console.log(num); // 1, 2, 4, 5 が出力される
});

2. continue相当の処理ができない

// ❌ continue は使えない
numbers.forEach(num => {
    if (num % 2 === 0) {
        continue; // エラー: Illegal continue statement
    }
    console.log(num);
});

// 回避策:return を使うが、意図が不明確
numbers.forEach(num => {
    if (num % 2 === 0) return; // 偶数をスキップ
    console.log(num);
});

3. 非同期処理での問題

const urls = ['url1', 'url2', 'url3'];

// ❌ 期待通りに動作しない(並列実行になる)
urls.forEach(async (url) => {
    const response = await fetch(url);
    console.log(await response.text());
});

// 順次実行したい場合は for...of を使用

for...of の特徴と利点

for...ofの基本的な利点

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

// 値に直接アクセス可能
for (const num of numbers) {
    console.log(num);
}

// インデックスも必要な場合
for (const [index, value] of numbers.entries()) {
    console.log(`${index}: ${value}`);
}

早期終了とスキップが可能

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

// break による早期終了
for (const num of numbers) {
    if (num === 3) {
        break; // ループを完全に終了
    }
    console.log(num); // 1, 2 のみ出力
}

// continue による次の要素へスキップ
for (const num of numbers) {
    if (num % 2 === 0) {
        continue; // 偶数をスキップ
    }
    console.log(num); // 1, 3, 5 のみ出力
}

非同期処理での正しい動作

const urls = ['url1', 'url2', 'url3'];

// ✅ 順次実行される
for (const url of urls) {
    const response = await fetch(url);
    console.log(await response.text());
}

// エラーハンドリングも簡潔
for (const url of urls) {
    try {
        const response = await fetch(url);
        const data = await response.text();
        console.log(data);
    } catch (error) {
        console.error(`Error fetching ${url}:`, error);
        break; // エラー時にループを停止可能
    }
}

様々なイテラブルオブジェクトで使用可能

// 文字列
for (const char of 'Hello') {
    console.log(char); // H, e, l, l, o
}

// Set
const uniqueNumbers = new Set([1, 2, 3, 2, 1]);
for (const num of uniqueNumbers) {
    console.log(num); // 1, 2, 3
}

// Map
const userMap = new Map([['name', 'Alice'], ['age', 25]]);
for (const [key, value] of userMap) {
    console.log(`${key}: ${value}`);
}

// NodeList(DOM要素)
for (const element of document.querySelectorAll('.item')) {
    element.classList.add('processed');
}

for...in の特徴と適用場面

for...inの適切な使用場面

// オブジェクトのプロパティを反復処理
const user = { 
    name: 'Alice', 
    age: 25, 
    city: 'Tokyo',
    isActive: true
};

for (const key in user) {
    console.log(`${key}: ${user[key]}`);
}

// 条件付きプロパティ処理
for (const key in user) {
    if (typeof user[key] === 'string') {
        console.log(`文字列プロパティ ${key}: ${user[key]}`);
    }
}

for...inでの注意点

1. プロトタイプチェーンの問題(初心者向け詳細解説)

プロトタイプチェーンとは?
JavaScriptでは、オブジェクトは他のオブジェクトから「プロパティを受け継ぐ」仕組みがあります。これをプロトタイプチェーンと呼びます。

// 基本的な例:親オブジェクトの作成
const parentObject = {
    inherited: 'これは親から来たプロパティ',
    parentMethod: function() {
        return 'parent method';
    }
};

// 子オブジェクトを作成(親のプロパティを受け継ぐ)
const childObject = Object.create(parentObject);
childObject.own = 'これは子自身のプロパティ';

// 子オブジェクトの状況を確認
console.log(childObject.own);        // '子自身のプロパティ'
console.log(childObject.inherited);  // '親から来たプロパティ' (受け継いだもの)

for...in の問題:受け継いだプロパティも列挙される

// ❌ for...in は親から受け継いだプロパティも出力する
console.log('=== for...in の結果 ===');
for (const key in childObject) {
    console.log(`${key}: ${childObject[key]}`);
}
// 出力:
// own: これは子自身のプロパティ
// inherited: これは親から来たプロパティ    ← 受け継いだもの
// parentMethod: function() { return 'parent method'; }  ← 受け継いだもの

// 期待していた結果:子自身のプロパティ('own')のみ

実際の開発でよくある問題例

// よくある開発シーン:ユーザー設定の処理
const defaultSettings = {
    theme: 'light',
    language: 'ja',
    notifications: true
};

// ユーザーの個別設定(デフォルト設定を継承)
const userSettings = Object.create(defaultSettings);
userSettings.userId = 123;
userSettings.customTheme = 'dark';  // ユーザーがカスタマイズした部分

// ❌ ユーザーがカスタマイズした設定のみを保存したいが...
const settingsToSave = {};
for (const key in userSettings) {
    settingsToSave[key] = userSettings[key];
}
console.log(settingsToSave);
// 結果: { 
//   userId: 123, 
//   customTheme: 'dark',
//   theme: 'light',        ← デフォルト設定も含まれてしまう
//   language: 'ja',        ← デフォルト設定も含まれてしまう
//   notifications: true    ← デフォルト設定も含まれてしまう
// }

解決方法1:hasOwnProperty() を使用

// ✅ 子オブジェクト自身のプロパティのみ取得
console.log('=== hasOwnProperty で修正 ===');
for (const key in childObject) {
    if (childObject.hasOwnProperty(key)) {
        console.log(`${key}: ${childObject[key]}`);
    }
}
// 出力: own: これは子自身のプロパティ (期待通り)

// 実用例:ユーザー設定の正しい処理
const correctSettingsToSave = {};
for (const key in userSettings) {
    if (userSettings.hasOwnProperty(key)) {
        correctSettingsToSave[key] = userSettings[key];
    }
}
console.log(correctSettingsToSave);
// 結果: { userId: 123, customTheme: 'dark' } (期待通り)

解決方法2:Object.hasOwn() を使用(ES2022、推奨)

// ✅ より新しい方法(hasOwnProperty より安全)
for (const key in userSettings) {
    if (Object.hasOwn(userSettings, key)) {
        console.log(`${key}: ${userSettings[key]}`);
    }
}

なぜ Object.hasOwn() の方が良いのか?

// hasOwnProperty が上書きされている場合の問題
const problematicObject = {
    name: 'test',
    hasOwnProperty: null  // hasOwnProperty が上書きされている
};

// ❌ これはエラーになる
for (const key in problematicObject) {
    if (problematicObject.hasOwnProperty(key)) {  // エラー!
        console.log(key);
    }
}

// ✅ Object.hasOwn() は安全
for (const key in problematicObject) {
    if (Object.hasOwn(problematicObject, key)) {  // 正常動作
        console.log(key);
    }
}

最も推奨される解決方法:Object.entries() + for...of

// ✅ 最推奨:プロトタイプチェーンの問題を根本的に回避
console.log('=== Object.entries + for...of ===');
for (const [key, value] of Object.entries(userSettings)) {
    console.log(`${key}: ${value}`);
}
// Object.entries() は自動的に「自身のプロパティのみ」を取得するため、
// プロトタイプチェーンを気にする必要がない

// 実用例:設定の安全な処理
const safeSettingsToSave = Object.fromEntries(
    Object.entries(userSettings)
);
console.log(safeSettingsToSave);
// 結果: { userId: 123, customTheme: 'dark' } (完璧!)

まとめ:なぜfor...inを避けるべきか

  1. 予期しないプロパティの混入:親オブジェクトのプロパティも処理してしまう
  2. 追加の条件チェックが必要hasOwnPropertyObject.hasOwnの記述が毎回必要
  3. コードの複雑化:単純な処理でも余計な条件分岐が必要
  4. バグの温床:プロトタイプチェーンを理解していない開発者がミスを犯しやすい

2. 配列では使用を避ける

const numbers = [1, 2, 3];
numbers.customProperty = 'added later';

// ❌ 配列で for...in を使用(予期しない結果)
for (const index in numbers) {
    console.log(index); // '0', '1', '2', 'customProperty'
}

// ✅ 配列は for...of を使用
for (const value of numbers) {
    console.log(value); // 1, 2, 3 のみ
}

for...inの適切な代替手段

const user = { name: 'Alice', age: 25, city: 'Tokyo' };

// Object.keys() + for...of
for (const key of Object.keys(user)) {
    console.log(`${key}: ${user[key]}`);
}

// Object.entries() + for...of
for (const [key, value] of Object.entries(user)) {
    console.log(`${key}: ${value}`);
}

// Object.values() + for...of(値のみ必要な場合)
for (const value of Object.values(user)) {
    console.log(value);
}

オブジェクト処理の推奨パターン

Object.entries() + for...of の活用

基本的な使用法

const user = {
    name: 'Alice',
    age: 25,
    city: 'Tokyo',
    isActive: true
};

// キーと値の両方を取得
for (const [key, value] of Object.entries(user)) {
    console.log(`${key}: ${value}`);
}
// 出力: name: Alice, age: 25, city: Tokyo, isActive: true

条件付き処理での活用

const config = {
    apiUrl: 'https://api.example.com',
    timeout: 5000,
    retries: 3,
    debug: false,
    secretKey: 'abc123'
};

// 設定値の検証
for (const [key, value] of Object.entries(config)) {
    // 数値の範囲チェック
    if (key === 'timeout' && value > 10000) {
        console.warn(`${key}の値が大きすぎます: ${value}`);
        break; // 問題があれば早期終了
    }
    
    // 機密情報のマスク
    if (key.includes('secret') || key.includes('key')) {
        console.log(`${key}: ${'*'.repeat(value.length)}`);
        continue; // 次の項目へスキップ
    }
    
    console.log(`${key}: ${value}`);
}

フォームデータの処理

const formData = {
    username: 'john_doe',
    email: 'john@example.com',
    password: 'password123',
    confirmPassword: 'password123',
    terms: true
};

// バリデーション処理
const errors = {};
for (const [field, value] of Object.entries(formData)) {
    // 必須フィールドチェック
    if (!value && field !== 'terms') {
        errors[field] = `${field}は必須です`;
        continue;
    }
    
    // メールフォーマットチェック
    if (field === 'email' && !value.includes('@')) {
        errors[field] = 'メールアドレスの形式が正しくありません';
        continue;
    }
    
    // パスワード確認
    if (field === 'confirmPassword' && value !== formData.password) {
        errors[field] = 'パスワードが一致しません';
        break; // 重要なエラーなので早期終了
    }
}

if (Object.keys(errors).length > 0) {
    console.error('バリデーションエラー:', errors);
}

API レスポンスの処理

const apiResponse = {
    status: 'success',
    data: {
        id: 123,
        name: 'Product A',
        price: 1000
    },
    meta: {
        timestamp: '2024-01-01T00:00:00Z',
        version: '1.0'
    }
};

// レスポンスデータの変換
const cleanedData = {};
for (const [key, value] of Object.entries(apiResponse)) {
    // meta情報は除外
    if (key === 'meta') {
        continue;
    }
    
    // データの正規化
    if (key === 'data' && typeof value === 'object') {
        cleanedData[key] = value;
    } else if (key === 'status' && value === 'success') {
        cleanedData.isSuccess = true;
    }
}

環境変数の処理

const envConfig = {
    NODE_ENV: 'production',
    API_URL: 'https://api.example.com',
    DEBUG_MODE: 'false',
    MAX_CONNECTIONS: '100',
    FEATURE_FLAG_A: 'true'
};

// 型変換と設定適用
const processedConfig = {};
for (const [key, value] of Object.entries(envConfig)) {
    // 数値変換
    if (key.includes('MAX_') || key.includes('_PORT')) {
        processedConfig[key] = parseInt(value, 10);
        continue;
    }
    
    // 真偽値変換
    if (value === 'true' || value === 'false') {
        processedConfig[key] = value === 'true';
        continue;
    }
    
    // 開発環境でのみ有効な設定をスキップ
    if (envConfig.NODE_ENV === 'production' && key.startsWith('DEBUG_')) {
        continue;
    }
    
    processedConfig[key] = value;
}

その他のObject系メソッドとの使い分け

const data = { a: 1, b: 2, c: 3, d: 4 };

// キーのみ必要な場合
for (const key of Object.keys(data)) {
    console.log(`キー: ${key}`);
}

// 値のみ必要な場合
for (const value of Object.values(data)) {
    console.log(`値: ${value}`);
}

// キーと値の両方が必要な場合(推奨)
for (const [key, value] of Object.entries(data)) {
    console.log(`${key}: ${value}`);
}

パフォーマンス比較

実行速度の比較

const largeArray = Array.from({ length: 1000000 }, (_, i) => i);

// パフォーマンステスト関数
function timeTest(name, fn) {
    const start = performance.now();
    fn();
    const end = performance.now();
    console.log(`${name}: ${end - start}ms`);
}

// 各ループのパフォーマンス測定
timeTest('for...of', () => {
    for (const value of largeArray) {
        // 処理
    }
});

timeTest('forEach', () => {
    largeArray.forEach(value => {
        // 処理
    });
});

timeTest('従来のforループ', () => {
    for (let i = 0; i < largeArray.length; i++) {
        const value = largeArray[i];
        // 処理
    }
});

一般的なパフォーマンス傾向

// 速度順序(一般的な傾向)
// 1. 従来のforループ(最高速)
// 2. for...of(高速、可読性とのバランス良好)
// 3. forEach(やや遅い、関数呼び出しのオーバーヘッド)

// ただし、実用的な差は微小で、可読性とメンテナンス性を優先すべき

実践的な使い分けガイド

シチュエーション別の選択指針

1. 配列の全要素を処理したい場合

const products = [
    { name: 'laptop', price: 1000 },
    { name: 'mouse', price: 50 },
    { name: 'keyboard', price: 100 }
];

// ✅ 単純な処理なら forEach
products.forEach(product => {
    console.log(`${product.name}: $${product.price}`);
});

// ✅ 条件分岐や早期終了が必要なら for...of
for (const product of products) {
    if (product.price > 500) {
        console.log(`高額商品: ${product.name}`);
        break; // 最初の高額商品で終了
    }
}

2. 条件に合致する要素を検索したい場合

const users = [
    { id: 1, name: 'Alice', active: true },
    { id: 2, name: 'Bob', active: false },
    { id: 3, name: 'Charlie', active: true }
];

// ❌ forEach では早期終了できない
let foundUser = null;
users.forEach(user => {
    if (user.id === 2) {
        foundUser = user;
        // ここで終了したいが、続行される
    }
});

// ✅ for...of なら見つかった時点で終了
for (const user of users) {
    if (user.id === 2) {
        console.log('見つかりました:', user);
        break; // 効率的に終了
    }
}

// より適切な方法:find() メソッドを使用
const targetUser = users.find(user => user.id === 2);

3. 非同期処理を含む場合

const apiUrls = ['/api/users', '/api/products', '/api/orders'];

// ❌ forEach + async/await(並列実行になる)
apiUrls.forEach(async (url) => {
    const response = await fetch(url);
    console.log(await response.json());
});

// ✅ for...of + async/await(順次実行)
for (const url of apiUrls) {
    try {
        const response = await fetch(url);
        const data = await response.json();
        console.log(data);
    } catch (error) {
        console.error(`Error fetching ${url}:`, error);
        break; // エラー時に処理を停止可能
    }
}

// 並列処理が必要な場合
const promises = apiUrls.map(url => fetch(url));
const responses = await Promise.all(promises);

4. オブジェクトのプロパティを処理したい場合

const config = {
    apiUrl: 'https://api.example.com',
    timeout: 5000,
    retries: 3,
    debug: false
};

// ✅ Object.entries() + for...of(推奨)
for (const [key, value] of Object.entries(config)) {
    console.log(`${key}: ${value}`);
    
    // 設定検証など
    if (key === 'timeout' && value > 10000) {
        console.warn('タイムアウト値が大きすぎます');
    }
}

// for...in も可能だが、上記の方が明確
for (const key in config) {
    if (Object.hasOwn(config, key)) {
        console.log(`${key}: ${config[key]}`);
    }
}

React/Vue.jsでの実用例

// React でのリスト描画
function ProductList({ products }) {
    return (
        <ul>
            {products.map((product, index) => (
                <li key={product.id}>{product.name}</li>
            ))}
        </ul>
    );
}

// Vue.js でのデータ処理
export default {
    computed: {
        activeUsers() {
            // 条件処理が必要な場合は for...of
            const result = [];
            for (const user of this.users) {
                if (user.active) {
                    result.push(user);
                    if (result.length >= 10) break; // 最大10件で打ち切り
                }
            }
            return result;
        }
    }
}

まとめ:実践での活用指針

迷った時の判断基準

処理を途中で止める可能性がある?
├─ YES → for...of を選択
└─ NO → forEach も選択肢だが、将来の変更を考慮して for...of を推奨

オブジェクトを処理する?
├─ YES → Object.entries() + for...of
└─ NO → 配列なら for...of

非同期処理を含む?
├─ YES → for...of(必須)
└─ NO → for...of または forEach

パフォーマンスが最重要?
├─ YES → 従来の for ループ
└─ NO → 可読性重視で for...of

変更に強いコード設計

for...of を基本とする理由

  • 後から条件分岐や早期終了が必要になっても、コードの大幅な書き換えが不要
  • 非同期処理への対応が容易
  • 一貫した書き方でチーム開発での混乱を防止

実際の開発シーン例

// 最初は単純な全件処理
items.forEach(item => console.log(item.name));

// 後で条件追加が必要になった場合...
// forEach では対応困難 → 全体を書き換え必要

// for...of なら簡単に対応可能
for (const item of items) {
    if (item.category === 'electronics') {
        console.log(item.name);
        break; // 最初の1件だけ処理して終了
    }
}
1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?