reduce とは何か
reduce は「配列のすべての要素を順に処理して、“ひとつの値”にまとめる(集約する)」メソッドです。合計、最大値、オブジェクトの構築、グルーピングなど“ひとつにまとめ上げる”処理に向いています。ここが重要です:reduce は非破壊で元配列を変えません。核となるのは“アキュムレータ(蓄積値)”と“初期値”の設計で、これが結果の正確さと安全性を決めます。
基本構文とアキュムレータ(蓄積値)
使い方の基本(合計の例)
const nums = [1, 2, 3, 4];
const sum = nums.reduce((acc, n) => acc + n, 0);
console.log(sum); // 10
JavaScript- acc: これまでの集計結果(アキュムレータ)
- n: 今処理している要素
- 初期値 0: 最初の acc の値
ここが重要です:“初期値”を明示すると、空配列や型の不一致に強く、バグが減ります。
初期値を省略した場合の挙動
const nums = [1, 2, 3];
const sum = nums.reduce((acc, n) => acc + n); // 初期値なし
console.log(sum); // 6(最初の acc は配列の先頭 1 から始まる)
JavaScriptここが重要です:初期値を省略すると“先頭要素が初期 acc”になります。空配列で使うと TypeError、要素型がオブジェクト等だと意図しない型で計算が始まるため、原則“初期値は必ず与える”が安全です。
コールバックに渡される引数と thisArg
4引数を取り扱える(精密な集約に使う)
const arr = [5, 12, 8];
const result = arr.reduce((acc, value, index, array) => {
// acc: 蓄積値
// value: 現在の要素
// index: 現在のインデックス
// array: 元の配列(必要なら参照)
return acc + (value % 2 === 0 ? value : 0); // 偶数の合計
}, 0);
console.log(result); // 20
JavaScriptここが重要です:インデックスや配列全体の参照を使えるため、位置条件や“次の要素”と比較する集計などに応用できます。
thisArg で文脈を渡す(必要なときだけ)
const cfg = { limit: 50 };
const xs = [10, 60, 40];
const count = xs.reduce(function (acc, x) {
return acc + (x > this.limit ? 1 : 0);
}, 0, cfg); // third argument は reduce にはありません(thisArg は第二引数に渡す)
JavaScriptここが重要です:reduce の thisArg は第二引数ではなく「reduce の第二引数は初期値」です。this を使いたいなら、関数外の変数を参照する(クロージャ)方が素直で安全です。
const limit = 50;
const count = xs.reduce((acc, x) => acc + (x > limit ? 1 : 0), 0);
JavaScriptよく使う集約レシピ(実務の定番)
合計・平均・最大・最小
const nums = [3, 7, 2, 9];
const sum = nums.reduce((acc, n) => acc + n, 0); // 21
const avg = nums.length ? sum / nums.length : 0; // 5.25
const max = nums.reduce((acc, n) => Math.max(acc, n), -Infinity); // 9
const min = nums.reduce((acc, n) => Math.min(acc, n), Infinity); // 2
JavaScriptここが重要です:境界(Infinity/-Infinity)を初期値に使うと、空配列でも安全な最大・最小の算出ができます。
配列をオブジェクトに変換(辞書化・インデックス化)
const users = [
{ id: 1, name: "A" },
{ id: 2, name: "B" }
];
const byId = users.reduce((acc, u) => {
acc[u.id] = u; // キーに格納
return acc;
}, {});
// { "1": {…}, "2": {…} }
JavaScriptここが重要です:reduce は「配列→構造(オブジェクト・Map)」への変換に最適。後続の高速アクセスが可能になります。
グルーピング(カテゴリごとにまとめる)
const items = [
{ cat: "fruit", name: "apple" },
{ cat: "fruit", name: "banana" },
{ cat: "veg", name: "carrot" }
];
const grouped = items.reduce((acc, item) => {
(acc[item.cat] ??= []).push(item); // そのカテゴリの配列に追加
return acc;
}, {});
/*
{
fruit: [{…}, {…}],
veg: [{…}]
}
*/
JavaScriptここが重要です:null 合体代入(??=)で“初期化+追加”を一行にでき、読みやすく安全です。
フラット化(1階層の平坦化)
const nested = [[1, 2], [3], [], [4, 5]];
const flat = nested.reduce((acc, xs) => acc.concat(xs), []);
// [1, 2, 3, 4, 5]
JavaScriptここが重要です:reduce+concat は“1階層”の平坦化に向いています。多階層は flat(depth 指定)を使う方が簡潔です。
filter/map を reduce でまとめる(1パス最適化)
const products = [
{ name: "A", stock: 0, price: 100 },
{ name: "B", stock: 5, price: 200 },
{ name: "C", stock: 3, price: 150 }
];
const labels = products.reduce((acc, p) => {
if (p.stock > 0) acc.push(`${p.name} (${p.price})`);
return acc;
}, []);
// ["B (200)", "C (150)"]
JavaScriptここが重要です:filter → map の2段を reduce 1本で合成でき、1回の走査で済みます。可読性と性能のバランスで選びます。
初期値の設計を深掘り(最重要ポイント)
空配列対応と型の安定性
- 初期値は“期待する出力の型”で与える(数値なら 0、文字列なら “”、配列なら []、オブジェクトなら {})。
- 空配列に対しても意味のある結果にする(合計 0、連結 “”、集約 {} など)。
const xs = [];
xs.reduce((acc, x) => acc + x, 0); // 0(安全)
xs.reduce((acc, x) => acc + x); // TypeError(初期値なしはNG)
JavaScript演算の単位と初期値の一貫性
const prices = [100, 200];
// 税込み合計の例:初期値を 0 にして合計後に掛ける
const sum = prices.reduce((acc, p) => acc + p, 0);
const totalWithTax = Math.round(sum * 1.1);
JavaScriptここが重要です:初期値に“税率”や“開始値”を混ぜるより、演算の段階を分ける方がトレーサブルでバグが減ります。
挙動・落とし穴・対策
疎配列(空スロット)と reduce
const sparse = Array(2); // [ <2 empty items> ]
sparse.reduce((acc, x) => acc + (x ?? 0), 0); // 0(空スロットはスキップ)
sparse.reduce((acc, x) => acc + (x ?? 0)); // TypeError(初期値なし)
JavaScriptここが重要です:reduce は空スロットを“スキップ”します。初期値なしだと“最初の要素”が見つからずエラーに。必ず初期値を与えましょう。
純粋なリデューサーにする(副作用禁止)
// NG例:外部状態を書き換える
const sum = nums.reduce((acc, n) => { log(n); return acc + n; }, 0); // ログは副作用
JavaScriptここが重要です:リデューサーは「入力→出力」だけに集中させる。副作用は別工程(forEach/外部で)へ分離すると、テスト容易性と再利用性が上がります。
破壊的操作に注意(concat ではなく push の是非)
// 非破壊の連結(推奨)
const flat = nested.reduce((acc, xs) => acc.concat(xs), []);
// 破壊的だが高速なパターン(コンテナは“accのみ”なら許容)
const flat2 = nested.reduce((acc, xs) => {
acc.push(...xs);
return acc;
}, []);
JavaScriptここが重要です:acc は“新規作成したコンテナ”なら破壊的操作(push)をしても外部へ副作用は漏れません。元配列や要素には触れないのが原則です。
非同期処理と reduce
// 並列に投げてまとめる
const ids = [1, 2, 3];
const promises = ids.map(id => fetch(`/api/${id}`));
const results = await Promise.all(promises);
// 逐次チェーン(順番保証)
const tasks = [a, b, c];
const out = await tasks.reduce(async (prev, task) => {
const acc = await prev;
const next = await task(acc);
return next;
}, Promise.resolve(initial));
JavaScriptここが重要です:reduce 自体は同期。非同期の逐次連鎖は“Promise をアキュムレータにする”書き方が定番です。
他メソッドとの使い分け(役割を明確に)
reduce と map/filter/some/every
- 「1対1変換」なら map。
- 「選別」なら filter。
- 「存在判定」なら some/every。
- 「ひとつにまとめる」なら reduce。
ここが重要です:reduce は“なんでもできる”反面、過剰に使うと読みにくくなります。役割ごとの最適メソッドを選び、reduce は“集約”に集中させるのが王道です。
reduceRight(右から左へ)
const xs = ["a", "b", "c"];
const joined = xs.reduceRight((acc, x) => acc + x, ""); // "cba"
JavaScriptここが重要です:右から畳みたい特殊ケースに reduceRight。通常は reduce で十分です。
実践例(UI・データ・アルゴリズム)
文字列連結と書式化
const temps = [18, 22.5, 25];
const csv = temps.reduce((acc, t, i) =>
acc + (i ? "," : "") + t.toFixed(1), "");
// "18.0,22.5,25.0"
JavaScript頻度カウント(ヒストグラム)
const tags = ["js", "web", "js"];
const freq = tags.reduce((acc, t) => {
acc[t] = (acc[t] ?? 0) + 1;
return acc;
}, {});
// { js: 2, web: 1 }
JavaScript安全な合計(欠損・非数値をスキップ)
const xs = [1, undefined, "3", null, 2];
const sum = xs.reduce((acc, x) => {
const n = Number(x);
return Number.isFinite(n) ? acc + n : acc;
}, 0);
// 6
JavaScriptまとめ
reduce は「配列を“ひとつの値”へ畳み込む」ための強力なメソッドです。鍵は“初期値を必ず与える”ことと、“リデューサーを純粋に保つ”こと。合計・最大最小・辞書化・グルーピング・フラット化など、集約仕事を短く安全に書けます。疎配列のスキップ挙動、空配列での TypeError、破壊的操作の扱い、非同期の逐次連鎖などのポイントを押さえ、役割別に他メソッドと使い分ければ、初心者でも“意図通りで壊れにくい集約ロジック”をシンプルに設計できます。
