何をしたいユーティリティか:「配列コピー」
「配列コピー」は、元の配列を壊さずに、同じ中身を持つ“別の配列”を作る処理です。
業務ではほぼ必ずと言っていいほど使います。なぜなら、元の配列を直接いじると「いつの間にか順番が変わっている」「どこかで要素が消えている」といったバグの温床になるからです。
JavaScript では、配列は「参照型」です。
変数に入っているのは“配列そのもの”ではなく、“配列への参照”です。
なので、「コピーしたつもりで同じ配列を触っていた」という事故が起きやすいのです。
ここをきちんと理解して、「安全にコピーするユーティリティ」を持っておくと、一気にコードが安定します。
まず押さえるべき前提:参照とコピーの違い
参照をコピーしているだけのパターン
次のコードを見てください。
const a = [1, 2, 3];
const b = a;
b.push(4);
console.log(a); // [1, 2, 3, 4]
console.log(b); // [1, 2, 3, 4]
JavaScriptb = a; は「配列の中身をコピー」しているのではなく、「同じ配列を指す参照をコピー」しているだけです。
そのため、b.push(4) は a にも影響します。
業務コードでこれをやると、「別の変数だと思っていたのに、片方を変えたらもう片方も変わる」という混乱が起きます。
これを避けるために、「ちゃんと別の配列を作る」=「配列コピー」が必要になります。
一番基本:浅いコピー(shallow copy)
slice を使った浅いコピー
配列を「浅くコピー」する一番基本の方法は slice() です。
function copyArray(array) {
if (!Array.isArray(array)) {
return [];
}
return array.slice();
}
JavaScriptここでのポイントは、slice() を引数なしで呼ぶと「全体のコピー」が作られることです。Array.isArray で配列かどうかをチェックし、違ったら空配列を返すようにしておくと、業務コードでも安全に使えます。
動きのイメージはこうです。
const a = [1, 2, 3];
const b = copyArray(a);
b.push(4);
console.log(a); // [1, 2, 3]
console.log(b); // [1, 2, 3, 4]
JavaScript今度は b を変えても a は変わりません。
「別の配列」がちゃんとできている、ということです。
スプレッド構文を使った浅いコピー
同じことをスプレッド構文でも書けます。
function copyArraySpread(array) {
if (!Array.isArray(array)) {
return [];
}
return [...array];
}
JavaScript[...array] は、「array の中身を展開して、新しい配列を作る」という意味です。slice() と同じく、「浅いコピー」です。
浅いコピーの“浅い”とは何か
中身がオブジェクトの場合の挙動
浅いコピーは、「配列そのもの」は別になりますが、「中に入っているオブジェクト」は同じ参照のままです。
const a = [{ id: 1 }, { id: 2 }];
const b = copyArray(a);
b[0].id = 999;
console.log(a[0].id); // 999
console.log(b[0].id); // 999
JavaScriptここで起きていることはこうです。
配列 a と配列 b は別物。
しかし、a[0] と b[0] は「同じオブジェクト」を指している。
つまり、「配列の箱」はコピーされているけれど、「箱の中身(オブジェクト)」は共有されている状態です。
これが「浅いコピー」です。
業務では、「配列の並びや要素の追加・削除だけを変えたい」なら浅いコピーで十分なことが多いです。
一方、「中のオブジェクトも含めて完全に独立させたい」なら、もう一段深いコピーが必要になります。
JSON で済むケース向け:簡易的な深いコピー
JSON で表現できるデータだけなら
「中身がプリミティブ・配列・プレーンオブジェクトだけ」で、
関数や Date、Map、Set などが含まれていない場合は、JSON.stringify / JSON.parse を使った簡易的な「深いコピー」が使えます。
function deepCopyJsonArray(array) {
if (!Array.isArray(array)) {
return [];
}
return JSON.parse(JSON.stringify(array));
}
JavaScriptこれを使うと、配列の中のオブジェクトも含めて「別物」が作られます。
const a = [{ id: 1 }, { id: 2 }];
const b = deepCopyJsonArray(a);
b[0].id = 999;
console.log(a[0].id); // 1
console.log(b[0].id); // 999
JavaScript今度は、b[0] を変えても a[0] は変わりません。
配列も中のオブジェクトも、完全に別のものになっています。
ただし、この方法には制限があります。
Date や Map、Set、関数などが含まれていると、正しくコピーできません。
「API レスポンスのような純粋な JSON データ」を扱うときにだけ使う、というルールにしておくと安全です。
業務での具体的な使い方
画面状態を更新するときに元配列を壊さない
例えば、選択中の ID リストを配列で持っているケース。
const selectedIds = [1, 2, 3];
function addSelection(id) {
const next = copyArray(selectedIds);
next.push(id);
return next;
}
JavaScriptここでのポイントは、「元の selectedIds を直接 push しない」ことです。
常に「コピーを作ってから変更する」ことで、「どこで何が変わったか」が追いやすくなります。
React などのフレームワークでも、「状態は不変(immutable)に扱う」ことが推奨されているので、
このパターンはそのまま役に立ちます。
API レスポンスを加工するときに元データを残す
API から配列が返ってきて、それを画面用に少し加工したいケース。
function prepareForView(rawList) {
const list = copyArray(rawList);
// 並び替えたり、フィルタしたりする
return list;
}
JavaScriptrawList をそのままいじるのではなく、必ずコピーしてから加工することで、
「生データ」と「画面用データ」を分離できます。
配列コピーユーティリティで意識してほしいポイント
1. 「参照をコピーしているだけ」になっていないか常に疑う
const b = a; はコピーではなく「同じものを指しているだけ」です。
業務コードで配列を扱うときは、「これは本当に別配列か?」「同じ配列を触っていないか?」を常に意識してください。
そのために、「配列をいじる前にまず copyArray を通す」という癖をつけるのは、とても有効です。
2. 浅いコピーで十分な場面と、深いコピーが必要な場面を分ける
浅いコピー(slice / スプレッド)は、「配列の構造だけを変えたい」場面に向いています。
中のオブジェクトも含めて完全に独立させたいなら、JSON ベースの深いコピーや、もっと本格的な deep copy が必要です。
「とりあえず全部 deep copy」にすると、パフォーマンスが悪くなったり、Date などが壊れたりします。
なので、
配列の並び・要素の追加削除だけ → 浅いコピー
中身のオブジェクトも独立させたい → 深いコピー(対象データをよく確認)
という切り分けを意識してください。
3. ユーティリティで「配列じゃないもの」を受けても壊れないようにする
copyArray や deepCopyJsonArray は、必ず Array.isArray をチェックしています。
if (!Array.isArray(array)) {
return [];
}
JavaScript現実の業務コードでは、「本当は配列のはずだけど、何かの拍子に null やオブジェクトが来る」ことが普通にあります。
そこで例外を投げて落ちるより、「空配列を返す」と決めておくほうが、呼び出し側のコードがシンプルになります。
手を動かして感覚をつかむ
コンソールで、次のコードを実際に打ってみてください。
function copyArray(array) {
if (!Array.isArray(array)) return [];
return array.slice();
}
function deepCopyJsonArray(array) {
if (!Array.isArray(array)) return [];
return JSON.parse(JSON.stringify(array));
}
const a1 = [1, 2, 3];
const b1 = copyArray(a1);
b1.push(4);
console.log(a1, b1);
const a2 = [{ id: 1 }, { id: 2 }];
const b2 = copyArray(a2);
b2[0].id = 999;
console.log(a2[0].id, b2[0].id);
const a3 = [{ id: 1 }, { id: 2 }];
const b3 = deepCopyJsonArray(a3);
b3[0].id = 999;
console.log(a3[0].id, b3[0].id);
JavaScript「どのケースで元の配列やオブジェクトが変わって、どのケースでは変わらないか」を、自分の目で確認してみてください。
そのうえで、自分のプロジェクトに
export function copyArray(...) { ... }
export function deepCopyJsonArray(...) { ... }
JavaScriptのような関数を置き、「配列をいじる前には必ず“配列コピーユーティリティ”を通す」と決めてみてください。
それだけで、「いつの間にかどこかで配列が書き換わっている」系のバグを、かなり減らせます。
