JavaScriptディープコピー完全ガイド!作り方・使い方・注意点・カスタマイズまで徹底解説

JavaScriptでディープコピーを解説する画像 JS
この記事は約9分で読めます。

【サイト内のコードはご自由に個人利用・商用利用いただけます】

この記事では、プログラムの基礎知識を前提に話を進めています。

説明のためのコードや、サンプルコードもありますので、もちろん初心者でも理解できるように表現してあります。

基本的な知識があればサンプルコードを活用して機能追加、目的を達成できるように作ってあります。

※この記事は、一般的にプロフェッショナルの指標とされる『実務経験10,000時間以上』を凌駕する現役のプログラマチームによって監修されています。

サイト内のコードを共有する場合は、参照元として引用して下さいますと幸いです

※Japanシーモアは、常に解説内容のわかりやすさや記事の品質に注力しております。不具合、分かりにくい説明や不適切な表現、動かないコードなど気になることがございましたら、記事の品質向上の為にお問い合わせフォームにてご共有いただけますと幸いです。
(送信された情報は、プライバシーポリシーのもと、厳正に取扱い、処分させていただきます。)

はじめに

この記事を読めばJavaScriptディープコピーを簡単に作成・利用できるようになります。

ディープコピーの基本から作り方、使い方、注意点、カスタマイズ方法まで徹底解説し、サンプルコードと応用例を交えて分かりやすく説明します。

JavaScript初心者でも安心して読める内容ですので、ぜひ最後までお読みください。

●JavaScriptディープコピーとは

○ディープコピーの基本

ディープコピーとは、オブジェクトや配列などのデータ構造を完全に複製することです。

シャローコピー(浅いコピー)とは異なり、ディープコピーでは元のデータ構造と複製したデータ構造が独立しています。

つまり、複製したデータ構造を変更しても、元のデータ構造に影響を与えません。

●ディープコピーの作り方

○サンプルコード1:JSON.parse()とJSON.stringify()を使ったディープコピー

このコードではJSON.parse()とJSON.stringify()を使ってディープコピーを実現します。

この例ではオブジェクトをJSON形式の文字列に変換し、その文字列を再度パースして新しいオブジェクトを生成しています。

const original = {
  a: 1,
  b: {
    c: 2
  }
};

const deepCopy = JSON.parse(JSON.stringify(original));
deepCopy.b.c = 3;

console.log(original.b.c); // 2
console.log(deepCopy.b.c); // 3

○サンプルコード2:再帰関数を使ったディープコピー

このコードでは再帰関数を使ってディープコピーを実現します。

この例では各プロパティを順番にコピーし、プロパティがオブジェクトや配列の場合は再帰的にコピーを行っています。

function deepCopy(obj) {
  if (typeof obj !== 'object' || obj === null) {
    return obj;
  }

  const result = Array.isArray(obj) ? [] : {};

  for (const key in obj) {
    if (Object.prototype.hasOwnProperty.call(obj, key)) {
      result[key] = deepCopy(obj[key]);
    }
  }

  return result;
}

const original = {
  a: 1,
  b: {
    c: 2
  }
};

const deepCopy = deepCopy(original);
deepCopy.b.c = 3;

console.log(original.b.c); // 2
console.log(deepCopy.b.c); // 3

●ディープコピーの使い方

○サンプルコード3:ディープコピーを使った配列のコピー

このコードではJSON.parse()とJSON.stringify()を使って、配列をディープコピーする方法を紹介しています。

この例では元の配列とコピーされた配列が独立していることを確認できます。

const originalArray = [
  {
    a: 1
  },
  {
    b: 2
  }
];

const copiedArray = JSON.parse(JSON.stringify(originalArray));
copiedArray[0].a = 3;

console.log(originalArray[0].a); // 1
console.log(copiedArray[0].a); // 3

○サンプルコード4:ディープコピーを使ったオブジェクトのコピー

このコードでは再帰関数を使って、オブジェクトをディープコピーする方法を紹介しています。

この例ではオブジェクト内のプロパティが参照型の場合でも、元のオブジェクトとコピーされたオブジェクトが独立していることを確認できます。

function deepCopy(obj) {
  if (typeof obj !== 'object' || obj === null) {
    return obj;
  }

  const result = Array.isArray(obj) ? [] : {};

  for (const key in obj) {
    if (Object.prototype.hasOwnProperty.call(obj, key)) {
      result[key] = deepCopy(obj[key]);
    }
  }

  return result;
}

const originalObject = {
  a: 1,
  b: {
    c: 2
  }
};

const copiedObject = deepCopy(originalObject);
copiedObject.b.c = 3;

console.log(originalObject.b.c); // 2
console.log(copiedObject.b.c); // 3

●ディープコピーの応用例

○サンプルコード5:ディープコピーを使った状態管理

このコードではディープコピーを使って、状態管理を行う方法を紹介しています。

この例では状態オブジェクトをディープコピーして更新することで、状態の変更が他の部分に影響を与えないことを確認できます。

function updateState(state, newState) {
  return {
    ...deepCopy(state),
    ...newState
  };
}

const initialState = {
  a: 1,
  b: {
    c: 2
  }
};

const newState = updateState(initialState, { a: 3, b: { c: 4 } });

console.log(initialState); // { a: 1, b: { c: 2 } }
console.log(newState); // { a: 3, b: { c: 4 } }

○サンプルコード6:ディープコピーを使った履歴機能の実装

このコードではディープコピーを使って、オブジェクトの編集履歴を保存し、元に戻す機能を実装する方法を紹介しています。

この例では編集履歴の配列にディープコピーされたオブジェクトを追加し、元に戻す際には最後の履歴を取り出すことで実現しています。

class History {
  constructor() {
    this.history = [];
  }

  add(obj) {
    this.history.push(deepCopy(obj));
  }

  undo() {
    return this.history.pop();
  }
}

const obj = { a: 1 };
const history = new History();

history.add(obj);
obj.a = 2;
history.add(obj);
obj.a = 3;

console.log(obj); // { a: 3 }
obj = history.undo();
console.log(obj); // { a: 2 }
obj = history.undo();
console.log(obj); // { a: 1 }

●注意点と対処法

○循環参照の問題

ディープコピーを実装する際には、循環参照が発生する可能性があることに注意が必要です。

循環参照が発生した場合、再帰的なディープコピーが無限ループに陥ることがあります。

これを回避するために、既にコピーされたオブジェクトをキャッシュしておく方法があります。

○パフォーマンスの問題

ディープコピーは、オブジェクトの構造が複雑であるほど、実行時間が長くなります。

パフォーマンスが重要な場合は、必要最低限のディープコピーを行うか、他の方法を検討することが望ましいです。

●ディープコピーのカスタマイズ方法

○サンプルコード7:特定のプロパティだけディープコピーする方法

このコードでは、特定のプロパティだけディープコピーする方法を紹介しています。

この例ではオブジェクトのプロパティbのみディープコピーを行い、他のプロパティはシャローコピーしています。

function customDeepCopy(obj, propertiesToDeepCopy) {
  const result = {};

  for (const key in obj) {
    if (propertiesToDeepCopy.includes(key)) {
      result[key] = deepCopy(obj[key]);
    } else {
      result[key] = obj[key];
    }
  }

  return result;
}

const originalObject = {
  a: 1,
  b: {
    c: 2
  }
};

const copiedObject = customDeepCopy(originalObject, ['b']);
copiedObject.b.c = 3;

console.log(originalObject.b.c); // 2
console.log(copiedObject.b.c); // 3

○サンプルコード8:ディープコピー時にカスタムロジックを適用する方法

このコードでは、ディープコピー時にカスタムロジックを適用する方法を紹介しています。

この例では、ディープコピーする際にプロパティの値が10より大きい場合、値を10に制限しています。

function customDeepCopyWithLogic(obj) {
  if (typeof obj !== 'object' || obj === null) {
    return obj;
  }

  const result = Array.isArray(obj) ? [] : {};

  for (const key in obj) {
    const value = obj[key];
    result[key] = typeof value === 'number' && value > 10 ? 10 : customDeepCopyWithLogic(value);
  }

  return result;
}

const originalObject = {
  a: 5,
  b: {
    c: 15
  }
};

const copiedObject = customDeepCopyWithLogic(originalObject);
console.log(copiedObject); // { a: 5, b: { c: 10 } }

まとめ

この記事では、JavaScriptにおけるディープコピーの基本概念、作成方法、利用方法、応用例、注意点、そしてカスタマイズ方法を紹介しました。

ディープコピーを適切に使用することで、データの編集や状態管理を安全かつ効率的に行うことができます。

ただし、循環参照やパフォーマンスの問題に注意して、適切な方法でディープコピーを実装することが重要です。