配列をコピーしたつもりが、元のデータも変わっていた|スプレッド構文は一段しかコピーしない
配列をスプレッド構文でコピーしたつもりが、複製先を書き換えただけで元のデータまで変わっていた。
一段しかコピーしないと知って、直し方が変わった。
設定画面を作っていた。
ユーザーごとに、絞り込み条件のテンプレートを配りたかった。
もとになる配列をひとつ用意して、それを人数ぶんコピーして、一人ひとりに渡すつもりだった。
ところが、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日時点で各ページを確認したものです。
ほかにも書いています
note・スタンプ※noteは運営者が個人で書いているものです。
