🔍

JavaScriptにおけるshallow copyとdeep copyの違いを理解する

2024/10/30に公開

JavaScriptでオブジェクトをコピーする際に、「shallow copy(浅いコピー)」と「deep copy(深いコピー)」という言葉をよく耳にするかと思います。本記事では、これら二つの違いについて、シンプルな例を用いて分かりやすく説明します。

Shallow Copy(浅いコピー)とは

shallow copyは、元のオブジェクトのプロパティを1階層だけコピーします。つまり、オブジェクトの最上位にあるプロパティはコピーされますが、ネストされたオブジェクトや配列などの複雑なデータ構造は、参照がコピーされるだけです。

例:Shallow Copyの動作

以下のコードを見てみましょう。

const original = {
  name: "Alice",
  details: {
    age: 25,
    city: "Tokyo"
  }
};

const shallowCopy = { ...original };

// コピー後に変更してみる
shallowCopy.details.age = 30;

console.log(original.details.age); // 出力: 30

shallowCopyを作成するためにスプレッド構文({ ...original })を使用していますが、detailsオブジェクトは参照としてコピーされています。そのため、shallowCopydetails.ageを変更すると、originaldetailsも影響を受けます。

Shallow Copyを行う方法

  • スプレッド構文 { ...original }
  • Object.assign({}, original)

これらの方法では、ネストされたオブジェクトは参照のコピーとなるため注意が必要です。

Shallow Copyの注意点

shallow copyは、オブジェクトの階層が深くなると意図しない動作を引き起こすことがあります。たとえば、複数階層のネストされたオブジェクトをコピーした場合、内部のネストされたオブジェクトは参照のみがコピーされるため、コピーしたつもりでも元のオブジェクトに影響を及ぼしてしまうことがあります。このため、浅いコピーが適しているのは、ネストが少なくシンプルなオブジェクトに限られます。

Deep Copy(深いコピー)とは

deep copyは、オブジェクトの全ての階層を再帰的にコピーします。これにより、元のオブジェクトとコピーされたオブジェクトは完全に独立し、ネストされたオブジェクトや配列の変更もお互いに影響しません。

例:Deep Copyの動作

以下のコードでdeep copyを行ってみます。

const original = {
  name: "Alice",
  details: {
    age: 25,
    city: "Tokyo"
  }
};

const _ = require('lodash');
const deepCopy = _.cloneDeep(original);

// コピー後に変更してみる
deepCopy.details.age = 30;

console.log(original.details.age); // 出力: 25

この例では、lodashcloneDeepを使って深いコピーを行っています。これにより、deepCopydetailsを変更しても、originalには影響がありません。

Deep Copyを行う方法

  • lodashcloneDeep関数
    • 安全に深いコピーを行うためには、lodashなどのライブラリを使用するのが一般的です。

例えば、lodashcloneDeepを使うと、関数や複雑な構造を含むオブジェクトでも安全に深いコピーを行うことができます。

const deepCopy = _.cloneDeep(original);

Deep Copyの注意点

deep copyは、オブジェクトが非常に大きい場合やネストが深すぎる場合にはパフォーマンスに影響を与えることがあります。特に、再帰的なコピーを行うため、処理コストが高くなる可能性があります。そのため、大規模なデータ構造のコピーには注意が必要です。

単純な変数の代入について

JavaScriptにおいて、基本データ型(プリミティブ型)であるnumberstringbooleannullundefinedなどを代入する場合、値そのものがコピーされます。これを「値渡し」と呼びます。そのため、単純な変数の代入においては、コピー後の変更が元の変数に影響を与えることはありません。

一方で、オブジェクトや配列などの複合データ型の場合、参照がコピーされます。これを「参照渡し」と呼びます。この場合、コピーされた変数は同じオブジェクトを指しているため、一方を変更するともう一方にも影響が及びます。

例:単純な変数の代入と参照渡し

let a = 10;
let b = a;
b = 20;
console.log(a); // 出力: 10(プリミティブ型は値渡し)

const obj1 = { value: 10 };
const obj2 = obj1;
obj2.value = 20;
console.log(obj1.value); // 出力: 20(オブジェクトは参照渡し)

この例からもわかるように、基本データ型はコピー時に新しい値が生成されるため安全ですが、オブジェクトや配列などを扱う際には、shallow copydeep copyを使うことで意図しない変更を防ぐ必要があります。

Shallow CopyとDeep Copyの選択基準

どちらのコピー方法を使うべきかは、次のような基準で考えると良いでしょう。

  1. ネストの深さが浅く、単純なデータ構造の場合: Shallow Copyで十分です。
  2. ネストされたオブジェクトや配列が含まれており、それらを独立させたい場合: Deep Copyを使用すべきです。
  3. コピーしたデータの一部だけを変更したい場合: 必ずDeep Copyを行い、元のデータへの影響を避けるようにしましょう。

また、パフォーマンス面を考慮して、必要以上にDeep Copyを使うことは避け、状況に応じて効率的に使い分けることが大切です。

関連するトピック

  • イミュータビリティ(不変性): Reactなどのフレームワークで重要な概念であり、データを変更せずにコピーすることが推奨されます。Deep Copyは、オブジェクトを不変のまま新しいコピーを作る際に役立ちます。
  • 参照と値の違い: JavaScriptにおいて、オブジェクトや配列は参照として扱われるため、コピー方法によって動作が異なります。これを理解することで、Shallow CopyとDeep Copyの使いどころが見えてきます。
  • ライブラリの選択: 深いコピーを行うためのライブラリとして、lodash以外にもramdaや他のユーティリティライブラリがあります。それぞれの特徴を理解し、用途に合ったものを選ぶと良いでしょう。

まとめ

  • Shallow Copy(浅いコピー): 1階層のみコピーし、ネストされたオブジェクトは参照がコピーされる。
  • Deep Copy(深いコピー): 再帰的に全ての階層をコピーし、元のオブジェクトと独立する。

オブジェクトのコピーを行う際は、データの構造に応じてshallow copydeep copyを使い分けることが重要です。特に、ネストされたデータを扱う場合は、意図しない変更が起こらないように、どちらのコピーが必要かをしっかりと理解しておきましょう。

株式会社くりぼー

Discussion