JavaScriptにおけるshallow copyとdeep copyの違いを理解する
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
オブジェクトは参照としてコピーされています。そのため、shallowCopy
でdetails.age
を変更すると、original
のdetails
も影響を受けます。
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
この例では、lodash
のcloneDeep
を使って深いコピーを行っています。これにより、deepCopy
のdetails
を変更しても、original
には影響がありません。
Deep Copyを行う方法
-
lodash
のcloneDeep
関数- 安全に深いコピーを行うためには、
lodash
などのライブラリを使用するのが一般的です。
- 安全に深いコピーを行うためには、
例えば、lodash
のcloneDeep
を使うと、関数や複雑な構造を含むオブジェクトでも安全に深いコピーを行うことができます。
const deepCopy = _.cloneDeep(original);
Deep Copyの注意点
deep copy
は、オブジェクトが非常に大きい場合やネストが深すぎる場合にはパフォーマンスに影響を与えることがあります。特に、再帰的なコピーを行うため、処理コストが高くなる可能性があります。そのため、大規模なデータ構造のコピーには注意が必要です。
単純な変数の代入について
JavaScriptにおいて、基本データ型(プリミティブ型)であるnumber
、string
、boolean
、null
、undefined
などを代入する場合、値そのものがコピーされます。これを「値渡し」と呼びます。そのため、単純な変数の代入においては、コピー後の変更が元の変数に影響を与えることはありません。
一方で、オブジェクトや配列などの複合データ型の場合、参照がコピーされます。これを「参照渡し」と呼びます。この場合、コピーされた変数は同じオブジェクトを指しているため、一方を変更するともう一方にも影響が及びます。
例:単純な変数の代入と参照渡し
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 copy
やdeep copy
を使うことで意図しない変更を防ぐ必要があります。
Shallow CopyとDeep Copyの選択基準
どちらのコピー方法を使うべきかは、次のような基準で考えると良いでしょう。
- ネストの深さが浅く、単純なデータ構造の場合: Shallow Copyで十分です。
- ネストされたオブジェクトや配列が含まれており、それらを独立させたい場合: Deep Copyを使用すべきです。
- コピーしたデータの一部だけを変更したい場合: 必ずDeep Copyを行い、元のデータへの影響を避けるようにしましょう。
また、パフォーマンス面を考慮して、必要以上にDeep Copyを使うことは避け、状況に応じて効率的に使い分けることが大切です。
関連するトピック
- イミュータビリティ(不変性): Reactなどのフレームワークで重要な概念であり、データを変更せずにコピーすることが推奨されます。Deep Copyは、オブジェクトを不変のまま新しいコピーを作る際に役立ちます。
- 参照と値の違い: JavaScriptにおいて、オブジェクトや配列は参照として扱われるため、コピー方法によって動作が異なります。これを理解することで、Shallow CopyとDeep Copyの使いどころが見えてきます。
-
ライブラリの選択: 深いコピーを行うためのライブラリとして、
lodash
以外にもramda
や他のユーティリティライブラリがあります。それぞれの特徴を理解し、用途に合ったものを選ぶと良いでしょう。
まとめ
- Shallow Copy(浅いコピー): 1階層のみコピーし、ネストされたオブジェクトは参照がコピーされる。
- Deep Copy(深いコピー): 再帰的に全ての階層をコピーし、元のオブジェクトと独立する。
オブジェクトのコピーを行う際は、データの構造に応じてshallow copy
とdeep copy
を使い分けることが重要です。特に、ネストされたデータを扱う場合は、意図しない変更が起こらないように、どちらのコピーが必要かをしっかりと理解しておきましょう。
Discussion