LoginSignup
4
2

この記事で分かること

  • ディープコピーの定義
  • 様々なディープコピーの手法

1.背景

以下の記事でstructuredCloneの存在を知った為です。

2. ディープコピーとは

コピー先のオブジェクトのプロパティがコピー元のオブジェクトのプロパティと同一の参照(同じ値を指す)を共有しないコピーです。

3. ディープコピーを作成する方法

3.1. JSON.parse(JSON.stringify(オブジェクト))

JSON.stringify() でオブジェクトを JSON 文字列に変換しJSON.parse() で文字列から(完全に新しい) JavaScript のオブジェクトに変換します。

const originalObj = {key:'originalValue'};
const deepcopy = JSON.parse(JSON.stringify(originalObj));
deepcopy.key = 'deepcopyValue';
console.log(originalObj.key);
// originalValue

欠点

  • 再帰的なデータ構造を指定するとJSON.stringify() は例外をthrowします
    再帰的なデータ構造の例は以下です。
リンクされたリスト
class Node {
  constructor(data, next = null) {
    this.data = data;
    this.next = next;
  }
}

const node1 = new Node(1);
const node2 = new Node(2);
const node3 = new Node(3);

node1.next = node2;
node2.next = node3;
node3.next = node1;

// node1 -> node2 -> node3 -> node1 -> node2 -> node3 -> node1 ->...

これをディープコピーしようとすると確かに以下のようにErrorになります。

const deepcopy = JSON.parse(JSON.stringify(node1));
VM707:1 Uncaught TypeError: Converting circular structure to JSON
    --> starting at object with constructor 'Node'
    |     property 'next' -> object with constructor 'Node'
    |     property 'next' -> object with constructor 'Node'
    --- property 'next' closes the circle
    at JSON.stringify (<anonymous>)
    at <anonymous>:1:34

web.devでは「組み込み型で
Map、Set、Date、RegExp、ArrayBuffer などの他の JS 組み込みが含まれている場合にJSON.stringify() は例外をスローします」
と記述がありましたが、例外は throw されませんでした。
僕の解釈が誤っていたらコメントでご教授ください🙏
問題なければweb.devに質問してみたいと思います。

Map
const map = new Map();
JSON.stringify(map);
// '{}'
Set
const set = new Set();
JSON.stringify(set);
// '{}'
Date
const date = new Date();
JSON.stringify(date);
// '"2024-01-07T06:37:26.243Z"'
RegExp
const regExp = new RegExp('a');
JSON.stringify(regExp);
// '{}'
ArrayBuffer
const buffer = new ArrayBuffer(10);
JSON.stringify(buffer);
// '{}'
  • JSON.stringify() は関数を静かに破棄(例外をthrowせず関数のプロパティを無視)します。
const myObject = {
  key: 'value',
  myFunction: () => {
    console.log('This is a function.');
  }
};

const jsonString = JSON.stringify(myObject);
console.log(jsonString);
// {"key":"value"}

3.2. structuredClone

Structured Clone Algorithm を使用して、指定された値のディープ・クローンを作成します。

structuredCloneの歴史について以下の記事で紹介されていましたのでご覧ください。

structuredCloneの利点

  • SetやMapなどのオブジェクトもコピーできる
Map
const map = new Map();
structuredClone(map);
// Map(0) {size: 0}
Set
const set = new Set();
structuredClone(set);
// Set(0) {size: 0}
RegExp
const regExp = new RegExp('a');
structuredClone(regExp);
// /a/
ArrayBuffer
const buffer = new ArrayBuffer(10);
structuredClone(buffer);
// ArrayBuffer(10)
  • 循環したオブジェクトをコピーできる
リンクされたリスト
class Node {
  constructor(data, next = null) {
    this.data = data;
    this.next = next;
  }
}

const node1 = new Node(1);
const node2 = new Node(2);
const node3 = new Node(3);

node1.next = node2;
node2.next = node3;
node3.next = node1;

// node1 -> node2 -> node3 -> node1 -> node2 -> node3 -> node1 ->...

これをディープコピーしたのが以下です。

image.png

structuredCloneの欠点

  • JSON.parse(JSON.stringify(オブジェクト))の時と同様に関数はディープコピーできません。
    違う点は例外がthrowされます。
const func = () => 123;
const func2 = structuredClone(func);
// Uncaught DOMException: Failed to execute 'structuredClone' on 'Window': () => 123 could not be cloned.
  • 独自のクラスのインスタンスをコピーした際にPrototypeが何かという情報が失われる
class MyClass {
  foo = 1;
}

const obj = new MyClass();

const obj2 = structuredClone(obj);
obj2.foo // => 1
obj2 instanceof MyClass // => false
  • Symbolをコピーできない
const sym = Symbol("foo");
structuredClone(sym);
//Uncaught DOMException: Failed to execute 'structuredClone' on 'Window': Symbol(foo) could not be cloned.

3.3 LodashのcloneDeep

こちらは他記事で記載されていますので割愛します。

バンドルサイズを小さくする方法があるそうです。

lodash一部の関数を使う時
import _delay from 'lodash/delay';

3.4 just-clone

他記事のコメントで拝見しました。

バンドルサイズが lodash より小さいそうです。

just-clone

image.png

lodash

image.png

lodashと同様に関数のコピーが可能、独自classのインスタンスのコピーは不可能です。

4. 最後に

  • JSON.parse(JSON.stringify(オブジェクト)) の欠点の多くをカバーできるstructuredCloneを積極的に使いたいなと思いました。
    ただし、上で説明した欠点を気にしないで良い場面であることが条件です。
  • 参考になる記事を書いてくださった皆様に感謝致します!
  • 話は脱線しますが、業務でJQueryを使っているのでJQueryのバンドルサイズを小さくする方法を調べたいなと思いました。

参考

4
2
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
2