本記事はArray.forEachとjQuery.eachの要点を整理。forEachはbreak不可・戻り値なし、$.each/$(…).eachの終了条件、NodeList適用とIE11対策、for/for…ofの速度比較、map・filter・findの使い分けまで、実務の選択基準を示します。
目次
JavaScriptの「each」とは何か:forEachとjQuery.eachの違い
検索で「javascript each」と入力すると、配列やDOMを繰り返し処理する方法を探している方が多いはずです。まず押さえるべきは、標準のJavaScriptには「each」という名前のAPIは存在せず、ネイティブでは主に「forEach」が用意されているという点です。一方、「each」はjQueryが提供するメソッド名で、用途や呼び出し方、コールバックの引数順序、途中終了の可否などに違いがあります。
以下に、Array.prototype.forEach(以下、forEach)とjQueryの$.each/jQueryオブジェクト.each(以下、jQuery.each)の主要な相違点を整理します。
観点 | forEach(ネイティブ) | jQuery.each(ライブラリ) |
---|---|---|
提供元 | ECMAScript標準(ブラウザ・Node.jsに内蔵) | jQuery(外部ライブラリ) |
主な対象 | 配列、TypedArray、Map/Set、NodeListなどがそれぞれforEachを実装 | $.eachは配列/配列風/プレーンオブジェクト、 $(selector).eachはjQueryオブジェクト内のDOM要素 |
コールバック引数 | (value, index, array) | 配列/配列風: (index, value)、オブジェクト: (key, value) |
thisの扱い | 第2引数thisArgで明示指定可。未指定時はundefined(strict) | $.each: thisは現在の値(配列)または値(オブジェクト)。 $(…).each: thisは現在のDOM要素 |
途中終了 | 不可(break/returnで止められない) | 可能(return falseで中断、return trueでスキップ) |
疎配列(穴)の扱い | 欠番(empty slot)はスキップ | インデックスで走査するため欠番も呼ばれやすく、valueはundefinedになりがち |
戻り値 | undefined(チェーン不可) | $.eachは走査対象、$(…).eachはjQueryオブジェクト(メソッドチェーン可) |
実際の呼び出しも形が異なります。最小限の例で雰囲気を比較してみましょう。
// forEach(ネイティブ)
const arr = [10, 20, 30];
arr.forEach((value, index) => {
console.log(index, value);
});
// $.each(配列): 引数は(index, value)、return falseで中断
$.each(arr, function(index, value) {
console.log(index, value);
if (value === 20) return false; // ここでループ中断
});
// $(...).each(DOMコレクション): thisがDOM要素
$('.item').each(function(index, el) {
// this === el (DOM要素)
console.log(index, this.tagName);
});
要点として、jQueryを使っていないプロジェクトや純粋なECMAScriptで書きたい場合はforEachが第一候補になります。jQueryを導入しているコードベースでは、配列やオブジェクトの汎用走査に$.each、選択したDOM集合の操作には$(…).eachが簡潔です。ただし、「途中終了が必要か」「thisをどう扱うか」「疎配列をどう解釈するか」といった要件で振る舞いが異なるため、目的に合う手段を選びましょう。
まとめると、「javascript each」と検索して到達した場合でも、標準API名はforEachであり、jQuery環境ではeachという別系統のメソッド群が存在します。両者は似た用途でも設計思想と細部の挙動が異なるため、上の差分を意識して選択することが品質と可読性の向上につながります。
Array.prototype.forEachの基礎と活用
「javascript each」をネイティブで実現する代表的な手段がArray.prototype.forEachです。配列を副作用ベースで順に処理したいときに読みやすく、安全に記述できます。この章では、構文、引数、典型パターン、制約と回避策、仕様・互換性まで実務目線で整理します。
構文と戻り値の基本
// 構文
array.forEach(callbackFn[, thisArg])
// 例
const nums = [1, 2, 3];
nums.forEach((value, index, array) => {
console.log(index, value, array === nums); // 0 1 true ...
});
- callbackFnは各要素に対して一度ずつ実行されます(疎配列の穴は除く。後述)。
- 戻り値はundefinedです。mapのように新しい配列を返しません。
- メソッドチェーンでの変換目的には不向きです(副作用用途に限定)。
コールバックの引数(値・インデックス・配列)とthisArg
array.forEach(function(currentValue, index, array) {
// thisはthisArgで指定された値(通常関数の場合)
}, thisArg);
- currentValue: 現在処理中の要素の値
- index: 現在のインデックス
- array: forEachが呼ばれた元の配列そのもの(同じ参照)
- thisArg: コールバック内のthisとして使う値(通常関数で有効)
注意: アロー関数はthisを束縛しないため、thisArgを指定しても反映されません。thisを使う場合はfunctionキーワードを使うか、明示的に外側の値を参照してください。
const ctx = {multiplier: 10};
[1,2].forEach(function(v){ console.log(v * this.multiplier); }, ctx); // 10, 20
[1,2].forEach((v) => { /* thisArgは無視される */ });
forループからforEachへ書き換える手順
- ループ条件とカウンタ初期化を削除し、対象配列にforEachを呼び出す。
- 本体で使っていた配列アクセス arr[i] をコールバックのcurrentValueへ置換。
- iの使用箇所はindexに置換。
- 副作用(push/加算/DOM操作など)だけを残し、戻り値による伝搬が必要ならmap/filterなどに再検討。
// Before: for
const arr = [1,2,3];
let sum = 0;
for (let i = 0; i < arr.length; i++) {
sum += arr[i];
}
// After: forEach
let sum2 = 0;
arr.forEach((value) => { sum2 += value; });
典型パターンで学ぶforEach
配列の内容を順番に出力する
const items = ['A', 'B', 'C'];
items.forEach((item, i) => {
console.log(`${i + 1}. ${item}`);
});
オブジェクトをコピーする関数の実装
浅いコピー(シャローコピー)であればObject.keysとforEachの組み合わせが簡潔です。
function shallowClone(obj) {
const clone = {};
Object.keys(obj).forEach((key) => {
clone[key] = obj[key];
});
return clone;
}
const src = {a: 1, b: {c: 2}};
const dst = shallowClone(src); // bは参照共有(深いコピーではない)
深いコピーが必要な場合は再帰処理や構造化クローンを検討してください。
配列の平坦化を行う例
一段だけ平坦化する例です。必要に応じて再帰で多段に対応できます。
function flattenOnce(nested) {
const result = [];
nested.forEach((v) => {
if (Array.isArray(v)) result.push(...v);
else result.push(v);
});
return result;
}
console.log(flattenOnce([1, [2, 3], 4])); // [1, 2, 3, 4]
// 再帰的に完全平坦化
function flattenDeep(nested) {
const out = [];
nested.forEach((v) => {
if (Array.isArray(v)) out.push(...flattenDeep(v));
else out.push(v);
});
return out;
}
コールバック第3引数の活用
第三引数のarrayを使うと、配列全体の文脈に依存した計算が簡潔になります。
const scores = [80, 70, 90];
let avg = 0;
scores.forEach((v, i, arr) => {
avg += v / arr.length; // 長さを毎回参照できる
});
console.log(avg); // 80
// その場正規化(同じ配列を参照できるため可能)
scores.forEach((v, i, arr) => { arr[i] = v / 100; });
疎配列(穴あき配列)を扱う際の注意
forEachは存在しない要素(いわゆる「穴」)をスキップします。undefinedという値が入っている場合は呼び出されます。
const a = [1, , 3]; // インデックス1は「穴」
const b = [1, undefined, 3]; // インデックス1は値undefined
a.forEach((v, i) => console.log('a', i, v)); // a 0 1 / a 2 3 (1はスキップ)
b.forEach((v, i) => console.log('b', i, v)); // b 0 1 / b 1 undefined / b 2 3
- forEachは開始時点のlengthを固定し、後からlengthを伸ばしても新要素は処理されません。
- 未処理インデックスに対して値を追加・更新した場合は、その時点で存在していればコールバックが呼ばれます。
- 意図せず穴ができないよう、deleteよりもspliceを優先するのが安全です。
配列以外のオブジェクトにforEachを適用する方法
配列風オブジェクト(lengthと整数キーを持つ)にはFunction.prototype.callで借用できます。TypedArrayには独自のforEachもあります。
// arguments(配列風)にforEachを適用
function demo() {
Array.prototype.forEach.call(arguments, (v, i) => {
console.log(i, v);
});
}
demo('x', 'y');
// Array-likeをArrayに変換してから使う方法
const arrayLike = {0: 'a', 1: 'b', length: 2};
Array.from(arrayLike).forEach(console.log);
// TypedArray
new Uint8Array([1,2,3]).forEach((v) => console.log(v));
早期終了できない制約と回避策(some/every/for…of等)
forEachはbreak/returnでループを中断できません。条件達成で止めたい処理には別手段を使います。
- some: 条件成立時にtrueを返し、中断される
- every: 条件不成立時にfalseを返し、中断される
- for…of: break/continueが使える伝統的な制御が可能
// someで「見つかったら終了」
const found = [5, 8, 13].some((v) => {
if (v > 10) { console.log('hit:', v); return true; }
return false;
});
// for...ofで柔軟に中断
for (const v of [5, 8, 13]) {
if (v > 10) { console.log('hit:', v); break; }
}
例外throwでforEachを強制終了する手もありますが、可読性・コスト面で非推奨です。
仕様リファレンスへの導線
- MDN: Array.prototype.forEach(使用例・注意点)
- ECMAScript仕様: Array.prototype.forEach(アルゴリズム定義)
ブラウザ互換性と対応状況
- Array.prototype.forEachはES5(2011年以降の主要ブラウザ)でサポート。IEは9以降で利用可能。
- IE8以前や非常に古い環境を対象にする場合は、Polyfill(例: core-js)や代替ループ(for/for…of)を検討。
- モバイル・モダンブラウザでは広く安定して利用できます。
互換性要件が厳しいプロジェクトでは、ビルド時に自動Polyfill注入(Babel + @babel/preset-env + core-jsなど)を利用し、forEachを含むES機能のサポートを保証するのが現実的です。
DOMコレクションを反復処理する(NodeList/HTMLCollection)
DOMをまとめて操作したいとき、CSSセレクタで取得できるNodeListや、古くからあるHTMLCollectionをどのように反復処理(いわば「javascript each」)するかが鍵になります。ここでは、NodeList.forEachの基本から実用的なパターン、そして古いブラウザへの対応までを整理します。
querySelectorAllとNodeList.forEachの基本
document.querySelectorAll(selector)
は、条件に一致する要素の静的な集合(NodeList)を返します。NodeListは多くのモダンブラウザでforEach
をサポートしており、配列のforEach
と同様に「要素、インデックス、コレクション」の3引数でコールバックを受け取れます。
// 静的なNodeListを取得
const items = document.querySelectorAll('.item');
// NodeList.forEach で反復処理(javascript each 的な書き方)
items.forEach((el, index, list) => {
console.log(index, el.textContent, list === items); // true
});
ポイント:
- NodeListは「静的」。取得後にDOMが変わってもNodeListの内容は変化しません。
- HTMLCollection(例:
document.getElementsByClassName
やchildren
など)は多くのケースで「ライブ」。DOMの変化が即時に反映されます。 - HTMLCollectionは仕様上
forEach
を持ちません。反復処理するにはArray.from
やスプレッド構文で配列化する、またはArray.prototype.forEach.call
を使います。
// HTMLCollection(ライブ)を配列化して安全に反復処理
const imgs = document.getElementsByTagName('img'); // HTMLCollection
Array.from(imgs).forEach(img => {
img.loading = 'lazy';
});
実例で理解するDOMループ
要素のテキストを順に取得・出力する
一覧要素のテキストを順番に収集してログや配列にまとめる基本パターンです。NodeList.forEachを使うとシンプルに書けます。
// 例: <li class="todo">...</li> をすべて収集
const todos = document.querySelectorAll('li.todo');
const texts = [];
todos.forEach((li, i) => {
const text = li.textContent.trim();
console.log(`${i + 1}. ${text}`);
texts.push(text);
});
// 必要ならデータ属性や他の情報も同時に処理可能
// todos.forEach((li) => { console.log(li.dataset.id, li.textContent); });
注意:
- 空白や改行を除きたい場合は
textContent.trim()
を使います。 - DOMを頻繁に更新するより、まず配列に取り出してからまとめて扱う方がパフォーマンス上有利なことがあります。
指定した要素にクラスを追加する
条件に合う要素へクラスを追加・削除してスタイルや挙動を切り替えます。classList
は読みやすくバグも起きにくいAPIです。
// 例: data-active="true" の要素に is-active クラスを付与
const cards = document.querySelectorAll('.card');
cards.forEach(card => {
if (card.dataset.active === 'true') {
card.classList.add('is-active');
} else {
card.classList.remove('is-active');
}
});
// 条件に合う最初の1件だけ処理したい場合は some/every/for...of の利用も検討
補足:
- ライブなHTMLCollectionに対してクラスの追加・削除を行うと、反復中にコレクションの内容が変わる場合があります。安全のため配列化してからループするとよいでしょう。
- 大量の要素にクラスを付け替えるとリフローが増えます。必要に応じて親要素にクラスを付け、CSSで子を制御する戦略も有効です。
古いブラウザへの対応(IE11向けの書き方・Polyfill)
IE11はNodeList.prototype.forEach
をサポートしません。以下のいずれかで対処します。
- 古典的なforループを使う
Array.prototype.forEach.call(nodeList, cb)
で借用する- Polyfillを読み込む(軽量の自前実装でも可)
// 1) for ループ
var nodes = document.querySelectorAll('.item');
for (var i = 0; i < nodes.length; i++) {
var el = nodes[i];
// ...処理
}
// 2) Array#forEach を借用
var nodes = document.querySelectorAll('.item');
Array.prototype.forEach.call(nodes, function (el, i, list) {
// ...処理
});
// 3) NodeList.forEach の Polyfill(IE11向け)
if (window.NodeList && !NodeList.prototype.forEach) {
NodeList.prototype.forEach = function (callback, thisArg) {
thisArg = thisArg || window;
for (var i = 0; i < this.length; i++) {
callback.call(thisArg, this[i], i, this);
}
};
}
HTMLCollectionを扱う場合の注意:
- ライブコレクションを反復中に変更すると、要素が抜けたり重複処理になることがあります。処理前に配列化して固定化するのが安全です。
// HTMLCollection を静的配列に固定してから処理(IE11対応)
var list = document.getElementsByClassName('row');
var arr = Array.prototype.slice.call(list); // or Array.from(list) with polyfill
arr.forEach(function (el) {
// ...処理
});
classListが使えない環境へのフォールバック(古いIEを想定):
function addClass(el, name) {
if (el.classList) { el.classList.add(name); return; }
if ((' ' + el.className + ' ').indexOf(' ' + name + ' ') === -1) {
el.className = (el.className ? el.className + ' ' : '') + name;
}
}
まとめると、モダン環境ではquerySelectorAll
とNodeList.forEach
でシンプルに書けます。レガシー環境を考慮する場合は、配列メソッドの借用やPolyfill、または素直なforループに切り替えることで、安定した「javascript each」パターンを実現できます。
jQueryのeachを使った繰り返し処理
jQueryのeachは、配列・オブジェクト・DOMコレクションをシンプルに走査できるユーティリティです。コールバックの引数やthisの扱い、ループの中断・スキップなど、抑えるべき作法を理解すれば、日常的な「javascript each」の処理を安全かつ簡潔に書けます。以下では用途別に具体例と注意点をまとめます。
$.eachで配列・オブジェクトを走査する方法
グローバル関数の$.each(object, callback)
は、配列とオブジェクトの両方を走査できます。コールバックのシグネチャはfunction(indexOrKey, value)
で、this
は現在の値(value)になります。
// 配列の走査(indexは数値、valueは要素)
var nums = [10, 20, 30];
$.each(nums, function(index, value) {
console.log(index, value, this === value); // 0 10 true ...
});
// オブジェクトの走査(keyは文字列、valueはプロパティ値)
var user = { id: 123, name: "Taro", active: true };
$.each(user, function(key, value) {
console.log(key + ":", value);
});
- 配列ではindexが0からの連番、オブジェクトではkeyがプロパティ名になります。
- コールバック内の
this
は現在の値を指します。値に対してメソッド呼び出しをしたい場合に便利です。 - 注意:オブジェクトの走査は列挙可能なプロパティが対象です。プロトタイプ上の列挙可能プロパティが混ざる設計では、必要に応じて
if (Object.prototype.hasOwnProperty.call(obj, key)) { ... }
のように絞り込んでください。
// hasOwnPropertyで自身のプロパティのみに限定
var obj = Object.create({ inherited: 1 });
obj.own = 2;
$.each(obj, function(key, value) {
if (!Object.prototype.hasOwnProperty.call(obj, key)) return true; // 続行(スキップ)
console.log(key, value); // own 2 のみ
});
jQueryオブジェクト.eachでDOM要素を走査する方法
$(selector)
で得たjQueryオブジェクトは、メソッドの.each(callback)
で各DOM要素を巡回できます。コールバックのシグネチャはfunction(index, element)
で、this
は生のDOM要素になります。
// すべてのリスト項目に連番クラスを付与
$("ul.todo li").each(function(index, element) {
// this === element(生のDOM)
var $li = $(this); // jQueryメソッドを使うならラップする
$li.addClass("item-" + (index + 1));
});
this
は生の要素なので、そのままではjQueryメソッドは使えません。$(this)
でラップしてから利用します。- パフォーマンスのため、
$(this)
の結果は変数に保持し、同一反復内で使い回すのが定石です。 - 注意:
this
を使う場合、アロー関数(() => {}
)は避けてください。アロー関数はthis
を束縛しないため、意図通りに動きません。
// NG(thisが期待通りにならない)
$(".btn").each((i, el) => {
console.log(this); // 期待した要素ではない
});
// OK(通常のfunctionでthisが要素になる)
$(".btn").each(function(i, el) {
console.log(this); // クリックボタンのDOM要素
});
ループの中断とスキップの書き方(return false / return true)
$.each
と.each
は、コールバックの戻り値で制御できます。return false
でループ全体を中断、return true
で現在の反復のみをスキップ(続行)します。
// 最初に見つかったアクティブな要素だけに処理し、以降は中断
$(".item").each(function(i) {
if ($(this).is(".active")) {
$(this).addClass("found");
return false; // break
}
});
// 無効化された要素は処理せずスキップし、他は続ける
$.each([ {id:1, disabled:true}, {id:2}, {id:3} ], function(i, row) {
if (row.disabled) return true; // continue
console.log("process", row.id);
});
return false
は「即時に脱出」する強い制御です。見つかったら終わり、のような探索に有効です。return true
は「現在の要素だけ飛ばす」制御です。条件によって処理対象を絞るときに使います。
多次元配列・ネスト構造を巡回するコツ
多次元配列やオブジェクトが入り混じるネスト構造は、入れ子の$.each
か再帰処理で巡回します。用途に応じて実装を選びましょう。
// 1) 多次元配列を入れ子の$.eachで処理
var matrix = [[1,2], [3,4,5], [6]];
$.each(matrix, function(rowIndex, row) {
$.each(row, function(colIndex, value) {
console.log("r" + rowIndex, "c" + colIndex, value);
});
});
// 2) オブジェクト混在のネスト構造を再帰で走査(パス付きで出力)
var data = {
user: { id: 1, tags: ["admin", "owner"] },
settings: { theme: { color: "dark", contrast: true } }
};
function walk(node, path) {
if ($.isArray(node)) {
$.each(node, function(i, v) { walk(v, path.concat("[" + i + "]")); });
} else if ($.isPlainObject(node)) {
$.each(node, function(k, v) { walk(v, path.concat("." + k)); });
} else {
console.log(path.join("").replace(/^\./, ""), "=", node);
}
}
walk(data, []);
$.isArray
や$.isPlainObject
を使うと、分岐が簡潔になります。- 循環参照があり得る構造では、訪問済みノードの管理(例:配列に参照を保持して重複検出)で無限ループを防止してください。
- 処理の途中終了が必要なら、再帰関数の戻り値を見て上位の
$.each
からreturn false
で伝播させる設計にします。
// 3) 条件を満たす最初の値を見つけたら探索を打ち切る再帰
function findFirst(node, predicate) {
var found;
if ($.isArray(node)) {
$.each(node, function(i, v) {
found = findFirst(v, predicate);
if (found !== undefined) return false; // break
});
} else if ($.isPlainObject(node)) {
$.each(node, function(k, v) {
found = findFirst(v, predicate);
if (found !== undefined) return false; // break
});
} else if (predicate(node)) {
return node;
}
return found;
}
var result = findFirst(data, function(x) { return x === "owner"; });
console.log(result); // "owner"
脱jQuery:eachをネイティブ実装へ置き換える
既存コードからjQuery依存を外す第一歩は、javascript each(jQueryの$.each / $(selector).each)をネイティブAPIに置き換えることです。ここでは配列・オブジェクト・DOMコレクションそれぞれの「代替パターン」と、移行時にハマりやすい注意点を整理します。
$.eachの代替(Array.prototype.forEach / for…of / Object.keys など)
jQueryの$.each
は配列・配列風オブジェクト・通常オブジェクトすべてを一つのAPIで回せます。ネイティブへ置き換える際は、対象の型に応じて最適な構文を選びます。
- 配列(Array)を走査:
Array.prototype.forEach
またはfor...of
- 通常オブジェクト(連想配列)を走査:
Object.keys
/Object.values
/Object.entries
- 早期終了(break/continue)が必要:
for...of
(またはsome
/every
)
// jQuery
$.each([10, 20, 30], function(i, val) {
console.log(i, val);
});
// ネイティブ: forEach(配列)
[10, 20, 30].forEach(function(val, i, arr) {
console.log(i, val);
});
// ネイティブ: for...of(早期終了が必要な場合)
const nums = [10, 20, 30];
for (const [i, val] of nums.entries()) {
console.log(i, val);
if (val === 20) break; // 途中で抜ける
}
// jQuery(オブジェクト)
$.each({ a: 1, b: 2 }, function(key, val) {
console.log(key, val);
});
// ネイティブ: Object.entries
Object.entries({ a: 1, b: 2 }).forEach(function([key, val]) {
console.log(key, val);
});
// キーだけ/値だけが欲しい場合
Object.keys(obj).forEach(function(key) { /* ... */ });
Object.values(obj).forEach(function(val) { /* ... */ });
ポイント:
forEach
は「途中でbreak
できない」ため、$.each
のreturn false
相当はfor...of
で置き換えるのが簡潔です。Object.entries
は「自分が持つ列挙可能なプロパティ」のみを対象にします。$.each
は状況次第で継承プロパティを拾う可能性があるため、移行時に意図しないキーが落ちる/残る差異に注意します。
$(selector).eachの代替(NodeList.forEach / for…of)
jQueryオブジェクトに対する.each
は、ネイティブのDOMクエリで返るNodeList
等に対してforEach
やfor...of
を使う形に置き換えます。
// jQuery
$('.item').each(function(i, el) {
$(el).addClass('active').text(i);
});
// ネイティブ: NodeList.forEach
document.querySelectorAll('.item').forEach(function(el, i, list) {
el.classList.add('active');
el.textContent = i;
});
// ネイティブ: for...of(break/continue可能)
const items = document.querySelectorAll('.item');
let i = 0;
for (const el of items) {
el.classList.add('active');
el.textContent = i++;
if (i > 3) break; // 必要なら途中で抜ける
}
派生テクニック:
NodeList
を配列メソッドで処理したい場合はArray.from(nodeList)
やスプレッド[...nodeList]
で配列化します。- イベント付与や属性操作は
addEventListener
、classList
、dataset
などで置き換えられます。
置き換え時の注意点(thisの扱いとコールバックの違い)
jQueryのeachとネイティブの反復は、コールバックの「引数順序」と「this」の意味が異なります。移行時はここでのズレによるバグに注意してください。
- 引数の順序:
$.each(array, fn)
:fn(index, value)
array.forEach(fn)
:fn(value, index, array)
$(selector).each(fn)
:fn(index, element)
NodeList.forEach(fn)
:fn(element, index, list)
- thisの指す先:
- jQuery:
$.each
でも$(selector).each
でも、コールバック内のthis
は「現在の値/要素」。 - ネイティブ:
forEach
内のthis
はデフォルトundefined
(strict mode)。第2引数thisArg
で指定可能だが、一般には「引数で受け取った値(el
など)」を使うのが安全。
- jQuery:
- 早期終了とスキップ:
- jQuery:
return false
で中断、return true
でスキップ。 - ネイティブ:
forEach
では不可。中断・スキップが必要ならfor...of
でbreak
/continue
を使う。
- jQuery:
- 書き換えの実際:
// jQuery $('.btn').each(function(i) { if (i === 0) return true; // スキップ if (i === 5) return false; // 中断 $(this).addClass('ready'); }); // ネイティブ(同等の挙動) const btns = document.querySelectorAll('.btn'); let i = 0; for (const el of btns) { if (i === 0) { i++; continue; } // スキップ if (i === 5) break; // 中断 el.classList.add('ready'); i++; }
- アロー関数の罠: jQuery時代の
this
依存コードをそのままアロー関数に置き換えるとthis
が変わるため、必ず「引数で受ける」形に改めるか、通常のfunction
でthisArg
/bind
を使う。
以上を押さえれば、javascript eachの置き換えは機械的に進められます。配列はforEach
、中断が必要ならfor...of
、オブジェクトはObject.entries
、DOMはNodeList.forEach
という原則で移行しましょう。
繰り返し構文の比較と選び方
同じ「繰り返し」でも用途やデータ構造により最適解は変わります。ここでは、for / for…of / for…in / forEach の違いを軸に、高階関数との住み分け、そして実務で迷わないためのパフォーマンス判断基準を整理します。検索キーワードとして多い「javascript each」にも触れつつ、可読性と意図の明確さを優先した選び方を示します。
for / for…of / for…in / forEachの違いと使い分け
まずは言語機能としての性質と、向いている場面を明確にします。
-
for(従来のカウンタループ)
- 配列のインデックス制御が必要、ループの途中で break / continue を使いたい、高パフォーマンスを狙いたい時に有効。
- length のキャッシュや逆順ループなどの最適化がしやすい。
-
for…of(値を順に取り出す)
- 配列・文字列・Map・Set・NodeList など「iterable」の値を順に取得。break / continue 可。
- 「値」が欲しい時に直感的。スパース配列では欠損要素も
undefined
として巡回される点が forEach と異なる。
-
for…in(キー名を列挙する)
- オブジェクトの列挙可能なプロパティ名を走査。配列には非推奨(順序保証なし・継承プロパティ混入の恐れ)。
- 必要なら
Object.prototype.hasOwnProperty.call(obj, key)
で自前のキーだけに限定。
-
Array.prototype.forEach(副作用中心の反復)
- 配列の各要素に対してコールバックを実行。戻り値は常に
undefined
。 - break / return による「早期終了」ができない。スパース配列の穴はスキップされる。
- 副作用(ログ出力、DOM更新、集計など)に適するが、値の生成には map/filter の方が意図が明確。
- 配列の各要素に対してコールバックを実行。戻り値は常に
// 同じ配列を各ループで比較
const arr = ['a', , 'c']; // インデックス1が欠損(スパース)
// for:インデックス制御が必要なとき
for (let i = 0; i < arr.length; i++) {
console.log('for', i, arr[i]); // 欠損は undefined として参照される
}
// for...of:値を直感的に取得(欠損も undefined として巡回)
for (const v of arr) {
console.log('for...of', v); // 'a', undefined, 'c'
}
// forEach:欠損要素はスキップ
arr.forEach((v, i) => {
console.log('forEach', i, v); // 0:'a', 2:'c'(1は呼ばれない)
});
// for...in:キー(インデックス文字列)を列挙(配列では推奨しない)
for (const k in arr) {
if (Object.prototype.hasOwnProperty.call(arr, k)) {
console.log('for...in', k); // '0', '2'
}
}
指針
- 配列の値が欲しい+早期終了したい → for / for…of
- 配列の全要素に副作用を適用 → forEach
- オブジェクトのプロパティ列挙 → Object.keys/values/entries と for…of/forEach の併用(for…in は慎重に)
map / filter / findなどの高階関数との住み分け
「何をするか」が明確で、ループの副作用を避けたい時は高階関数が読みやすさと意図の明確さで有利です。
- map: 配列を同じ長さで変換し新しい配列を返す(純粋変換)
- filter: 条件を満たす要素だけを残す(部分集合の抽出)
- find / findIndex: 最初に条件を満たす要素(インデックス)を返す(短絡評価)
- some / every: 真偽判定を短絡評価で返す(ループを早期終了)
- reduce: 畳み込みで1つの値(または構造)に集約
// 変換:forEach ではなく map
const prices = [100, 200, 300];
const withTax = prices.map(p => Math.round(p * 1.1));
// 抽出:filter
const even = prices.filter(p => p % 200 === 0);
// 一致探索:find(見つかったら即終了)
const firstOver250 = prices.find(p => p > 250);
// 判定:some/every(短絡あり)
const hasFree = prices.some(p => p === 0);
const allPositive = prices.every(p => p > 0);
// 集約:reduce
const sum = prices.reduce((acc, p) => acc + p, 0);
住み分けのコツ
- 新しい配列を生成する意図がある → map / filter
- 条件成立で止めたい → find / some / every(forEach は止められない)
- 副作用中心(ログ、DOM更新、外部状態更新) → forEach / for / for…of
- 複雑な変換+パフォーマンス重視 → reduce か単一の for/for…of でまとめる(map と filter の連鎖を減らす)
パフォーマンス比較の傾向と実務での判断基準
エンジン最適化やデータ特性で差は変動しますが、傾向と意思決定の基準は次の通りです。
- 一般傾向
- 関数呼び出しコストがあるぶん、forEach / map / filter は従来の for や for…of より遅くなりがち。
- for は最も低コストで安定。for…of はイテレータのオーバーヘッドがあるが読みやすさとのバランスが良い。
- メモリアロケーションが増えるため、map / filter の多段チェーンは大規模データで不利になりやすい。
- スパース配列は最適化を阻害することがある(穴のない配列か TypedArray が安定)。
- 測定の原則
- 「読みやすさ優先」→「本当に遅い箇所を計測」→「ピンポイント最適化」の順で進める。
- ブラウザ・Node.js・データ量で結果が変わる。必ず実運用に近い条件でプロファイルする。
- 実務での判断基準
- 1回あたりの配列サイズが小〜中規模(〜数千)なら、意図が明確な API(map/filter/find/some)を優先。
- ホットパス(フレームごと・大量データ処理)では、for または for…of で早期終了と副作用を最小化。
- 複数の map/filter をチェーンしている場合、必要に応じて1回の for / for…of に統合して中間配列を削減。
- オブジェクト列挙は Object.keys/values/entries + for…of/forEach を基本に、順序保証や所有プロパティの明確化で安全性を担保。
まとめると、可読性と意図の明確化のために高階関数や for…of を基本とし、性能が問題化した局所だけを for に置き換えるのが、現実的かつ堅実な「javascript each」の選び方です。