破壊的 / 非破壊的操作とは何か
破壊的操作は「元の配列・オブジェクトを直接書き換える」こと、非破壊的操作は「元を保ったまま、新しい配列・オブジェクトを返す」ことです。ここが重要です:共有されているデータ(UI状態、キャッシュ、他モジュールが参照)に対して破壊的操作をすると予期せぬ副作用が起きがちです。反対に、非破壊は安全ですが“新インスタンスの作成コスト”が常に伴います。状況に応じて選択するのが設計の要です。
// 破壊的(元を変更)
const arr = [3,1,2];
arr.sort((a,b)=>a-b); // arr が書き換わる
// 非破壊的(元を保つ)
const arr2 = [3,1,2];
const sorted = arr2.toSorted((a,b)=>a-b); // arr2 は維持、sorted は新規
JavaScript代表的な破壊的 / 非破壊的操作
配列での例(どれが元を壊す?)
- 破壊的: sort, reverse, splice, push, pop, shift, unshift
- 非破壊的: toSorted, toReversed, toSpliced, slice, concat, map, filter, reduce, flat, flatMap
const a = [1,2,3];
// 破壊的
a.splice(1, 1); // a => [1,3]
a.reverse(); // a => [3,1]
// 非破壊的
const b = a.slice(0, 2); // 新しい配列、a はそのまま
const c = a.concat([4,5]); // 新しい配列を返す
JavaScriptオブジェクトでの例(上書きとコピー)
- 破壊的: 直接代入(obj.key = …)、delete、Object.assign(obj, …)
- 非破壊的: スプレッド({ …obj, key: … })、Object.assign({}, obj, …)、構造化コピー(structuredClone)
const obj = { a: 1, b: 2 };
// 破壊的
obj.b = 3;
delete obj.a;
// 非破壊的(新インスタンスを返す)
const next = { ...obj, c: 4 }; // obj は維持
JavaScriptどちらを選ぶべきか(設計指針)
非破壊を優先する場面
UI状態管理(React/Vueなど)、履歴を持つ処理、複数モジュールで参照している共有データでは非破壊を基本にします。ここが重要です:非破壊にすると同一性(===)の変化で差分検知が安定し、バグ調査が容易になります。
// 状態の部分更新(非破壊)
const state = { user: { name: "A" }, prefs: { theme: "light" } };
const next = {
...state,
prefs: { ...state.prefs, theme: "dark" }
};
JavaScript破壊が有効な場面
一時的でローカルなデータ、参照が他に渡らないことが自明、メモリや速度を極限まで詰めたいループ内部では破壊的が有利なことがあります。ここが重要です:外部に影響しない限定範囲でのみ使う、という線引きを徹底します。
// ローカルの一時配列を高速に並び替える(共有しない前提)
const tmp = getChunk(); // 新規生成
tmp.sort((a,b)=>a-b); // 破壊的でも副作用は外へ漏れない
process(tmp);
JavaScriptパフォーマンスの現実(コストの比較)
破壊的の利点・欠点
破壊的操作は“新しい配列やオブジェクトを作らない”ので割り当てコストが小さく、ガベージコレクション(GC)の負荷も軽くなります。欠点は、副作用が外へ波及して“原因不明の挙動”を招くこと。とくに sort/reverse/splice は見た目の副作用が大きいので慎重に。
非破壊的の利点・欠点
非破壊操作は安全でテストしやすく、並行処理やUIの差分検知と相性が良い一方、毎回新インスタンスを割り当てるためメモリピークと割り当て時間が増えます。ここが重要です:大規模データでは“必要な部分だけコピー”に絞り、無駄な浅コピー(…)の連鎖を避けます。
// 悪い例:無駄な全体コピーの連続
let s = { a: { b: { c: 1 } } };
s = { ...s }; // 意味がない全体コピー
s = { ...s, a: { ...s.a, b: { ...s.a.b, c: 2 } } }; // 必要階層だけで十分
JavaScript事故を防ぐテクニック(重要ポイントの深掘り)
“入力を壊さない”ルールを関数の契約にする
関数は「引数を変更しない(非破壊)」を基本ポリシーにします。返り値で新値を渡す契約にすると、誰がいつ状態を変えたかが明確になります。
// 悪い:引数を書き換える
function addTagInPlace(item, tag) { item.tags.push(tag); return item; }
// 良い:新インスタンスで返す
function addTag(item, tag) { return { ...item, tags: [...(item.tags ?? []), tag] }; }
JavaScript“書き換えても良い”ことを明示する
性能上、破壊的にしたいときは名前やコメントでスコープを明示します。レビューで見逃されにくくなります。
// 注意:破壊的に並び替え(ローカル専用)
function sortInPlace(nums) { nums.sort((a,b)=>a-b); return nums; }
JavaScriptネスト更新は“階層ごとにコピー”
1段でもコピーを省くと参照共有が残り、他所から見えてしまいます。更新が必要な階層のみスプレッドで再構築するのが安全です。
const state = { list: [{ id: 1, active: false }] };
const next = {
...state,
list: state.list.map(it => it.id === 1 ? { ...it, active: true } : it)
};
JavaScript実務レシピ(安全・高速の両立)
非破壊ソート(toSorted)で事故を防ぐ
UI用の一覧は常に toSorted を使い、元配列は保つ方針にします。古い環境では slice().sort(...) で代替します。
const sorted = rows.toSorted((a,b)=>a.price-b.price);
// rows はそのまま
JavaScriptfilter+map の1パス融合(非破壊・高効率)
中間配列を減らしつつ非破壊にするため、1回の走査で最終配列を作ります。
function filterMap(arr, pred, mapFn) {
const out = [];
for (const x of arr) { if (pred(x)) out.push(mapFn(x)); }
return out;
}
JavaScript大規模更新は“辞書化”して部分だけ置換
配列を丸ごと作り直すより、辞書で該当キーだけ非破壊更新し、表示時に配列へ戻すと効率的です。
const dict = Object.fromEntries(list.map(x => [x.id, x]));
const nextDict = { ...dict, [id]: { ...dict[id], active: true } };
const nextList = Object.values(nextDict);
JavaScriptまとめ
破壊的操作は速くメモリ効率も良い反面、共有状態で副作用を招きやすい。非破壊的操作は安全で差分検知に強いが、割り当てコストが増える。ここが重要です:UIや共有データでは“非破壊を原則”、ローカル・限定スコープでは“破壊も選択肢”。ソートや配列編集は非破壊版(toSorted / toReversed / toSpliced)を優先し、ネストは必要階層だけコピーする。この指針に従えば、初心者でも“壊さず速い”配列・オブジェクト操作を設計できます。
