JavaScriptで配列をシャローコピー・ディープコピーする方法!

JavaScriptでは、配列をコピーする方法は大きく分けて「シャローコピー(浅いコピー)」と「ディープコピー(深いコピー)」の2種類があります。

この記事では『JavaScriptで配列をコピーする方法』について、以下の内容をサンプルコードを用いてわかりやすく解説します。

  • シャローコピーとディープコピーの違い
  • 配列をシャローコピーする方法とその問題点
    • スプレッド構文でシャローコピーする
    • Array.prototype.slice()でシャローコピーする
    • Array.prototype.concat()でシャローコピーする
    • Array.from()でシャローコピーする
  • 配列をディープコピーする方法
    • JSON.stringify()JSON.parse()でディープコピーする
    • lodashライブラリのcloneDeep()メソッドでディープコピーする
    • structuredClone()メソッドでディープコピーする

シャローコピーとディープコピーの違い

まず、シャローコピーディープコピーの違いについて説明します。

  1. シャローコピー
    • 名前通り浅い(shallow)コピーです。
    • 配列の1層目の要素だけをコピーします。ネストされた配列やオブジェクトは同じ参照を持つため、片方の配列を変更するともう一方にも影響が出る点に注意が必要です。
  2. ディープコピー
    • 名前通り深い(deep)コピーです。
    • ネストされた配列やオブジェクトを含め、すべてのデータを完全にコピーします。そのため、片方の配列を変更しても一方には影響が出ません。

配列をシャローコピーする方法とその問題点

配列をシャローコピーする方法について、以下の内容を順番に説明します。

  • スプレッド構文でシャローコピーする
  • Array.prototype.slice()でシャローコピーする
  • Array.prototype.concat()でシャローコピーする
  • Array.from()でシャローコピーする

スプレッド構文でシャローコピーする

JavaScriptでは、スプレッド構文(...)を使って簡単にシャローコピーができます。以下のサンプルコードを示します。

const arr = [1, [2, 3]];

// スプレッド構文でシャローコピー
const shallowCopy = [...arr];

// コピーした配列を変更
shallowCopy[0] = 99;
shallowCopy[1][0] = 99;

console.log(arr);
// [1, [99, 3]] ← ネストされた配列の要素に影響がある

console.log(shallowCopy);
// [99, [99, 3]]

シャローコピーは配列の1層目の要素だけをコピーするため、shallowCopy[0]の変更はarr[0]に影響しません。

しかし、shallowCopy[1]はネストされた配列であり、その参照がコピーされているだけなので、arr[1]shallowCopy[1]は同じ配列を指します。そのため、shallowCopy[1][0]を変更すると、arr[1][0]も変更されます。

Array.prototype.slice()でシャローコピーする

slice()メソッドを使ってもシャローコピーができます。サンプルコードを以下に示します。

const arr = [1, [2, 3]];

// Array.prototype.slice()でシャローコピー
const shallowCopy = arr.slice();

// コピーした配列を変更
shallowCopy[0] = 99;
shallowCopy[1][0] = 99;

console.log(arr);
// [1, [99, 3]] ← ネストされた配列の要素に影響がある

console.log(shallowCopy);
// [99, [99, 3]]

上記のサンプルコードでもシャローコピーを行っているため、shallowCopy[1][0]を変更すると、arr[1][0]も変更されます。

Array.prototype.concat()でシャローコピーする

concat()メソッドを使用してもシャローコピーが可能です。サンプルコードを以下に示します。

const arr = [1, [2, 3]];

// Array.prototype.concat()でシャローコピー
const shallowCopy = [].concat(arr);
// このコードでもOK
// const shallowCopy = arr.concat();

// コピーした配列を変更
shallowCopy[0] = 99;
shallowCopy[1][0] = 99;

console.log(arr);
// [1, [99, 3]] ← ネストされた配列の要素に影響がある

console.log(shallowCopy);
// [99, [99, 3]]

concat()もスプレッド構文やslice()と同様にシャローコピーを行っているため、shallowCopy[1][0]を変更すると、arr[1][0]も変更されます。

Array.from()でシャローコピーする

Array.from()メソッドは配列や配列風オブジェクトをコピーする際に使われます。このメソッドでもシャローコピーが可能です。サンプルコードを以下に示します。

const arr = [1, [2, 3]];

// Array.from()でシャローコピー
const shallowCopy = Array.from(arr);

// コピーした配列を変更
shallowCopy[0] = 99;
shallowCopy[1][0] = 99;

console.log(arr);
// [1, [99, 3]] ← ネストされた配列の要素に影響がある

console.log(shallowCopy);
// [99, [99, 3]]

Array.from()もシャローコピーのため、shallowCopy[1][0]を変更すると、arr[1][0]も変更されます。

配列をディープコピーする方法

配列をディープコピーする方法について、以下の内容を順番に説明します。

  • JSON.stringify()JSON.parse()でディープコピーする
  • lodashライブラリのcloneDeep()メソッドでディープコピーする
  • structuredClone()メソッドでディープコピーする

JSON.stringify()とJSON.parse()でディープコピーする

ディープコピーの簡単な方法として、JSON.stringify()で配列を文字列に変換し、それをJSON.parse()で再度配列に変換する方法があります。サンプルコードを以下に示します。

const arr = [1, [2, 3]];

// JSON.stringify()とJSON.parse()でディープコピー
const deepCopy = JSON.parse(JSON.stringify(arr));

// コピーした配列を変更
deepCopy[0] = 99;
deepCopy[1][0] = 99;

console.log(arr);
// [1, [2, 3]] ← 元の配列に影響がない

console.log(deepCopy);
// [99, [99, 3]]

ディープコピーはネストされた配列を含め、すべてのデータを完全にコピーするため、deepCopyの変更は元の配列に影響を与えません。ただし、この方法には以下の問題点があります

  • undefinednullに変換される
  • 日付(Dateオブジェクト)は文字列に変換される

実際にサンプルコードで確認してみましょう。

const arr = [1, [2, 3], undefined, new Date()];

// JSON.stringify()とJSON.parse()でディープコピー
const deepCopy = JSON.parse(JSON.stringify(arr));

// コピーした配列を変更
deepCopy[0] = 99;
deepCopy[1][0] = 99;

console.log(arr);
// [1, [2, 3], undefined, 2024-10-21T01:01:01.001Z] ← Dateオブジェクトはそのまま

console.log(deepCopy);
// [99, [99, 3], null, '2024-10-21T01:01:01.001Z'] ← undefinedはnullに変換され、Dateオブジェクトは文字列に変換される

lodashライブラリのcloneDeep()メソッドでディープコピーする

lodashライブラリのcloneDeep()メソッドを使うと、JSON.stringify()JSON.parse()で生じる問題を解決できます。

まずはlodashをインストールします。

npm install lodash

次に、以下のコードを使用してディープコピーを実行します。

const _ = require('lodash');
// ES Modules を使用している場合は以下のコード
// import _ from 'lodash';

const arr = [1, [2, 3], undefined, new Date()];

// lodashのcloneDeep()メソッドでディープコピー
const deepCopy = _.cloneDeep(arr);

// コピーした配列を変更
deepCopy[0] = 99;
deepCopy[1][0] = 99;

console.log(arr);
// [1, [2, 3], undefined, 2024-10-21T01:01:01.001Z] ← 元の配列に影響がない

console.log(deepCopy);
// [1, [99, 3], undefined, 2024-10-21T01:01:01.001Z]

structuredClone()メソッドを使う方法

structuredClone()メソッドを使用してディープコピーすることも可能です。サンプルコードを以下に示します。

const arr = [1, [2, 3], undefined, new Date()];

// structuredClone()メソッドでディープコピー
const deepCopy = structuredClone(arr);

// コピーした配列を変更
deepCopy[0] = 99;
deepCopy[1][0] = 99;

console.log(arr);
// [1, [2, 3], undefined, 2024-10-21T01:01:01.001Z] ← 元の配列に影響がない

console.log(deepCopy);
// [1, [99, 3], undefined, 2024-10-21T01:01:01.001Z]

ほとんどのデータ型を正確にコピーできますが、関数やSymbolなどはコピーできないので注意してください。

本記事のまとめ

この記事では『JavaScriptで配列をディープコピーをする方法』について、以下の内容を説明しました。

  • 配列のコピーにはシャローコピーとディープコピーがある
  • シャローコピーする方法
    • スプレッド構文を使う
    • Array.prototype.slice()を使う
    • Array.prototype.concat()を使う
    • Array.from()を使う
  • ディープコピーする方法
    • JSON.stringify()JSON.parse()を使う
      • undefinednullに変換される、日付(Dateオブジェクト)は文字列に変換される点に注意
    • lodashライブラリのcloneDeep()メソッドを使う
      • npmライブラリとしてlodashをインストールする必要がある
    • structuredClone()メソッドを使う
      • 関数やSymbolはコピーできない点に注意

上記に示すように、それぞれの方法には特徴があり、目的に応じて使い分けることで、効率的にコピーできます。

お読み頂きありがとうございました。