オブジェクトのクローン

Mattia Rollo](https://dev.to/mattiarollo)

Mattia Rollo

2022年10月29日に投稿、2022年10月31日に更新

オブジェクトをクローンするのは、プリミティブな値を別の変数からクローンするのとはちょっと違うんだ。例えば (1, 'hello world!', true, null) みたいなね。

例えばこんな感じ:

const name = 'Pippo';
const newName = name;
// これで newName は 'Pippo' という文字列を持つことになるけど
// 元の変数とは独立してるんだ

フルスクリーンモードにする

だけど、この方法をオブジェクトに適用しようとしてもうまくいかないんだ。オブジェクトを含む配列もそういう挙動になるけど、変数に束縛されるのは 参照による もので、オブジェクトそのものではないからね。

例を見てみよう:

// オブジェクトを初期化する変数を作って、配列と同様に
// それはオブジェクトそのものではなくオブジェクトへの参照なんだ
let studente = {
    nome: 'Mattia',
    cognome: 'Rollo',
    abilità: {
        suonare: 5,
        programmare: 5,
    }
};

// オブジェクトをクローンするためには、単純にこんな風に
const cloneStudente = studente;

// ってやるだけじゃダメだよ。なぜなら studente オブジェクトを変更したら
// cloneStudente にも変更が見られるからね
// 例を見てみよう
studente.nome = 'Pippo';


console.log(studente)
console.log(cloneStudente);

フルスクリーンモードにする

出力:

console.logのコード

では、どうやったらオブジェクトをクローンできるの?

オブジェクトをクローンして元のコピーから独立したコピーを作るにはいくつかの方法があるよ:

  • for in ループ を使ってオブジェクトの各キーを一つ一つコピーする
  • Object.assign(clone, コピーするオブジェクト) を使う
  • スプレッド構文(...) を使う

⚠️注意⚠️
これらの方法では、オブジェクトがプロパティだけを含む場合に効率的にクローンすることができる。別のオブジェクトを持っている場合は別の手順、つまりメソッドを使わなくちゃいけないんだ。

ただのプロパティを持つオブジェクトで for in ループを使ってクローンする例を見てみよう:

// ここでは、中にオブジェクトを持たないただのプロパティを
// 持つオブジェクトを作るよ
let studente = {
    nome: 'Mattia',
    cognome: 'Rollo'
};
const cloneStudente = {}

// for ループを使ってオブジェクトの各キーをコピーする
// これで元のオブジェクトから独立したコピーを持つことができる
for(key in studente) {
    cloneStudente[key] = studente[key];
}

// 実際、studente オブジェクトを変更しても
// cloneStudente には影響しない
studente.nome = 'Pippo';


console.log(studente)
console.log(cloneStudente);

フルスクリーンモードにする

出力:

javascriptのコード

ちょっと面倒だね。では、Object.assign() を使った場合はどうなるか見てみよう。出力は先ほどの for in ループと同じになるよ:

// オブジェクトを初期化する変数を作り、これまた配列と同様に
// それはオブジェクトそのものではなく、オブジェクトへの参照なんだ
let studente = {
    nome: 'Mattia',
    cognome: 'Rollo'
    // abilità: {
    //     suonare: 5,
    //     programmare: 5,
    // }
};
const cloneStudente = {}

// Object.assign() メソッドを例にするよ
// 括弧の最初の値はクローンオブジェクト(目的のオブジェクト)
// 二つ目の値はコピーするオブジェクト
Object.assign(cloneStudente, studente)

// 実際、studente オブジェクトを変更しても、cloneStudente には
// 影響しない。前の例と同じだね
studente.nome = 'Pippo';


console.log(studente)
console.log(cloneStudente);

フルスクリーンモードにする

あるいは、もっと良い方法はスプレッド構文を直接変数に使うことだよ:

const cloneStudente = {...studente}

フルスクリーンモードにする

上記の方法は、クローンするオブジェクトがただのプロパティだけを含む場合はうまくいくんだ。だけど、いつもそうとは限らないよね。
しばしば、内部に別のオブジェクトを持つオブジェクトをクローンしたいときがある。さっきの例に一つ追加してみよう:

// studenteオブジェクトに内部にオブジェクトを追加したよ
let studente = {
    nome: 'Mattia',
    cognome: 'Rollo',
    abilita: {
        suonare: 10,
        programmare: 10,
    }
};
// スプレッド構文でクローンする
const cloneStudente = {...studente}

// studenteオブジェクトの中の'abilita'オブジェクトの
// 最初の値を変更する
studente.abilita.suonare = 15;

// 両方のオブジェクトをconsole.logで確認してみよう
console.log(studente)
console.log(cloneStudente);

フルスクリーンモードにする

出力:

console.logの出力画像

見てわかる通り、'suonare'というプロパティが元のオブジェクトでもクローンでも変わってしまう。実はこの場合も、残念ながらクローンは 参照によって 行われていて、両方が同じオブジェクトを持っているんだ。片方を変更すると、もう片方も変わってしまう。

では、どうやって解決するのか?

幸い、私たちより先に考えた人がいて、そのためのライブラリlodashを作ったんだ。

実際にここで _.cloneDeep() メソッドを見つけることができるけど、これを使うと元のコピーから独立したクローンを作ることができるんだ。

ライブラリをインポートするには、jsdelivr からダウンロードできる head タグに追加する cdn をコピーすればいい:

<script src="https://cdn.jsdelivr.net/npm/lodash@4.17.21/lodash.min.js"></script>

フルスクリーンモードにする

こんな感じでね:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <!--lodashライブラリを使うためにコピーするcdn-->
    <script src="https://cdn.jsdelivr.net/npm/lodash@4.17.21/lodash.min.js"></script>

    <link rel="stylesheet" href="css/style.css">
    <title>Document</title>
</head>
<body>

    <script src="js/script.js"></script>
</body>
</html>

フルスクリーンモードにする

早速例を見てみよう:

// studenteオブジェクトの中にオブジェクトを追加したよ
let studente = {
    nome: 'Mattia',
    cognome: 'Rollo',
    abilita: {
        suonare: 10,
        programmare: 10,
    }
};
// lodashライブラリが提供する_.cloneDeep()メソッドでクローンする
const cloneStudente = _.cloneDeep(studente);

// studenteオブジェクトの中の'abilita'オブジェクトの
// 最初の値を変更する
studente.abilita.suonare = 15;

// 両方のオブジェクトをconsole.logで確認してみよう
console.log(studente)
console.log(cloneStudente);

フルスクリーンモードにする

出力:

console.logの出力画像

見てわかる通り、studenteの'abilita'オブジェクトの値を変更しても、cloneStudente内のそれは変わらない。これは完全に独立したコピーが作られたからだよ。

こちらの記事はdev.toの良い記事を日本人向けに翻訳しています。
https://dev.to/mattiarollo/clone-objects-2l5a