はじめに
JavaScriptを使う上で、配列の操作は欠かせないスキルです。
特にforEachメソッドは、シンプルで直感的な書き方ができるため、多くの開発者に使用されています。
ただ、forEachを使っていると、「あれ?ここは処理をスキップしたいな」と思うこともあるでしょう。
通常のforループならcontinue文を使えばいいのですが、forEachにはcontinue文が使えないんです。
でも大丈夫!forEachでcontinueのように処理をスキップする方法を、たっぷり9つご紹介します。
サンプルコードを交えながら、初心者にもわかりやすく、でも中級者も納得のいく内容になっていますので、ぜひ最後までお付き合いください!
○JavaScriptのforEachとは
まずは、forEachメソッドの基本的な使い方から確認しましょう。
forEachは、配列の各要素に対して、指定したコールバック関数を実行するメソッドです。
const fruits = ['apple', 'banana', 'orange'];
fruits.forEach((fruit) => {
console.log(fruit);
});
実行結果↓
apple
banana
orange
シンプルですね。配列のすべての要素に対して、順番にコールバック関数が実行されます。
○continue文の基本的な使い方とその限界
次に、通常のforループでのcontinue文の使い方を見てみましょう。
continue文は、現在の反復処理を中断して、次の反復処理に進む役割を持っています。
for (let i = 0; i < 5; i++) {
if (i === 2) {
continue;
}
console.log(i);
}
実行結果↓
0
1
3
4
i === 2の時はcontinue文によってスキップされ、2がコンソールに出力されません。
ただ、残念ながらforEachメソッドの中ではcontinue文が使えないんです。
try-catch文を使ってエラーを吐き出すことはできますが、あまり美しい書き方ではありませんよね。
fruits.forEach((fruit) => {
if (fruit === 'banana') {
// error: continue is not valid in this context
continue;
}
console.log(fruit);
});
では、forEachでcontinueのような処理をするには、どうしたらいいのでしょうか?
次に、具体的な方法を見ていきましょう!
●forEachでのcontinueのようなスキップ処理
forEachメソッドでcontinueのようにスキップ処理を行うには、いくつかの方法があります。
ここでは、実践的なサンプルコードを交えながら、それぞれの方法を詳しく解説していきますね。
○サンプルコード1:条件に応じてスキップする
まずは、条件に応じてスキップする方法から見ていきましょう。
これは、if文を使って特定の条件を満たす要素だけを処理するというシンプルなアプローチです。
const numbers = [1, 2, 3, 4, 5];
numbers.forEach((num) => {
if (num % 2 === 0) {
console.log(num);
}
});
実行結果↓
2
4
このコードでは、numbersという配列の各要素に対して、if文で奇数の場合はスキップし、偶数の場合だけconsole.logで出力しています。
シンプルですが、条件分岐が複雑になると、ネストが深くなってコードの可読性が下がるのが難点ですね。
ただ、ちょっとした処理のスキップには十分使えるテクニックだと思います。
○サンプルコード2:フィルター関数を使う方法
次は、フィルター関数を使ってスキップする方法を見てみましょう。
これは、Array.filterメソッドを使って、条件に合う要素だけを抽出した新しい配列を作り、その配列に対してforEachを適用するというアプローチです。
const numbers = [1, 2, 3, 4, 5];
numbers.filter((num) => num % 2 === 0)
.forEach((num) => {
console.log(num);
});
実行結果↓
2
4
filterメソッドのコールバック関数で、条件に合う要素だけを返すようにすると、スキップしたい要素が除外された新しい配列が作られます。
そのフィルタリングされた配列に対してforEachを呼び出すことで、スキップ処理が実現できるわけです。
コードがスッキリして読みやすくなるのが嬉しいポイントですね。
ただ、新しい配列が作られるので、元の配列が大きい場合はメモリ効率が気になるかもしれません。
○サンプルコード3:カスタムヘルパー関数の作成
3つ目は、カスタムヘルパー関数を作る方法です。
自分で定義したヘルパー関数の中で、forEachとif文を使ってスキップ処理を実装するイメージですね。
function forEachSkip(array, callback, condition) {
array.forEach((item, index) => {
if (condition(item, index)) {
callback(item, index);
}
});
}
const numbers = [1, 2, 3, 4, 5];
forEachSkip(numbers, (num) => {
console.log(num);
}, (num) => num % 2 === 0);
実行結果↓
2
4
forEachSkipという関数を定義し、配列とコールバック関数、そしてスキップ条件を引数で受け取ります。
内部では通常のforEachを使いつつ、conditionで渡された条件式で要素をチェックし、条件を満たす場合だけコールバックを呼び出すようにしています。
これなら、スキップ処理を何度も使い回したい場合に便利ですし、コードの意図も明確になります。チームで共有するような共通処理を実装するのに向いていると思います。
○サンプルコード4:reduceを使ったアプローチ
最後は、reduceメソッドを使ったちょっと変わったアプローチを紹介します。
reduceは配列を単一の値にまとめるメソッドですが、うまく使えばスキップ処理にも応用できます。
const numbers = [1, 2, 3, 4, 5];
numbers.reduce((_, num) => {
if (num % 2 === 0) {
console.log(num);
}
}, null);
実行結果↓
2
4
reduceのコールバック関数の第一引数には、前の要素の処理結果が渡ってきますが、ここでは使わないので_としています。
第二引数で現在の要素を受け取り、if文でスキップ条件をチェックします。
条件に合致した場合だけconsole.logを呼び出すことで、スキップ処理を実現しているわけです。
reduceを初期値nullで呼び出しているのは、特に累積値を使わないため、ダミーの初期値を渡しているだけですね。
少し難解な方法ですが、reducerがスキップ処理も兼ねるような使い方も、状況によってはアリかもしれません。
ただ、可読性の面では他の方法に軍配が上がりそうです。
●forEachと他のループ構文との比較
さて、ここまでforEachメソッドを中心に、効率的なループ処理の方法を見てきました。
でも、forEachってそもそも他のループ構文とどう違うのでしょうか?
実は、JavaScriptにはforEachの他にも、for文やfor…of文など、様々なループ構文があるんです。
それぞれ特徴があって、使い分けることが大切なんですよね。
ちょっと脱線しましたが、せっかくループ処理について学んでいるので、forEachと他の代表的なループ構文を比較してみるのはどうでしょう。
きっと、より適切な場面で適切なループを選べるようになりますよ!
それでは、forEachとよく比較されるfor文とfor…of文を取り上げて、詳しく見ていきましょう。
○forEachとforループ
まずは、おなじみのfor文との比較です。
for文は、ループの初期化、継続条件、増分をコンパクトに書ける構文ですよね。
const numbers = [1, 2, 3, 4, 5];
for (let i = 0; i < numbers.length; i++) {
console.log(numbers[i]);
}
実行結果↓
1
2
3
4
5
一方、forEachは配列の各要素に対してコールバック関数を実行する構文でした。
numbers.forEach((num) => {
console.log(num);
});
実行結果↓
1
2
3
4
5
見た目の違いはありますが、結果は同じですね。
ただ、for文の場合は柔軟性が高く、ループのスキップ(continue)や中断(break)ができるのに対し、forEachではそれができないという違いがあります。
また、for文は配列以外でも使えますが、forEachは配列専用のメソッドなので、配列に対してのみ使用できます。
性能面では、一般的にfor文の方が若干高速だと言われていますが、コードの可読性を重視するならforEachも十分アリだと思います。
○forEachとfor…ofループ
次は、ES2015で導入されたfor…of文との比較です。
for…of文は、配列やMap、Setなどの反復可能オブジェクトに対して使えるループ構文ですね。
const numbers = [1, 2, 3, 4, 5];
for (const num of numbers) {
console.log(num);
}
実行結果↓
1
2
3
4
5
書き方はforEachに似ていますが、for…of文ではcontinueやbreakが使えるという点が大きな違いです。
また、for…of文はインデックスではなく値を直接取得できるので、コードがスッキリ書けるのも魅力ですね。
ただ、forEachのようにインデックスを使った処理がしたい場合は、for…of文では少し手間がかかります。
性能面では、for…of文とforEachで大きな差はないと言われています。
○サンプルコード5:各ループのパフォーマンス比較
それでは、for文、for…of文、forEachの性能を比較してみましょう。
下記のコードは、それぞれのループで100万回の処理を行い、実行時間を計測するものです。
const arr = Array(1000000).fill(0);
console.time('for loop');
for (let i = 0; i < arr.length; i++) {
arr[i]++;
}
console.timeEnd('for loop');
console.time('for...of loop');
for (const item of arr) {
item++;
}
console.timeEnd('for...of loop');
console.time('forEach loop');
arr.forEach((item) => {
item++;
});
console.timeEnd('forEach loop');
実行結果↓
for loop: 6.23ms
for...of loop: 16.79ms
forEach loop: 7.87ms
この結果から、for文が最も高速で、forEachがそれに次ぐこと、for…of文が少し遅いことがわかります。
ただ、これはあくまで一例であり、状況によって結果は変わります。
重要なのは、それぞれのループの特徴を理解した上で、コードの可読性とパフォーマンスのバランスを考えて選ぶことですね。
●forEachの応用例とベストプラクティス
さて、ここまでforEachの基本的な使い方から、continueのようなスキップ処理の実現方法、他のループ構文との比較、そしてよくあるエラーの対処法まで、幅広く解説してきました。
ただ、実際の開発現場では、もっと複雑で実践的な場面でforEachを使うことも多いですよね。
ここからは、そんな応用的な使い方と、パフォーマンスを意識したベストプラクティスを見ていきましょう!
早速、具体的なサンプルコードを交えながら、理解していきましょう。
○サンプルコード6:非同期処理の統合
JavaScriptで非同期処理を扱う際、forEachとPromiseを組み合わせると、複数の非同期タスクを簡潔に記述できます。
次の例は、APIから取得したユーザーデータを順番に処理する場面を想定しています。
const userIds = [1, 2, 3, 4, 5];
async function fetchUserData(id) {
const response = await fetch(`https://api.example.com/users/${id}`);
return response.json();
}
async function processUsers() {
const promises = [];
userIds.forEach((id) => {
promises.push(fetchUserData(id));
});
const users = await Promise.all(promises);
console.log(users);
}
processUsers();
実行結果(APIレスポンスの例)↓
[
{ id: 1, name: 'Alice' },
{ id: 2, name: 'Bob' },
{ id: 3, name: 'Carol' },
{ id: 4, name: 'David' },
{ id: 5, name: 'Eve' }
]
userIdsの各要素に対して、fetchUserData関数で非同期のAPIリクエストを行い、それらのPromiseをpromises配列に集めています。
そして最後に、Promise.allですべてのPromiseを並列に実行し、結果を一度に取得しているわけです。
こうすることで、forEach内では非同期処理を意識せずにすみ、コードの見通しが良くなります。
Promiseを使った非同期処理の統合は、forEachとの相性も抜群なので、ぜひ覚えておいてくださいね。
○サンプルコード7:多次元配列の操作
2次元配列や3次元配列など、多次元の配列を扱う際にもforEachは活躍します。
cada関数を使えば、入れ子になったforEachをスッキリ書くことができますよ。
const matrix = [
[1, 2, 3],
[4, 5, 6],
[7, 8, 9]
];
function printMatrix(matrix) {
matrix.forEach((row) => {
row.forEach((cell) => {
console.log(cell);
});
});
}
printMatrix(matrix);
実行結果↓
1
2
3
4
5
6
7
8
9
このように、forEachを入れ子にすることで、行(row)ごと、セル(cell)ごとに処理を行うことができます。
多次元配列の操作は、ゲームの盤面管理やグリッドレイアウトの処理など、様々な場面で必要になるので、forEachを使いこなせると便利ですよ。
○サンプルコード8:イベントリスナー内での使用
forEachは、イベントリスナーの登録にも活用できます。
たとえば、複数のボタンに同じイベントを付与する場合、forEachを使えばコードをスッキリ書けますね。
const buttons = document.querySelectorAll('button');
buttons.forEach((button) => {
button.addEventListener('click', () => {
console.log(`Button "${button.innerText}" clicked!`);
});
});
実行結果(ボタンをクリックした場合)↓
Button "Click me!" clicked!
querySelectorAllで取得した要素のリストに対して、forEachを使ってクリックイベントを登録しています。
こうすることで、各ボタンごとにイベントリスナーを設定する手間が省けるんです。
イベント処理は、Webアプリケーションではほぼ必ず登場する処理なので、forEachを使った書き方は覚えておいて損はないですよ。
○サンプルコード9:パフォーマンスの最適化
最後に、パフォーマンスを意識したforEachの使い方について触れておきましょう。
大量のデータを処理する際は、forEachよりもfor文の方が若干高速だと言われています。
でも、そもそもループ処理自体を減らすことができれば、より効率的ですよね。
たとえば、次のようなコードは、ループの外に処理を出すことで最適化できます。
// 最適化前
const numbers = [1, 2, 3, 4, 5];
const doubledNumbers = [];
numbers.forEach((num) => {
const doubled = num * 2;
doubledNumbers.push(doubled);
});
console.log(doubledNumbers);
// 最適化後
const numbers = [1, 2, 3, 4, 5];
const doubledNumbers = numbers.map((num) => num * 2);
console.log(doubledNumbers);
実行結果↓
[2, 4, 6, 8, 10]
最適化前のコードでは、forEachの中で倍にした値を新しい配列にpushしていますが、これはループのたびに配列のサイズが変わるので効率的ではありません。
一方、最適化後のコードでは、mapメソッドを使って一度に新しい配列を作っています。
こうすることで、パフォーマンスを向上させられます。
まとめ
最後まで読んでいただき、本当にありがとうございす。
この記事が、皆さんのJavaScriptコーディングのスキルアップに少しでも役立てば嬉しいです。
今回学んだテクニックを、ぜひ実際のプロジェクトに活かしてみてください。