30
21

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

はじめての記事投稿

【JavaScript】配列・オブジェクトをコピーする時に、イコール(=)で代入しちゃダメなの、なぁぜなぁぜ?

Last updated at Posted at 2023-06-25

はじめに

  • この記事ではJavaScriptの配列・オブジェクトを正しくコピー(複製)する方法を紹介します
  • 初歩的な内容ですが、筆者が初見殺しに会った為、備忘録として共有します

この記事の対象者

  • 現在JavaScriptを学ばれている方
  • これからJavaScriptを学ばれる方

NGパターン

以下は配列「arr1」のコピー(複製)を作るソースコードです。

const arr1 = [10, 20];
const arr2 = arr1; // 代入によるコピー

console.log(arr1);
console.log(arr2);

以下、実行結果です。

> [10, 20]
> [10, 20]

コピーは出来てそうですね。

ただ、これは危険な書き方です。
何が危険なのでしょうか?それは...

配列のイコール(=)による代入は、コピー元を参照してしまう

上述NGパターンについて、arr2[0]の値を変えてみます。

const arr1 = [10, 20];
const arr2 = arr1;

arr2[0] = 30;

console.log(arr1);
console.log(arr2);

以下、実行結果です。

> [30, 20]
> [30, 20]

コピー先(arr2)の値を変えるとコピー元(arr1)の値も変わってしまう、
つまりコピー元を参照していますね!!

これは予期せぬ挙動と成り得るので修正しましょう。

じゃあどうすれば?

いくつか方法はありますが、この記事ではその中で最も直感的に分かり易かった
スプレッド構文(後ほど補足します)を使用します。

まずは修正後のソースコードを見てみましょう。

const arr1 = [10, 20];
const arr2 = [...arr1];  // スプレッド構文

arr2[0] = 30;

console.log(arr1);
console.log(arr2);

以下、実行結果です。

> [10, 20]
> [30, 20]

コピー先(arr2)のみが変更されました。めでたしめでたし...

...と思うじゃん? ※2023/06/26追記

有識者の方々にご指摘頂きましたので追記です。
実は、スプレッド構文は1次元までのコピー(シャローコピー)でした。

その為、それより深い階層では以下のようにコピー元を参照してしまいます。

const arr1 = [10, [20, 30]];
const arr2 = [...arr1];

arr2[1][0] = 100;

console.log(arr1); // [ 10, [ 100, 30 ] ]
console.log(arr2); // [ 10, [ 100, 30 ] ]

これを回避する為に、全ての階層のコピー(ディープコピー)を行う必要があります。

それを実現するのが...

structuredClone() ※2023/06/26追記

以下、structuredClone()を使用したソースコードです。
シンプル且つ直感的で分かりやすいですね!!

const arr1 = [10, [20, 30]];
const arr2 = structuredClone(arr1);

arr2[1][0] = 100;

console.log(arr1); // [ 10, [ 20, 30 ] ]
console.log(arr2); // [ 10, [ 100, 30 ] ]

コピー先(arr2)の2階層目のみが変更されました。めでたしめでたし。

補足

スプレッド構文とは?

  • スプレッド構文は本来、配列・オブジェクトの前に「...」を記述することで、展開した要素内の値を出力するものです
  • ES2015(ES6) で追加された機能で、現在では全てのブラウザで対応しています
const arr1 = [10, 20];

console.log(arr1);     // [10, 20]
console.log(...arr1);  // 10 20

今回紹介したスプレッド構文を使って配列をコピーする方法は、展開した値から新しい配列が生成されたということですね。

オブジェクトも代入を行うと、コピー元を参照してしまう

配列同様にスプレッド構文を使用することで、1階層であればコピー元が参照されることを回避できます。
※ディープコピーを行う場合はstructuredClone()を使いましょう

  • 代入によるコピー(オブジェクト)
const obj1 = {val1: 10, val2: 20};
const obj2 = obj1; // 代入によるコピー

obj2.val1 = 30;

console.log(obj1);  // {val1: 30, val2: 20}
console.log(obj2);  // {val1: 30, val2: 20}
  • スプレッド構文を使用したコピー(オブジェクト)
const obj1 = {val1: 10, val2: 20};
const obj2 = {...obj1};

obj2.val1 = 30;

console.log(obj1);  // {val1: 10, val2: 20}
console.log(obj2);  // {val1: 30, val2: 20}

まとめ

  • 配列・オブジェクトはイコール(=)で代入すると、コピー元を参照してしまう
  • 代入ではなく、スプレッド構文 structuredClone()を使おう!
30
21
5

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
30
21

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?