javascript 配列をディープコピーする

javascript 配列をディープコピーする

javascriptで、配列をディープコピーするサンプルコードを記述してます。具体的に配列をディープコピーする方法はページの真ん中の方にあります。「JSON.stringify()」と「JSON.parse()」を使用してます。

  • OS windows11 pro 64bit
  • ブラウザ chrome 107.0.5304.63

配列をコピー

配列の場合は、ただ変数を代入すると元の値が変更されると、メモリ上の同じデータを参照しているので、代入された配列の値も変更されます。

const arr = ['a', 'b'];

const new_arr = arr;

arr.push('c'); // 値を追加

console.log(arr); // ['a', 'b', 'c']
console.log(new_arr); // ['a', 'b', 'c']

元の値に影響しないようにするには、スプレッド構文を使用します。

const arr = ['a', 'b'];

const new_arr = [...arr];

arr.push('c'); // 値を追加

console.log(arr); // ['a', 'b', 'c']
console.log(new_arr); // ['a', 'b']

また、スプレッド構文以外には、以下の方法があります。

const arr = ['a', 'b'];

const new_arr1 = Array.from(arr); // 配列化
const new_arr2 = [].concat(arr); // 空の配列と結合
const new_arr3 = arr.slice(); // 範囲を指定しないでスライス

arr.push('c'); // 値を追加

console.log(arr); // ['a', 'b', 'c']
console.log(new_arr1); // ['a', 'b']
console.log(new_arr2); // ['a', 'b']
console.log(new_arr3); // ['a', 'b']

パフォーマンスは「concat」か「slice」がいいです。

実行回数:100000回 関数名:スプレッド構文  実行時間:371(ms)
実行回数:100000回 関数名:Array.from 実行時間:228(ms)
実行回数:100000回 関数名:concat   実行時間:6.90(ms)
実行回数:100000回 関数名:slice    実行時間:9.30(ms)

ただし、配列の中に配列やオブジェクトがあり階層が2階層になっている場合は「ディープコピー」する必要があります。

const arr = [['aaa'],{'key':'aaa'}];

const new_arr1 = Array.from(arr); // 配列化
const new_arr2 = [].concat(arr); // 空の配列と結合
const new_arr3 = arr.slice(); // 範囲を指定しないでスライス
const new_arr4 = [...arr]; // スプレッド構文

arr[0].push("bbb");
arr[1].key = "bbb"

console.log(arr); // [['aaa', 'bbb'],{key: 'bbb'}]
console.log(new_arr1); // [['aaa', 'bbb'],{key: 'bbb'}]
console.log(new_arr2); // [['aaa', 'bbb'],{key: 'bbb'}]
console.log(new_arr3); // [['aaa', 'bbb'],{key: 'bbb'}]
console.log(new_arr4); // [['aaa', 'bbb'],{key: 'bbb'}]

配列をディープコピー

ディープコピーする場合は「JSON.stringify()」で文字列化した後に、「JSON.parse()」でまたオブジェクト化します。

const arr = [['aaa'],{'key':'aaa'}];

const new_arr = JSON.parse( JSON.stringify(arr) );

arr[0].push("bbb");
arr[1].key = "bbb"

console.log(arr); // [['aaa', 'bbb'],{key: 'bbb'}]
console.log(new_arr); // [['aaa'],{'key':'aaa'}];

ただし、上記の方法は「new Date()」や「undefined」や「関数」に使用するとおかしな挙動になります。

const arr = [[new Date()],{'key':undefined}];

const new_arr = JSON.parse( JSON.stringify(arr) );

arr[0].push("bbb");
arr[1].key = "bbb"

console.log(arr);
console.log(new_arr);

実行結果

「map」を使用して条件ごとに処理を適応すると、期待する動作になります。

const arr = [[new Date()],{'key1':undefined},{'key2':function(){}}];

const new_arr = arr.map( v => {
 if( Array.isArray(v) ){
   return [ ...v ]
 }else if( typeof(v) == "object" ){
   return { ...v }
 }else return v
} );

arr[0].push("bbb");
arr[1].key1 = "bbb"
arr[2].key2 = "bbb"

console.log(arr);
console.log(new_arr);

実行結果

外部ライブラリ「lodash」を使用することも可能です。

サンプルコード

以下は、
「ディープコピー」ボタンをクリックすると、ランダムに生成した配列をディープコピーして、コピー元を再度ランダムに生成した配列に変更して双方の値を表示する
サンプルコードとなります。

※cssには「tailwind」を使用してます。関数はアロー関数を使用してます。

<!DOCTYPE html>
<html lang="ja">

<head>
  <meta charset="utf-8">
  <title>mebeeサンプル</title>
  <!-- MDB -->
  <link href="https://cdnjs.cloudflare.com/ajax/libs/mdb-ui-kit/4.2.0/mdb.min.css" rel="stylesheet" />
</head>

<style>
  .main {
    margin: 0 auto;
    margin-top: 200px;
    display: flex;
    flex-direction: column;
    align-items: center;
    font-size: 30px;
  }
</style>
<script>

  const hoge = () => {

    // ランダムな3つの値がある配列を生成
    let arr1 = radarr(3);

    // 生成した配列をdedpコピー
    let arr2 = arr1.map(v => {
      if (Array.isArray(v)) {
        return [...v]
      } else if (typeof (v) == "object") {
        return { ...v }
      } else return v
    });

    // もう一度、ランダムな3つの値がある配列を生成
    arr1 = radarr(3);

    // arr1表示
    disp(arr1, "txt1");

    // arr2表示
    disp(arr2, "txt2");

  }

  const radarr = (len) => {

    //ランダムな9までの配列を生成
    let arr = [];
    let num = 10;
    let length = len;
    for (let i = 0; i < length; i++) {
      arr.push(Math.floor(Math.random() * num));
    }

    return arr;

  }

  //フロントに表示する関数
  const disp = (arr, id) => {

    let text = [];
    // for ofを使用
    for (let item of arr) {
      text.push('<li class="list-group-item">' + item + '</li>');
    }
    //innerHTMLを使用して表示    
    document.getElementById(id).innerHTML = text.join('');

  }

  window.onload = () => {
    // ボタンを取得
    let elmbtn = document.getElementById('btn');
    // クリックイベントを登録
    elmbtn.onclick = () => {
      hoge();
    };
  }

</script>

<body>
  <div class="main">

    <h2 class="badge badge-success">配列を生成</h2>
    <ul id="txt1" class="list-group"></ul>
    <h2 class="badge badge-success">配列をディープコピー</h2>
    <ul id="txt2" class="list-group"></ul>

    <button id="btn" type="button" class="btn btn-raised btn-danger">
      ディープコピー
    </button>

  </div>
</body>

</html>

ディープコピーなので元の値のみが変更されていることが確認できます。