非破壊的操作とは何か
非破壊的操作は「元の配列を変更せず、新しい配列を返す」考え方とテクニックの総称です。ここが重要です:配列を“直接書き換えない”ことで、予期せぬ影響(共有参照のバグ、UI状態の崩れ)を防げます。React/Vue などの状態管理、関数型スタイル、テスト容易性の向上に直結します。
破壊的操作と非破壊的操作の違い
代表的な破壊的操作(元配列が書き換わる)
// 破壊的
arr.sort(); // 並び替え
arr.reverse(); // 逆順
arr.splice(1, 2); // 途中削除・挿入
arr.push(10); // 末尾追加
arr.pop(); // 末尾削除
arr.shift(); // 先頭削除
arr.unshift(10); // 先頭追加
JavaScript破壊的操作は戻り値が“同じ配列の参照”であることが多く、他箇所が同じ配列を参照していると一斉に影響が及びます。
代表的な非破壊的操作(新しい配列を返す)
// 非破壊的
const b1 = arr.slice(); // 全体コピー
const b2 = arr.concat([10]); // 連結
const b3 = arr.map(x => x*2); // 変換
const b4 = arr.filter(x => x>0); // 抽出
const b5 = arr.flat(); // 平坦化
const b6 = arr.toReversed?.(); // ES提案系:逆順の新配列(対応環境のみ)
const b7 = arr.toSorted?.(); // ES提案系:ソート済み新配列(対応環境のみ)
JavaScriptここが重要です:標準で非破壊なもの(map/filter/flat など)を優先し、破壊的操作が必要なら“コピーしてから”使うのが安全です。
非破壊にする基本パターン(まずコピーする)
スプレッド・slice で浅いコピーを作る
const a = [1, 2, 3];
// 非破壊ソート
const sorted = [...a].sort((x, y) => x - y);
// 非破壊リバース
const reversed = a.slice().reverse();
console.log(a); // [1, 2, 3](元はそのまま)
console.log(sorted); // [1, 2, 3]
console.log(reversed); // [3, 2, 1]
JavaScriptここが重要です:コピー→破壊的操作の順にすれば、外部に副作用は漏れません。浅いコピーなので“要素の参照”は同じ点に注意(オブジェクト要素の編集は別途対策)。
追加・削除・置換を非破壊で書く
const a = [1, 2, 3];
// 末尾に追加
const add = [...a, 4]; // 非破壊
// 先頭に追加
const unshifted = [0, ...a];
// 任意位置へ挿入
const inserted = [...a.slice(0, 1), 99, ...a.slice(1)]; // 1 の後ろに 99
// 条件削除(splice ではなく filter)
const removed = a.filter(x => x !== 2);
// 置換(map で差し替え)
const replaced = a.map(x => (x === 2 ? 200 : x));
JavaScriptここが重要です:splice を避け、slice+スプレッド/filter/map を組み合わせるのが非破壊の基本レシピです。
参照の扱い(浅いコピーの限界と対策)
浅いコピーは“要素のオブジェクト参照”を共有する
const users = [{ id: 1, name: "A" }, { id: 2, name: "B" }];
const copy = [...users]; // 浅いコピー
copy[0].name = "A2";
console.log(users[0].name); // "A2"(同じ参照なので影響する)
JavaScriptここが重要です:非破壊は“配列の並び・箱”を守るだけ。要素がオブジェクトなら参照は共有されます。編集時は“要素自体”を新規オブジェクトへ。
要素を安全に更新(スプレッドで新インスタンス)
const users = [{ id: 1, name: "A" }, { id: 2, name: "B" }];
const updated = users.map(u => (u.id === 2 ? { ...u, name: "B2" } : u));
// users は不変、updated は新しいオブジェクト参照
JavaScript要素の差し替えはスプレッド({ …u, key: newValue })で“新インスタンス”を返すのが基本です。深いネストはライブラリや専用関数(構造的コピー)を検討します。
非破壊のメリット(実務で効くポイント)
状態管理が楽になる(予測可能性・差分検出)
非破壊は「前の配列と新しい配列が“別参照”」になるため、UIフレームワークが差分(=== の変化)を検出しやすく、再レンダリングが正しく行われます。バグ調査でも「いつ変更が起きたか」が辿りやすくなります。
テスト容易性(入力→出力が安定)
破壊的だと「グローバルな状態」が変わり、テストが相互依存しがちです。非破壊は“純粋な関数”に近づくため、テストと再利用が容易になります。
並行・共有の安全性
複数のモジュールやイベントが同じ配列を参照する状況で、非破壊なら“勝手に変わる”事故を防げます。ロックやディープコピーより軽く安全性を上げられます。
非破壊での加工レシピ(よくある実務パターン)
条件更新(ID マッチした要素だけ変更)
function updateById(arr, id, patch) {
return arr.map(x => (x.id === id ? { ...x, ...patch } : x));
}
JavaScript条件削除(1件だけ、複数件)
function removeById(arr, id) {
return arr.filter(x => x.id !== id);
}
function removeByPredicate(arr, pred) {
return arr.filter(x => !pred(x));
}
JavaScript挿入(特定位置/条件位置の前に差し込む)
function insertAt(arr, index, value) {
return [...arr.slice(0, index), value, ...arr.slice(index)];
}
function insertBefore(arr, pred, value) {
const i = arr.findIndex(pred);
return i >= 0 ? [...arr.slice(0, i), value, ...arr.slice(i)] : [...arr, value];
}
JavaScriptソート・逆順(元を守りつつ)
const asc = [...arr].sort((a, b) => a - b);
const reversed = [...arr].reverse();
JavaScript平坦化・絞り込み・変換の合成
const out = arr
.filter(x => x.active)
.flatMap(x => x.items) // 1階層だけ展開
.map(item => ({ ...item, flag: true }));
JavaScript重要な注意点(深掘り)
疎配列(空スロット)への意識
forEach/map/filter などは“空スロットをスキップ”します。非破壊ロジックのはずが、穴が残って想定外になることがあります。必要なら先に正規化します。
const normalized = Array.from(arr, x => x ?? null); // 空スロットも値に
JavaScriptパフォーマンスとメモリ
非破壊は“新しい配列”を作るためメモリを使います。巨大配列で頻繁に再構築するなら、部分的な更新に絞る(slice の範囲を最小化)、データ構造を見直す(Map/Set、イミュータブルな木構造)などを検討します。
まとめて一度に(合成で1パスに)
filter → map → concat などを何段も重ねるとコストが増えます。必要に応じて reduce で“絞り込み+変換+集約”を1パスにまとめ、可読性と性能のバランスを取ります。
const labels = products.reduce((acc, p) => {
if (p.stock > 0) acc.push(`${p.name} (${p.price})`);
return acc;
}, []);
JavaScript破壊的操作をどうしても使うなら
“コピーしてから”が鉄則です。さらに、破壊的操作直後は“元配列に依存する後続処理”と分離し、意図が明確な境界(関数やスコープ)を作ると事故が減ります。
実践例(UI状態・APIレスポンス・ログ)
UI選択のトグル(非破壊)
function toggle(arr, value) {
return arr.includes(value)
? arr.filter(x => x !== value)
: [...arr, value];
}
JavaScriptAPIレスポンスのクレンジング(欠損除去+整形)
function cleanse(rows) {
return rows
.filter(x => x && typeof x.id === "number")
.map(x => ({ id: x.id, name: String(x.name ?? "(unknown)") }));
}
JavaScriptログ用配列の末尾追記(読み取り側を壊さない)
function appendLog(logs, entry) {
return [...logs, { ...entry, ts: Date.now() }];
}
JavaScriptまとめ
非破壊的操作は「元配列を変えず、新しい配列で結果を返す」設計思想です。map/filter/flat/concat を基本に、破壊的操作が必要なら“コピーしてから”。オブジェクト要素は参照を共有するため、更新は“新インスタンス”で差し替える。疎配列の挙動、メモリと性能、合成での1パス最適化を意識すれば、初心者でも“意図通りで壊れにくい”配列加工を安定して書けます。

