2026.09.23 泊まれる場所4,223 立ち寄りスポット59,693 営業中を確認したキャンプ場1,005 note を読む

配列をコピーしたつもりが、元のデータも変わる|スプレッド構文は一段しかコピーしない

スプレッド構文でコピーしたつもりでも、複製先を書き換えると元のデータまで変わることがある。
一段しかコピーしないと知っておくと、直し方が変わる。

設定画面を作っている場面を考える。

ユーザーごとに、絞り込み条件のテンプレートを配りたい。
もとになるオブジェクトをひとつ用意して、それを人数ぶんコピーして、一人ひとりに渡す。

ところが、Aさんの画面で条件をひとつ変えただけで、Bさんの画面まで同じように変わる。
保存もしていないのに、である。
JavaScript でよく踏む罠のひとつだ。

コピーしたつもりのところで、コピーされていない

たとえばこういうコードだ。

const base = { filters: ["camp", "outdoor"], sort: "new" };
const userA = { ...base };
userA.filters.push("gear");

スプレッド構文で複製したのだから、userA だけが変わるように見える。
実際には base.filters にも "gear" が増える。

MDN の説明にはこうある。

「スプレッド構文は、配列をコピーする際に効果的に1レベル深くコピーします。したがって、多次元配列のコピーには適していない可能性があります」
オブジェクトの場合も同じで、「単一のスプレッドは、元のオブジェクトのシャローコピーを作成します」と書いてある。

1レベルだけ、という言葉は読み流しやすい。
自分のオブジェクトで引っかかるまでは、気にならない一文だと思う。

1レベルの外側にあるものは、コピーではなく「同じもの」

{ ...base } がコピーするのは、filters という「箱」ではなく「箱の場所」だ。
箱の中身(配列そのもの)は複製されず、base.filters と userA.filters は同じ配列を指したままになる。

MDN の多次元配列の例が、まさにこの状態を示している。

const a = [[1], [2], [3]];
const b = [...a];
b.shift().shift();
// 1

「しまった。'a' も影響を受けてしまった」
と MDN 自身が書いている。
それだけ多くの人が踏む罠なのだと思う。

文字列や数値のような「値そのもの」を並べた配列なら、スプレッドでのコピーは期待どおりに動く。
要素がオブジェクトや配列になった瞬間、話が変わる。
上の例の base.filters は配列なので、まさにその境目にある。

直すなら structuredClone

いちばん手っ取り早い直し方は、ネストしている部分ごと複製することだ。

const userA = structuredClone(base);

これで base と userA は完全に独立する。
filters を push しても、base 側は変わらない。

以前は JSON.parse(JSON.stringify(base)) という書き方をよく見かけた。
動くのだが、関数や undefined、Date オブジェクトが混ざっていると、コピーの途中で形が変わったり消えたりする。
structuredClone はブラウザの組み込み機能で、Date なども形を保ったまま複製できる。
関数が入っているとエラーになるので、そこだけは別に扱う。
対応状況を確認したいときは、この API のページ自体を見るのが早い。

それでも全部を深くコピーすればいいわけではない

なんでも structuredClone で複製すれば安全かというと、そうとも言い切れない。

設定オブジェクトの中に、あえて共有しておきたい参照が混ざっていることがある。
例えばアイコンの画像オブジェクトのような「変更されない前提のもの」まで複製すると、無駄にメモリを食うし、同じ画像を指しているという前提が崩れて、後から見分けがつきにくくなる。

「この先で書き換えるかもしれない部分だけ複製する」という発想のほうが素直だと思う。
上の例なら filters と sort だけが書き換え対象なので、そこだけ複製すれば足りる。

疑うタイミングを一つ決めておく

複製してから片方だけ変えたつもりなのに両方変わる、という症状を見たら、まずここを疑うといい。

複製に使ったのがスプレッド構文か Object.assign で、かつ中身にオブジェクトや配列が入っていないか。
入っていたら、そこだけ浅いコピーで止まっている。

並べ替えで数字の順番がおかしくなる話を前に書いたけれど(並び替えたら、100が2より先に来た。JavaScriptは数字を数字として見ていなかった)、あれもこれも「見た目どおりに動いていると思い込みやすい場所」が原因だ。
JavaScript は親切に動くぶん、どこまでが自動でどこからが自分の思い込みか、たまに見えなくなる。

出典

※ 内容は2026年9月7日時点で各ページを確認したものです。

まる子パパ

まる子パパ

会社員。受託開発のエンジニアを15年以上続けていて、その前は寿司職人・長距離運転手・農業。 技術ブログは、キャンプの合間に踏んだバグと、AI・Web開発の運用の失敗を書いています。 このサイト自体が、開発に携わっているCMS「コンテナ」の稼働中の実例です。 このサイトについて / 小さな自動化の相談

—

ほかにも書いています

受託・note・スタンプ

※受託の相談とnoteは、運営者が個人で受けている・書いているものです。

技術ブログ一覧へ戻る