JavaScriptでオブジェクトをシャローコピー・ディープコピーする方法!

JavaScriptでは、オブジェクトをコピーする方法は大きく分けて「シャローコピー(浅いコピー)」と「ディープコピー(深いコピー)」の2種類があります。

この記事では『JavaScriptでオブジェクトをコピーする方法』について、以下の内容をサンプルコードを用いてわかりやすく解説します。

  • シャローコピーとディープコピーの違い
  • オブジェクトをシャローコピーする方法とその問題点
    • スプレッド構文でシャローコピーする
    • Object.assign()でシャローコピーする
  • オブジェクトをディープコピーする方法
    • JSON.stringify()JSON.parse()でディープコピーする
    • lodashライブラリのcloneDeep()メソッドでディープコピーする
    • structuredClone()メソッドでディープコピーする

シャローコピーとディープコピーの違い

まず、シャローコピーディープコピーの違いについて説明します。

  1. シャローコピー
    • 名前通り浅い(shallow)コピーです。
    • オブジェクトの1層目のプロパティだけをコピーします。ネストされたオブジェクトや配列は同じ参照を持つため、片方のオブジェクトを変更するともう一方にも影響が出る点に注意が必要です。
  2. ディープコピー
    • 名前通り深い(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層目のプロパティだけをコピーするので、shallowCopyaプロパティはコピーされます。そのため、shallowCopy.aを変更しても、obj.aには影響がありません。

しかし、shallowCopybプロパティはネストされたオブジェクトであり、その参照がコピーされているだけなので、obj.bshallowCopy.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はコピーできない点に注意

上記に示すように、それぞれの方法には特徴があり、目的に応じて使い分けることで、効率的にコピーできます。

お読み頂きありがとうございました。