テーマの整理:「メモ化配列」とは何か
「メモ化配列」は、配列を入力にする処理の結果を“覚えておいて”、同じ入力に対しては再計算せずに結果を返す仕組みです。 さっきの「配列キャッシュ」とほぼ同じ考え方ですが、より「関数レベル」で使いやすくしたものが「メモ化」です。
業務だと、例えば次のような場面で効いてきます。
同じ一覧データに対して、何度も同じフィルタ・ソート・集計をしている。 画面の再描画のたびに、同じ配列処理が何度も走っていて重い。 「この処理は入力が同じなら結果も同じ」という前提がある。
こういうときに「メモ化配列」を使うと、一度計算した結果を再利用できるので、処理が軽く・安定します。
基本コンセプト:配列を入力にする関数を「メモ化」する
「メモ化」とは何かを一度言葉で整理する
メモ化(memoization)は、 「関数の入力と出力の対応を覚えておいて、同じ入力が来たら計算せずに覚えていた出力を返す」テクニックです。
ポイントは、
入力が同じなら出力も同じ(副作用がない)こと。 覚えておくのは「入力 → 出力」のペア。 覚えておく場所は Map やオブジェクトなど。
配列を入力にする場合は、 「配列の中身をどうやって“同じ”と判定するか」が重要になります。
配列版メモ化の一番シンプルな形
初心者向けに分かりやすい形は、 「配列を JSON.stringify して文字列にし、それをキーにする」メモ化です。
function memoizeArray(fn) {
const cache = new Map();
return function memoized(array, options = {}) {
const key = JSON.stringify({ array, options });
if (cache.has(key)) {
return cache.get(key);
}
const result = fn(array, options);
cache.set(key, result);
return result;
};
}
JavaScriptここでの役割を整理すると、
fn は「配列を受け取って結果を返す元の関数」。 cache は「キー → 結果」を覚えておく Map。 key は「配列とオプションをまとめて文字列化したもの」。 memoized は「メモ化された関数」で、呼び出すとキャッシュを見てから必要なら計算する。
これが「メモ化配列」の基本形です。
例題 1:配列の合計をメモ化する
シナリオ
売上データの配列があり、 「合計金額」を出す処理が何度も呼ばれる。 同じ配列に対しては、合計を一度だけ計算して覚えておきたい。
コード
function sumAmounts(items) {
return items.reduce((acc, item) => acc + item.amount, 0);
}
const sumAmountsMemo = memoizeArray(sumAmounts);
const data = [
{ id: 1, amount: 100 },
{ id: 2, amount: 200 },
{ id: 3, amount: 300 },
];
console.log(sumAmountsMemo(data)); // 初回: 計算してキャッシュ
console.log(sumAmountsMemo(data)); // 2回目: キャッシュから取得
JavaScriptここで起きていることを丁寧に追うと、
1 回目の呼び出しでは、cache にキーがないので sumAmounts が実行される。 計算結果(600)が cache に保存される。 2 回目の呼び出しでは、同じ配列+同じ options なので、cache から 600 が返る。
「同じ配列に対して同じ処理を何度もする」場面では、こういうメモ化が効いてきます。
例題 2:フィルタ処理をメモ化する(条件付き)
シナリオ
ユーザー一覧の配列があり、 「active なユーザーだけを取り出す」処理を何度も呼び出している。 配列と条件の組み合わせでメモ化したい。
コード
function filterUsers(users, options) {
const { activeOnly } = options;
if (activeOnly) {
return users.filter((u) => u.active);
}
return users.slice();
}
const filterUsersMemo = memoizeArray(filterUsers);
const users = [
{ id: 1, name: "A", active: true },
{ id: 2, name: "B", active: false },
{ id: 3, name: "C", active: true },
];
console.log(filterUsersMemo(users, { activeOnly: true })); // 初回: 計算
console.log(filterUsersMemo(users, { activeOnly: true })); // 2回目: キャッシュ
console.log(filterUsersMemo(users, { activeOnly: false })); // 別条件: 計算
console.log(filterUsersMemo(users, { activeOnly: false })); // 同条件: キャッシュ
JavaScriptここでは、
キーに「配列」と「options(activeOnly)」をまとめて入れている。 同じ配列+同じ options なら、結果を再利用できる。 違う options なら別のキーになるので、別の結果が計算される。
という構造になっています。
重要ポイント:メモ化配列は「入力が変わらない」前提で使う
配列を書き換えるとメモ化が破綻する
メモ化は、「同じ入力なら同じ結果」という前提で成り立っています。 配列を直接書き換えると、「同じ配列オブジェクトなのに中身が違う」という状態になり、 JSON.stringify をキーにしている場合でも、意図しない結果になることがあります。
例えば、
配列に新しい要素を push した。 既存要素の値を直接変更した。
こういう「破壊的変更」をすると、 「前に計算した結果」と「今欲しい結果」がズレてしまう可能性があります。
イミュータブルな配列とメモ化は相性が良い
React などのフロントエンドでは、 「配列を更新するときは新しい配列を作る(イミュータブル)」というスタイルがよく使われます。
例えば、
新しい要素を追加するときは newArray = [...oldArray, newItem]。 要素を更新するときは newArray = oldArray.map(...)。
このように「配列オブジェクト自体が変わる」前提なら、 メモ化配列はかなり安全に使えます。
初心者向けには、
配列を直接書き換えるより、新しい配列を作る習慣をつける。 メモ化は「入力が変わらない間だけ有効」と意識する。
というところを強く意識してほしいです。
もう一歩進んだ形:非同期処理をメモ化する「メモ化配列+Promise」
シナリオ
配列を入力にして、API を叩いて結果を返すような非同期処理がある。 同じ配列に対しては、API を何度も叩きたくない。 結果の Promise をメモ化したい。
実装例
function memoizeArrayAsync(fn) {
const cache = new Map();
return async function memoized(array, options = {}) {
const key = JSON.stringify({ array, options });
if (cache.has(key)) {
return cache.get(key);
}
const promise = fn(array, options);
cache.set(key, promise);
return promise;
};
}
JavaScriptここでは、
fn は「配列を受け取って Promise を返す関数」。 キャッシュには「結果そのもの」ではなく「Promise」を保存する。 呼び出し側は await memoized(...) で結果を受け取る。
という形になっています。
例題:ユーザー一覧を API から取得する処理をメモ化する
async function fetchUsersFromApi(ids) {
console.log("call API with", ids);
await new Promise((r) => setTimeout(r, 500));
return ids.map((id) => ({ id, name: `User-${id}` }));
}
const fetchUsersMemo = memoizeArrayAsync(fetchUsersFromApi);
async function main() {
const ids = [1, 2, 3];
const users1 = await fetchUsersMemo(ids); // 初回: API 呼び出し
const users2 = await fetchUsersMemo(ids); // 2回目: キャッシュされた Promise
console.log(users1);
console.log(users2);
}
main();
JavaScriptここでのポイントは、
1 回目は API を叩いて Promise を作り、それをキャッシュする。 2 回目は同じキーなので、キャッシュされた Promise をそのまま返す。 結果として、API は 1 回しか叩かれない。
という動きになっていることです。
メモ化配列を使うときに意識してほしいこと
「この処理はメモ化しても安全か?」を考える
メモ化は便利ですが、 副作用がある処理(ログを書いたり、外部状態を変えたり)には向きません。
考えてほしいポイントは、
入力が同じなら、毎回同じ結果が欲しい処理か。 外部状態に依存していないか(時間・ランダム・グローバル変数など)。 結果が変わるタイミングを自分でコントロールできるか。
これらに「はい」が多いほど、メモ化配列を入れる価値が高いです。
「キャッシュをいつ捨てるか?」を決める
メモ化は「覚えっぱなし」にすると、 メモリを食い続けたり、古い結果を使い続けたりします。
初心者向けには、
まずはスコープを小さくする(画面単位・処理単位で関数を作る)。 必要になったら「キャッシュをクリアする関数」を追加する。
くらいから始めるのがちょうどいいです。
例えば、こういう形もありです。
function createArrayMemo(fn) {
const cache = new Map();
function memoized(array, options = {}) {
const key = JSON.stringify({ array, options });
if (cache.has(key)) return cache.get(key);
const result = fn(array, options);
cache.set(key, result);
return result;
}
memoized.clear = () => cache.clear();
return memoized;
}
JavaScriptこれなら、必要に応じて memoized.clear() でキャッシュを捨てられます。
手を動かして「メモ化配列」の感覚をつかむ
次のコードを実行して、「メモ化あり/なし」の違いをログで体感してみてください。
function memoizeArray(fn) {
const cache = new Map();
return function memoized(array, options = {}) {
const key = JSON.stringify({ array, options });
if (cache.has(key)) {
console.log("use memo");
return cache.get(key);
}
console.log("compute");
const result = fn(array, options);
cache.set(key, result);
return result;
};
}
function sumAmounts(items) {
return items.reduce((acc, item) => acc + item.amount, 0);
}
const sumAmountsMemo = memoizeArray(sumAmounts);
const data = [
{ id: 1, amount: 100 },
{ id: 2, amount: 200 },
{ id: 3, amount: 300 },
];
console.log("=== memoized ===");
console.log(sumAmountsMemo(data)); // compute
console.log(sumAmountsMemo(data)); // use memo
console.log("=== normal ===");
console.log(sumAmounts(data)); // 毎回計算
console.log(sumAmounts(data)); // 毎回計算
JavaScriptログを見れば、
メモ化あり:1 回目だけ「compute」、2 回目は「use memo」。 メモ化なし:毎回計算が走る。
という違いがはっきり分かるはずです。
まとめ:メモ化配列ユーティリティで「配列処理のムダな再計算」を減らす
「メモ化配列」は、 「同じ配列に対して何度も同じ処理をするなら、一度だけ計算して覚えておこう」 という、とても素直な発想をコードにしたものです。
プロジェクトに例えば次のような形で置いておくイメージです。
export function memoizeArray(fn) { ... }
export function memoizeArrayAsync(fn) { ... }
JavaScriptそして、
重い配列処理で、同じ入力が何度も来るところにはメモ化を挟む。 入力が変わるタイミングと、キャッシュを捨てるタイミングを意識する。
こう決めておくと、 パフォーマンスが安定し、 「どこで計算していて、どこで結果を使い回しているか」がコードから読み取りやすくなります。
メモ化は“裏技”ではなく、“ちゃんと覚えておく工夫”です。 配列ユーティリティとしてのメモ化配列をうまく使うと、 業務コードがぐっと落ち着いた動き方をするようになります。
