JavaScriptでは、オブジェクトをコピーする方法は大きく分けて「シャローコピー(浅いコピー)」と「ディープコピー(深いコピー)」の2種類があります。
この記事では『JavaScriptでオブジェクトをコピーする方法』について、以下の内容をサンプルコードを用いてわかりやすく解説します。
- シャローコピーとディープコピーの違い
- オブジェクトをシャローコピーする方法とその問題点
- スプレッド構文でシャローコピーする
Object.assign()
でシャローコピーする
- オブジェクトをディープコピーする方法
JSON.stringify()
とJSON.parse()
でディープコピーするlodash
ライブラリのcloneDeep()
メソッドでディープコピーするstructuredClone()
メソッドでディープコピーする
シャローコピーとディープコピーの違い
まず、シャローコピーとディープコピーの違いについて説明します。
- シャローコピー
- 名前通り浅い(shallow)コピーです。
- オブジェクトの1層目のプロパティだけをコピーします。ネストされたオブジェクトや配列は同じ参照を持つため、片方のオブジェクトを変更するともう一方にも影響が出る点に注意が必要です。
- ディープコピー
- 名前通り深い(deep)コピーです。
- ネストされたオブジェクトや配列を含め、すべてのデータを完全にコピーします。そのため、片方のオブジェクトを変更しても一方には影響が出ません。
オブジェクトをシャローコピーする方法とその問題点
オブジェクトをシャローコピーする方法について、以下の内容を順番に説明します。
- スプレッド構文でシャローコピーする
Object.assign()
でシャローコピーする
スプレッド構文でシャローコピーする
JavaScriptでは、スプレッド構文(...
)を使ってシャローコピーを簡単に行うことができます。サンプルコードを以下に示します。
const obj = {
a: 'a',
b: {
bb: 'bb',
},
};
// スプレッド構文でシャローコピー
const shallowCopy = { ...obj };
// プロパティを変更
shallowCopy.a = 'hoge';
shallowCopy.b.bb = 'hoge';
console.log(obj);
// {
// a: 'a', ← 元のオブジェクトの a プロパティは影響を受けない
// b: {
// bb: 'hoge', ← ネストされたオブジェクトのプロパティ bb は影響を受ける
// },
// };
console.log(shallowCopy);
// {
// a: 'hoge',
// b: {
// bb: 'hoge',
// },
// };
シャローコピーはオブジェクトの1層目のプロパティだけをコピーするので、shallowCopy
のa
プロパティはコピーされます。そのため、shallowCopy.a
を変更しても、obj.a
には影響がありません。
しかし、shallowCopy
のb
プロパティはネストされたオブジェクトであり、その参照がコピーされているだけなので、obj.b
とshallowCopy.b
は同じオブジェクトを指します。そのため、shallowCopy.b.bb
を変更すると、obj.b.bb
も変更されます。
Object.assign()でシャローコピーをする
Object.assign()
を使ってもシャローコピーができます。サンプルコードを以下に示します。
const obj = {
a: 'a',
b: {
bb: 'bb',
},
};
// Object.assignでシャローコピー
const shallowCopy = Object.assign({}, obj);
// プロパティを変更
shallowCopy.a = 'hoge';
shallowCopy.b.bb = 'hoge';
console.log(obj);
// {
// a: 'a', ← 元のオブジェクトの a プロパティは影響を受けない
// b: {
// bb: 'hoge', ← ネストされたオブジェクトのプロパティ bb は影響を受ける
// },
// };
console.log(shallowCopy);
// {
// a: 'hoge',
// b: {
// bb: 'hoge',
// },
// };
上記のサンプルコードでもシャローコピーをしているので、shallowCopy.b.bb
を変更すると、obj.b.bb
にも影響します。
オブジェクトをディープコピーする方法
オブジェクトをディープコピーする方法について、以下の内容を順番に説明します。
JSON.stringify()
とJSON.parse()
でディープコピーするlodash
ライブラリのcloneDeep()
メソッドでディープコピーするstructuredClone()
メソッドでディープコピーする
JSON.stringify()とJSON.parse()でディープコピーする
ディープコピーの簡単な方法として、JSON.stringify()
でオブジェクトを文字列に変換し、それをJSON.parse()
で再度オブジェクトに変換する方法があります。サンプルコードを以下に示します。
const obj = {
a: 'a',
b: {
bb: 'bb',
},
};
//JSON.stringify()とJSON.parse()でディープコピー
const deepCopy = JSON.parse(JSON.stringify(obj));
// プロパティを変更
deepCopy.a = 'hoge';
deepCopy.b.bb = 'hoge';
console.log(obj);
// {
// a: 'a', ← 元のオブジェクトは影響を受けない
// b: {
// bb: 'bb', ← 元のオブジェクトは影響を受けない
// },
// };
console.log(deepCopy);
// {
// a: 'hoge',
// b: {
// bb: 'hoge',
// },
// };
ディープコピーはネストされたオブジェクトや配列を含め、すべてのデータを完全にコピーします。そのため、shallowCopy.b.bb
を変更しても、obj.b.bb
には影響がありません。ただし、この方法には以下の問題点があります。
undefined
や関数はコピーされないDate
オブジェクトは文字列(string
型)になる
実際にサンプルコードで確認してみましょう。
const obj = {
a: 'a',
b: {
bb: 'bb',
},
c: undefined,
func: () => {
return 'hello!!';
},
date: new Date(),
};
//JSON.stringify()とJSON.parse()でディープコピー
const deepCopy = JSON.parse(JSON.stringify(obj));
console.log(obj);
// {
// a: 'a',
// b: {
// bb: 'bb',
// },
// c: undefined,
// func: [Function: func],
// date: 2024-10-21T01:01:01.001Z
// };
console.log(deepCopy);
// {
// a: 'a',
// b: {
// bb: 'bb',
// },
// date: '2024-10-21T01:01:01.001Z', ← Dateオブジェクトは文字列(string型)に変換される
// };
// undefinedや関数はコピーされていない
lodashライブラリのcloneDeep()メソッドでディープコピーする
lodash
ライブラリのcloneDeep()
メソッドを使うと、JSON.stringify()
とJSON.parse()
で生じる問題を解決できます。lodash
ライブラリを用いると、関数やundefined
を含むオブジェクトも適切にディープコピーできます。
まずはlodash
をインストールします。
npm install lodash
次に、以下のコードを使用してディープコピーを実行します。
const _ = require('lodash');
// または ES Modules を使用している場合は以下のコード
// import _ from 'lodash';
const obj = {
a: 'a',
b: {
bb: 'bb',
},
c: undefined,
func: () => {
return 'hello!!';
},
date: new Date(),
};
//lodashライブラリのcloneDeep()メソッドでディープコピーする
const deepCopy = _.cloneDeep(obj);
console.log(obj);
// {
// a: 'a',
// b: {
// bb: 'bb',
// },
// c: undefined,
// func: [Function: func],
// date: 2024-10-21T01:01:01.001Z
// };
console.log(deepCopy);
// {
// a: 'a',
// b: {
// bb: 'bb',
// },
// c: undefined,
// func: [Function: func],
// date: 2024-10-21T01:01:01.001Z
// };
structuredClone()メソッドを使う方法
ディープコピーを作成する別の方法で、structuredClone()
メソッドを使用する方法もあります。structuredClone()
メソッドは、ほとんどのデータ型を正確にコピーできますが、関数やSymbol
などはコピーできない点に注意が必要です。サンプルコードを以下に示します(関数はコピーできないので、コピー元オブジェクト(obj
)から関数を抜いてディープコピーしています)。
const obj = {
a: 'a',
b: {
bb: 'bb',
},
c: undefined,
date: new Date(),
};
//structuredClone()メソッドでディープコピー
const deepCopy = structuredClone(obj);
console.log(obj);
// {
// a: 'a',
// b: {
// bb: 'bb',
// },
// c: undefined,
// date: 2024-10-21T01:01:01.001Z
// };
console.log(deepCopy);
// {
// a: 'a',
// b: {
// bb: 'bb',
// },
// c: undefined,
// date: 2024-10-21T01:01:01.001Z
// };
コピー元オブジェクト(obj
)に関数がある状態でコピーすると、以下に示すようにDOMExceptionがスローされます。
DOMException [DataCloneError]: () => {
return 'hello!!';
} could not be cloned.
本記事のまとめ
この記事では『JavaScriptでオブジェクトをディープコピーをする方法』について、以下の内容を説明しました。
- オブジェクトのコピーにはシャローコピーとディープコピーがある
- シャローコピーする方法
- スプレッド構文を使う
Object.assign()
を使う
- ディープコピーする方法
JSON.stringify()
とJSON.parse()
を使うundefined
、関数はコピーできない、Date
オブジェクトは文字列(string
型)に変換される点に注意
lodash
ライブラリのcloneDeep()
メソッドを使うnpm
ライブラリとしてlodash
をインストールする必要がある
structuredClone()
メソッドを使う- 関数や
Symbol
はコピーできない点に注意
- 関数や
上記に示すように、それぞれの方法には特徴があり、目的に応じて使い分けることで、効率的にコピーできます。
お読み頂きありがとうございました。