知らないとはまってしまうJavaScriptの配列コピー


概要

JavaScriptで配列コピーをする際、
「=(イコール)」によるコピーでは意図した動作にならない場合があります。
意外にはまりやすい落とし穴だと思ったので、
初心者にもわかりやすいよう、図も載せておきました。

配列コピーによる動き

まずは、動きを確認しましょう。

変数aの配列を宣言し、変数bにコピーします。

test.js
var a = ['','','','',''];
var b = a;

その後、変数b[0]の内容を書き換えます。

test.js
b[0] = '';

この時、書き換えたいのは変数bの値ですが実際は

test.js
  console.log('変数a: ' + a); // ['か','い','う','え','お']
  console.log('変数b: ' + b); // ['か','い','う','え','お']

と、変数aの値も書き換わってしまいます。
書き換えたのは変数bだったのに、なぜ変数aも一緒に書き換わってしまったのでしょうか。

コピー元の値が書き換わる理由

では、なぜ変数bの値を書き換えたにも関わらず、変数aの値も書き換わってしまったのか
図を交えて説明していきます。

まず、この変数aに格納されている値は、配列の情報ではなく、
配列aのインスタンスを参照するアドレスが格納されています。

その為、変数bに変数aをコピーした際、
配列ではなくアドレスの情報が渡されているのです。

実際に図にすると、以下の状態です。

次に、変数b[0]に「か」という文字を代入しましたが、
この時、変数b[0]は変数bの値を書き換えるのではなく、
配列の値を保持したメモリを直接書き換えることになります。

つまり、このb=aという式では、配列は複製されず
同じアドレス先を共有しているだけなのです。

この動作を理解していないと、
まるで両方の変数で値が書き換わったように感じてしまうのです。

配列を複製する方法

では、実際に配列を複製するためにはどうすればいいか、いくつか例を挙げておきます。
※上記の説明でコピーという表現が適切ではないと分かったので、ここからは「複製」という表現に変えます

for文による複製

for文を使って、配列要素文のループにより値を代入します。
この時、変数bは空の配列で宣言します。

test.js
  var a = ['','','','',''];
  var b =[];

  for( var i =0; i < a.length; i++){
    b[i] = a[i];
  }

  b[0] = '';
  console.log('変数a: ' + a); // ['あ','い','う','え','お']
  console.log('変数b: ' + b); // ['か','い','う','え','お']

Array.concat()による複製

Arrayオブジェクトのconcatメソッドは指定された配列を連結して値を返すメソッドですが
引数を指定しない場合は、元の配列の複製を返します。

test.js
  var a = ['','','','',''];
  var b = a.concat();

  b[0] = '';
  console.log('変数a: ' + a); // ['あ','い','う','え','お']
  console.log('変数b: ' + b); // ['か','い','う','え','お']

Array.slice()による複製

Arrayオブジェクトのsliceオブジェクトは配列の一部を取り出して値を返すメソッドですが、
開始を指定し、終了を省略すると元の配列の複製を返します。

test.js
  var a = ['','','','',''];
  var b = a.slice(0);

  b[0] = '';
  console.log('変数a: ' + a); // ['あ','い','う','え','お']
  console.log('変数b: ' + b); // ['か','い','う','え','お']

...演算子による複製

ES6(ES2015)で追加されたスプレッド構文を使用すると、配列をそのまま複製してくれます。

ただし、ES6をサポートしたブラウザのみ対応です。
※ 2020年1月時点では、IE11は非サポートです。

test.js
  var a = ['','','','',''];
  var b = [...a]

  b[0] = '';
  console.log('変数a: ' + a); // ['あ','い','う','え','お']
  console.log('変数b: ' + b); // ['か','い','う','え','お']

まとめ

「=(イコール)」を使って変数の値をコピー(代入)する際には
その変数がなんの値を保持しているのか注意しましょう。