公開日

【JavaScript】オブジェクトをコピーする方法

JavaScriptでオブジェクトをコピーする方法には様々なものが存在します。状況に応じて、適切なコピー方法を選ぶことが重要です。

著者
  • avatar
    名前
    Lan oo
    Twitter
    @Lan_o_sir
  • Freelance IT Engineer
目次

JavaScriptでオブジェクトをコピーする際に考慮すべきポイントは、Shallow Copy(浅いコピー)とDeep Copy(深いコピー)の違いです。それぞれの方法と利点を理解し、正しいケースで使用しましょう。

Shallow Copy

浅いコピーは、オリジナルのオブジェクトとコピーしたオブジェクトが、ネストされたオブジェクトへの参照を共有する方式です。コピー元のオブジェクトを変更しても、コピー先のオブジェクトには影響しませんが、参照先のネストされたオブジェクトを変更すると、双方に影響が出ます。

スプレッド構文

スプレッド構文(…)を使うと、オブジェクトを浅くコピーすることができます。

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

// ネストされたオブジェクトを変更すると元のオブジェクトにも影響が出る
shallowCopy.b.c = 3;
console.log(original.b.c); // 3

この例では、スプレッド構文で生成した shallowCopy オブジェクト内のプロパティ b.c を変更すると、元の original オブジェクトにも影響が及んでいます。

Object.assign

Object.assign も浅いコピーを行うのに利用できます。

const original = { a: 1, b: { c: 2 } };
const shallowCopy = Object.assign({}, original);

// ネストされたオブジェクトを変更すると元のオブジェクトにも影響が出る
shallowCopy.b.c = 3;
console.log(original.b.c); // 3

Object.assign で生成したオブジェクトでも同様に、ネストされたプロパティが変更された場合にコピー元にも影響が及びます。

スプレッド構文と Object.assign の違い

スプレッド構文と Object.assign のどちらも浅いコピーを実現しますが、スプレッド構文はシンプルで読みやすいコードを書くために役立ちます。Object.assignは一部のプロパティだけを選択的にコピーする場合や、異なるオブジェクトからまとめてコピーしたい場合に便利です。

Deep Copy

JSON.stringifyJSON.parse を使う

オブジェクトを深くコピーするために、JSON.stringify でオブジェクトを文字列化し、その文字列を JSON.parse で再構築する方法があります。

const original = { a: 1, b: { c: 2 } };
const deepCopy = JSON.parse(JSON.stringify(original));

// コピー元とコピー先が独立している
deepCopy.b.c = 3;
console.log(original.b.c); // 2

ただし、この方法を利用すると関数や undefined、シンボルなど、JSONに変換できないプロパティは失われてしまうため注意が必要です。

再帰的なコピー

再帰的なコピーでは、オブジェクトの各プロパティを手動でコピーし、ネストされたオブジェクトが存在する場合にはその内部もコピーするようにします。以下は簡単な再帰的なコピーの実装例です。

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

    const copy = Array.isArray(obj) ? [] : {};
    for (const key in obj) {
        if (obj.hasOwnProperty(key)) {
            copy[key] = deepCopy(obj[key]);
        }
    }
    return copy;
}

const original = { a: 1, b: { c: 2, d: [3, 4] } };
const copy = deepCopy(original);

// コピー元とコピー先が独立している
copy.b.c = 3;
console.log(original.b.c); // 2

外部ライブラリを使う

深いコピーのニーズに応えるために、外部ライブラリを利用するのも手です。例えば、lodashcloneDeep は非常に使いやすい関数で、幅広いケースに対応しています。

const _ = require('lodash');
const original = { a: 1, b: { c: 2 } };
const deepCopy = _.cloneDeep(original);

// コピー元とコピー先が独立している
deepCopy.b.c = 3;
console.log(original.b.c); // 2

cloneDeepは多様なデータ型を扱う上で信頼性が高いです。

まとめ

  • 浅いコピー は、オブジェクトのプロパティをそのままコピーするため簡単で高速ですが、ネストされたオブジェクトの参照は共有されるため、コピー元とコピー先のオブジェクトが互いに影響を受けます。スプレッド構文や Object.assign を使うことで浅いコピーを簡単に実現できます。

  • 深いコピー は、オブジェクト全体を完全にコピーし、コピー元とコピー先が独立した状態を作ります。JSON.stringifyJSON.parse を組み合わせる方法や、再帰的なコピーの関数を使うことで深いコピーを作ることができますが、関数や undefined などのプロパティには対応していません。lodashcloneDeep などの外部ライブラリを使うと、多様なデータ型に対応した深いコピーを簡単に行えます。

それぞれのコピー方法には特性があるため、コピーするオブジェクトの内容や使いたい用途に合わせて適切な方法を選ぶことが重要です。