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

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

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

設定画面を作っていた。

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

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

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

やっていたのはこういうコードだった。

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 はブラウザの組み込み機能として同じことをやってくれて、その手の欠けが起きにくい。
対応状況を確認したいときは、この API のページ自体を見るのが早い。

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

ここで一度立ち止まった。
なんでも structuredClone で複製すれば安全かというと、そうとも言い切れない。

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

結局のところ、「この先で書き換えるかもしれない部分だけ複製する」という発想のほうが素直だった。
うちの場合は filters と sort だけが書き換え対象だったので、そこだけ複製すれば足りた。

疑うタイミングを一つ決めた

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

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

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

出典

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

まる子パパ

まる子パパ

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

ほかにも書いています

note・スタンプ

※noteは運営者が個人で書いているものです。

技術ブログ一覧へ戻る