📘

JavaScript / シャローコピーとディープコピー

2025/01/11に公開

非破壊的にコピーをしたい時によく使うものといえば

1, スプレッド演算子

const array = [1, 2, 3]
const copiedArray = [...array]
copiedArray.pop();

console.log(array)
// > [1,2,3]
console.log(copiedArray)
// > [1,2]

2, sliceメソッド(コピー操作に使われることは少ないけど)

const array = [1, 2, 3]
const copiedArray = array.slice(0, array.length)
copiedArray.pop();

console.log(array)
// > [1,2,3]
console.log(copiedArray)
// > [1,2]

このようにコピー元の配列に影響を与えることがなく重宝する。
実際「非破壊的に操作をしたいならスプレッド演算子を使え」とか「sliceは非破壊的、splitは破壊的」とか言われた人は多いと思う。

でも実は意外な落とし穴がある

以下の配列をスプレッド演算子でコピー&編集してみる

const persons = [
  john = {
    age: 20,
    nationality: "us",
    hobby: {
      reading: "novel",
      music: "guitar"
    }
  },
  alice = {
    age: 25,
    nationality: "uk",
    hobby: {
      reading: "comic",
      music: "piano"
    }
  }
];
const copiedPersons = [...persons];
delete (copiedPersons.join.hobby)

複製した配列からhobbyプロパティを削除した結果は?

console.log(persons.join.hobby)
> undefined
console.log(copiedPersons.join.hobby)
> undefined

コピー元、コピー先双方persons.john.hobbyプロパティが削除されている。なぜ?

ここでシャローコピーとディープコピーという概念に行き当たる

シャローコピー

  • オブジェクトや配列の最上位のプロパティのみをコピーし、ネストされた部分は参照がコピーされる。
  • スプレッド演算子やsliceメソッドで複製された配列はシャローコピーとなる
  • 参照の概念を知らない人は理解が難しいと思うが、平たく言ってしまえば「たとえコピーした配列だとしても、ネスト2階層目以降への操作はコピー元に影響を与える」ということ

ディープコピー

  • ネストされた部分も含め、元のオブジェクトや配列とは完全に独立したコピーを作成する。
  • JavaScriptにはディープコピーを作成するメソッドや構文は標準仕様として存在しない

ディープコピーの作り方

1, JSON.parse() と JSON.stringify() を使う

const original = {
  name: "John",
  hobbies: ["reading", "gaming"],
  address: {
    city: "New York",
    country: "USA"
  }
};

const deepCopy = JSON.parse(JSON.stringify(original));
console.log(deepCopy);

オブジェクトや配列を一旦JSON文字列に変換し、その文字列を再度パースして新しいオブジェクトを作成する。簡単な記述だけど、undefinedやメソッドがコピーできない

2, 再帰的にコピーを作成する

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

  // 新しいオブジェクトまたは配列を作成
  const copy = Array.isArray(obj) ? [] : {};

  for (let key in obj) {
    if (obj.hasOwnProperty(key)) {
      // 再帰的にコピー
      copy[key] = deepCopy(obj[key]);
    }
  }

  return copy;
}

const original = {
  name: "John",
  hobbies: ["reading", "gaming"],
  address: {
    city: "New York",
    country: "USA"
  }
};

const deepCopyResult = deepCopy(original);
console.log(deepCopyResult);

記述に手間がかかるが、jsonの制限なしにコピーができる

まとめ

前提知識として、参照の概念が必要になるので初学者の人たちは理解が難しいところだと思う。ディープコピーについてはライブラリで作成することも可能なようだ。
元も子もないけれども、個人的にはディープコピーを作成する標準機能を早く実装してもらいたい。

Discussion