何をしたいユーティリティか:「シャローコピー」
ここでの「シャローコピー」は、配列そのものだけをコピーして、“中身(要素)への参照はそのまま共有する”コピーのことです。
一言でいうと、「配列の箱だけ新しくして、中に入っているものはそのまま使い回すコピー」です。
業務では、次のような場面でよく使います。
- 元の配列を壊さずに、並び替え・追加・削除だけしたい
- React などで「新しい配列インスタンス」を作りたい(変更検知のため)
- 大きなデータ構造を全部コピーするのは重いので、“箱だけ”差し替えたい
ディープコピーと違って「中身は共有」なので、どこまでが安全で、どこからが危険かを理解することがとても大事です。
まず前提:参照コピーとシャローコピーの違い
参照コピー(ただの代入)
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]
JavaScriptここでやっているのは「コピー」ではなく、同じ配列を指す変数を増やしているだけです。a と b は“別名”であって、中身は完全に同じ 1 つの配列です。
シャローコピー(箱だけ別)
const a = [1, 2, 3];
const b = a.slice(); // シャローコピー
b.push(4);
console.log(a); // [1, 2, 3]
console.log(b); // [1, 2, 3, 4]
JavaScript今度は、a と b は「別の配列」です。b に push しても a は変わりません。
これが「シャローコピー」の基本的なイメージです。
シャローコピーの実装:業務で使うならこの形
slice を使ったシャローコピー
一番オーソドックスな書き方です。
function shallowCopy(array) {
if (!Array.isArray(array)) {
return [];
}
return array.slice();
}
JavaScriptポイントは次の2つです。
slice()を引数なしで呼ぶと、「全体のコピー」が作られるArray.isArrayで配列かどうかをチェックし、違ったら空配列を返す(業務で落ちないようにするため)
スプレッド構文を使ったシャローコピー
モダンな書き方はこちらです。
function shallowCopySpread(array) {
if (!Array.isArray(array)) {
return [];
}
return [...array];
}
JavaScript[...array] は、「array の中身を展開して、新しい配列に詰め直す」という意味です。slice() と同じく、「箱だけ別、中身は同じ参照」のシャローコピーになります。
“シャロー”の意味:中身がオブジェクトのときに何が起きるか
ここが一番大事なポイントです。
シャローコピーは「配列の箱」は別ですが、「中のオブジェクト」は共有されます。
例:オブジェクト配列をシャローコピーした場合
const a = [{ id: 1 }, { id: 2 }];
const b = shallowCopy(a);
b[0].id = 999;
console.log(a[0].id); // 999
console.log(b[0].id); // 999
JavaScriptなぜこうなるかというと、
aとbは別の配列(箱は別)- しかし
a[0]とb[0]は同じオブジェクトを指している
からです。
つまり、「配列の構造(並び・要素の追加削除)を変える分には安全だが、中のオブジェクトを書き換えると元の配列にも影響する」ということです。
どんなときにシャローコピーで十分か
パターン1:並び替え・フィルタ・追加削除だけしたいとき
例えば、「選択中の ID リスト」を扱うケース。
const selectedIds = [1, 2, 3];
function addSelection(id) {
const next = shallowCopy(selectedIds);
next.push(id);
return next;
}
JavaScriptここでは、中身はプリミティブ(数値)なので、シャローコピーで十分です。
また、「配列の構造(要素の追加)」だけを変えていて、中の値そのものは書き換えていません。
React などで「新しい配列インスタンスを作る」ことが目的なら、この使い方が典型です。
パターン2:API レスポンスの配列を“並びだけ”変えたいとき
function sortByCreatedAt(list) {
const copied = shallowCopy(list);
copied.sort((a, b) => new Date(a.createdAt) - new Date(b.createdAt));
return copied;
}
JavaScriptここでは、「配列の順番だけ変えたい」のでシャローコピーで十分です。
中のオブジェクトのプロパティを書き換えていない限り、元データの“内容”は壊れません。
どんなときにシャローコピーでは危険か(ディープコピーが必要な場面)
パターン:中のオブジェクトも書き換えたいとき
例えば、「全ユーザーのフラグを一括で書き換える」ような処理。
function markAllActive(users) {
const copied = shallowCopy(users);
copied.forEach((u) => {
u.active = true;
});
return copied;
}
JavaScript一見よさそうに見えますが、これは元の users も書き換えています。
なぜなら、u は元の配列の要素と同じオブジェクトだからです。
元データを壊したくないなら、ここはディープコピーが必要になります。
function deepCopy(value) {
if (value === null || typeof value !== "object") {
return value;
}
if (Array.isArray(value)) {
return value.map((v) => deepCopy(v));
}
const result = {};
for (const key in value) {
if (Object.prototype.hasOwnProperty.call(value, key)) {
result[key] = deepCopy(value[key]);
}
}
return result;
}
function markAllActiveSafely(users) {
const copied = deepCopy(users);
copied.forEach((u) => {
u.active = true;
});
return copied;
}
JavaScriptここまでやって初めて、「元の users は一切変わらない」状態になります。
シャローコピー・ディープコピーの使い分けの感覚
ざっくりとした指針を言うと、こうです。
- 配列の構造(順番・要素の追加削除)だけ変えたい
→ シャローコピーで十分(slice/ スプレッド) - 中のオブジェクトのプロパティも書き換えたい
→ ディープコピーを検討する(少なくとも「元データを壊していいか」を意識する)
「とりあえず全部ディープコピー」は重くなりがちなので、
“何を変えたいのか”を意識して、シャローで足りるかどうかを判断するのがプロっぽい使い方です。
手を動かしてシャローコピーの感覚をつかむ
次のコードをコンソールで実行して、挙動を自分の目で確認してみてください。
function shallowCopy(array) {
if (!Array.isArray(array)) return [];
return array.slice();
}
const a1 = [1, 2, 3];
const b1 = shallowCopy(a1);
b1.push(4);
console.log("a1:", a1); // [1, 2, 3]
console.log("b1:", b1); // [1, 2, 3, 4]
const a2 = [{ id: 1 }, { id: 2 }];
const b2 = shallowCopy(a2);
b2[0].id = 999;
console.log("a2[0].id:", a2[0].id); // 999
console.log("b2[0].id:", b2[0].id); // 999
JavaScript前半では「箱だけ別になっている」こと、
後半では「中身のオブジェクトは共有されている」ことが、はっきり分かるはずです。
まとめ:シャローコピーは“箱だけ差し替える”ための道具
シャローコピーは、
- 元の配列を壊さずに構造だけ変えたい
- フレームワークの変更検知のために「新しい配列インスタンス」が欲しい
といった場面で、とてもよく効く“軽量なコピー”です。
プロジェクトに
export function shallowCopy(...) { ... }
JavaScriptのような関数を置き、「配列をいじる前に“とりあえず箱だけ別にしたい”ときは必ずこれを通す」と決めておくと、
「参照コピーの罠」にハマる回数がぐっと減ります。
そのうえで、「中身まで変えたいならディープコピーが必要だな」と意識できるようになると、
配列・オブジェクト周りのバグが一気に減って、コードの安定感が変わってきます。
