JavaScript:データ(オブジェクト)をディープコピーする

”データ(オブジェクト)”としているのは、

  • オブジェクトにすると話が広がる( function や getter / setter 等も含むよね)
  • そういったオブジェクトをディープコピーする機会に会ったことがない

からです。で、どうするかですが、

  • structuredClone を使う (2022/04/12追記)
    使える環境でサポート対象の型のデータなら、これが良さそうです。

    以下の話は、使えなかったときの参考になれば、ぐらいしょうか。。
  • ライブラリが使える環境であればそれを頼る
  • JSON.parse、JSON.stringify でコピーしても問題ないデータであるなら使う

かなと思います。そして避けるのは、

  • Objects.assign を使う

でしょうか。説明を読むと勘違いしそうですが、シャローコピーなので駄目でしょう。
ということで、それぞれどんな感じか見ていきたいと思います。
ライブラリは以下を試してみます。

コピー元のデータ

以下のデータを例に試します。

  const data = {
    n: 1,
    s: 'abc',
    d: new Date('2022-01-01T00:00:00Z'),
    u: undefined,
    o: {
      n: 2,
      s: 'def',
      d: new Date('2022-01-02T00:00:00Z'),
      u: undefined,
    }
  };

jQuery ライブラリを使う

import $ from 'jquery';

const j = $.extend(true, {}, data);
console.log(j);

コンソールに出力される結果は、

ということで、undefined だった data.u、data.o.u が消失していますね。
使用するなら許容できるか検討が必要ですが、私自身が使う範囲では問題にならないと思いました。

lodash ライブラリを使う

import _ from 'lodash';

const l =  _.cloneDeep(data);
console.log(l);

コンソールに出力される結果は、

同じですね。

angular ライブラリを使う

import angular from 'angular';

const a = angular.copy(data);
console.log(a);

コンソールに出力された結果は、lodash と同じだったので割愛します。

JSON.parse、JSON.stringify を使う

const s = JSON.parse(JSON.stringify(data));
console.log(s);

コンソールに出力される結果は、

  • 日付が文字列になった(s.d、s.o.d)
  • undefined が消失した(s.u、s.o.u)

ということで、こちらも使う場合は許容できるか検討が必要でしょう。
数値や文字列でない型が含まれるデータで使うとハマるかもしれません。

Objects.assign を使う

シャローコピーなことを後で確認する為に、Objects.assign も使ってみます。

const o = Object.assign({}, data);
console.log(o);

コンソールに出力される結果は、

と一見良さそうですが、コピー先は以下と同様でディープコピーになっていません。

const o = { ...data };

ディープコピーされているか

コピー元のデータを更新した後、コピー先のデータをそれぞれ出力してみます。
結果はコメントに記載しました。

data.o.n = 9;
console.log(j); // j.o.n = 2 : jquery
console.log(l); // l.o.n = 2 : lodash
console.log(a); // a.o.n = 2 : angular
console.log(s); // s.o.n = 2 : JSON.stringify, JSON.parse
console.log(o); // o.o.n = 9 : Objects.assign

ということで、Objects.assign 以外は大丈夫ですね。
Objects.assign の場合、コピー先を変えたつもりがコピー元も変わるので、勘違いして使っているとハマるかもしれません。

o.o.n = 7;
console.log(data); // data.o.n = 7

ということで、どれを選択するにしても、コピー対象のデータで問題ないか検討してからの方が良いでしょう。

コメント