JavaScriptで開発をしていると、オブジェクトや配列を別の変数にコピーして使いたい場面が頻繁に訪れます。
しかし、「コピーしたはずのデータを変更したら、元のデータまで変わってしまった!」というバグに遭遇したことはないでしょうか?
この仕組みを理解せずに単純な代入を行ってしまうと、予期せぬ不具合の原因となります。
この記事では、JavaScriptにおけるオブジェクトコピーの落とし穴である「参照の共有」の解説から、スプレッド構文を使った手軽なコピー、そして最新の標準機能である structuredClone を使った完全なコピー(ディープコピー)まで、状況に応じた正しいコピー方法を徹底解説します。
![]() 執筆者:マヒロ |
|
JavaScriptにおけるオブジェクトコピーの基本と落とし穴
JavaScriptでオブジェクトや配列を扱う際、最も注意しなければならないのが「コピーの深さ」と「参照」の概念です。
まずは、なぜ単純な代入ではうまくいかないのか、そして「シャローコピー」と「ディープコピー」の違いについて理解しましょう。
単なる代入は「コピー」ではない(参照のコピー)
変数を別の変数に = で代入しただけでは、オブジェクトの中身はコピーされません。
コピーされるのは、オブジェクトがメモリ上のどこにあるかを示す「住所(参照)」だけです。
以下のコードを見てみましょう。
const original = { name: "Tanaka", age: 25 };
// 変数を代入(参照のコピー)
const copy = original;
// コピー先のプロパティを変更
copy.age = 30;
console.log("copy:", copy);
console.log("original:", original);
実行結果
copy: { name: 'Tanaka', age: 30 }
original: { name: 'Tanaka', age: 30 }
const copy = original; とした時点で、この2つの変数は同じオブジェクト(同じメモリ上の場所)を指し示すようになります。
そのため、copy の内容を変更すると、同じ場所を見ている original の内容も変わってしまいます。
これを防ぐためには、新しいオブジェクトを作成して中身を移す「複製」の処理が必要です。
シャローコピー(浅いコピー)とディープコピー(深いコピー)の違い
オブジェクトを複製する方法には、大きく分けて2つのレベルがあります。
シャローコピー(浅いコピー)
オブジェクトの「1階層目」だけを新しい入れ物にコピーします。
ただし、オブジェクトの中にさらにオブジェクト(ネスト)がある場合、その内側のオブジェクトは「参照」のままコピーされます。
ディープコピー(深いコピー)
オブジェクトの階層がどれだけ深くても、すべてを再帰的に複製します。
コピー元とコピー先は完全に切り離され、互いに影響を与えることはありません。
シャローコピー(浅いコピー)の実装方法
ネスト(入れ子)されていないシンプルなオブジェクトであれば、処理が高速なシャローコピーで十分です。
現代のJavaScript開発では、主に「スプレッド構文」が使用されます。
スプレッド構文 ... を使う(推奨)
ES2015(ES6)から導入されたスプレッド構文を使うと、非常に簡潔にオブジェクトをコピーできます。
... を使ってオブジェクトの中身を展開し、新しい {} の中に詰め込むイメージです。
const original = { name: "Suzuki", age: 28 };
// スプレッド構文でシャローコピー
const copy = { ...original };
// コピー先を変更
copy.age = 35;
console.log("copy:", copy);
console.log("original:", original);
実行結果
copy: { name: 'Suzuki', age: 35 }
original: { name: 'Suzuki', age: 28 }
{ ...original } と記述することで、original のプロパティを展開して新しいオブジェクトを作成しています。
今度は copy を変更しても、original は影響を受けていません。
シンプルなデータの複製なら、この方法が最も一般的で推奨されます。
Object.assign() を使う
スプレッド構文が登場する前によく使われていた方法です。
現在でも互換性維持などのコードで見かけることがあります。
const original = { name: "Sato", isStaff: true };
// Object.assignで空のオブジェクトにプロパティをコピー
const copy = Object.assign({}, original);
console.log(copy);
実行結果
{ name: 'Sato', isStaff: true }
Object.assign({}, original) は、第一引数の空オブジェクト {} に対して、第二引数 original のプロパティを上書きコピーして返します。
結果として新しいオブジェクトが生成されます。
シャローコピーの限界(ネストされたデータの罠)
シャローコピーはあくまで「浅い」コピーです。
オブジェクトの中にさらにオブジェクトがある場合(ネスト構造)、内側のデータは参照共有されたままになります。
const original = {
id: 1,
details: { color: "red", size: "L" } // ネストされたオブジェクト
};
const shallowCopy = { ...original };
// ネストされた中のデータを変更
shallowCopy.details.color = "blue";
console.log(original.details.color);
実行結果
blue
shallowCopy の中の details を変更したはずなのに、original の details.color も "blue" に変わってしまいました。
これは、1階層目の id や details というプロパティ自体はコピーされましたが、details の中身(オブジェクトへの参照)はそのままコピーされたためです。
このような複雑なデータを扱う場合は、次項の「ディープコピー」が必要になります。
ディープコピー(深いコピー)の実装方法
ネストされたオブジェクトや配列を含む複雑なデータを完全に複製するには、ディープコピーを行います。
以前は外部ライブラリやJSON変換を使うのが定石でしたが、現在は標準機能だけで安全に実現できるようになりました。
structuredClone() を使う(推奨・モダン)
2022年頃から主要なブラウザやNode.jsでサポートされた structuredClone() 関数は、JavaScript標準のディープコピー機能です。
特別なライブラリを入れずに使えて、後述するJSON変換よりも多くのデータ型(Date型やMap, Setなど)に対応しています。
const original = {
id: 1,
details: { color: "red", size: "L" },
created: new Date()
};
// structuredCloneでディープコピー
const deepCopy = structuredClone(original);
// コピー先を変更
deepCopy.details.color = "green";
console.log("original color:", original.details.color);
console.log("copy color:", deepCopy.details.color);
実行結果
original color: red
copy color: green
非常に便利な structuredClone ですが、万能ではありません。
以下のデータはコピーできず、エラーが発生するか意図しない結果になります。
- 関数(Function):エラーが発生します。
- DOM要素:エラーが発生します。
- Symbol:無視されます。
- プロトタイプチェーン:インスタンスのクラス情報は失われ、プレーンなオブジェクトになります。
JSON.parse(JSON.stringify()) を使う(従来の方法)
古くから使われている「JSON文字列に一度変換してから、オブジェクトに戻す」というテクニックです。
手軽ですが、以下のような制約があります。
- 関数や
undefined、Symbolは消滅する。 - Dateオブジェクトは文字列になる。
- NaN や Infinity は
nullになる。 - 循環参照しているオブジェクトを渡すとエラーになる。
const original = {
id: 2,
details: { area: "Tokyo" }
};
// JSON変換を経由してディープコピー
const deepCopy = JSON.parse(JSON.stringify(original));
deepCopy.details.area = "Osaka";
console.log(original.details.area); // Tokyo (変更されていない)
JSON.stringify でオブジェクトを文字列化することで参照を断ち切り、JSON.parse で再度オブジェクト化しています。
純粋なJSONデータ(数値、文字列、配列、オブジェクトのみ)であれば問題なく動作します。
パフォーマンスについては、扱うデータの種類やサイズによって structuredClone より速い場合も遅い場合もあるため、状況に応じた判断が必要です。
ライブラリ(Lodash)を使う
大規模な開発で、すでにユーティリティライブラリの Lodash を導入している場合は、cloneDeep メソッドを使うのも一般的です。
// Lodashが必要(バンドルサイズを考慮して個別インポート推奨)
import cloneDeep from 'lodash/cloneDeep';
const deepCopy = cloneDeep(original);
structuredClone が使えない古い環境(レガシーブラウザなど)をサポートする必要がある場合には、こうしたライブラリが頼りになります。
配列のコピーについて
配列(Array)もJavaScriptではオブジェクトの一種であるため、ここまでの話はすべて配列にも当てはまります。
配列のスプレッド構文とディープコピー
配列をコピーする場合も、スプレッド構文 [...] が便利です。
もちろんこれもシャローコピーとなります。
const numbers = [1, 2, 3];
const numbersCopy = [...numbers]; // 配列のシャローコピー
const users = [{ name: "A" }, { name: "B" }];
// 配列の中にオブジェクトがある場合は注意
const usersCopy = [...users];
// usersCopy[0].name を変えると users[0].name も変わる(参照の共有)
// 完全なコピーが必要なら structuredClone
const usersDeepCopy = structuredClone(users);
[...numbers] のように配列リテラルの中でスプレッド構文を使うことで、要素を展開して新しい配列を作成できます。
しかし、配列の要素がオブジェクトである場合(users の例)、その中身は参照が共有されたままです。
この場合も structuredClone を使うことで、配列内のオブジェクトごと完全に複製できます。
JavaScriptのスキルを活かして年収を上げる方法
以上、JavaScriptでオブジェクトをコピーする方法について解説してきました。
なお、JavaScriptのスキルがある場合には、「副業で稼いで年収を上げる」といったことが可能です。
エージェントサービスは、登録も利用もすべて完全無料なので、どんな求人や案件があるのか気になる方は、気軽に利用してみるとよいでしょう。



