概要
JavaScriptで配列コピーをする際、
「=(イコール)」によるコピーでは意図した動作にならない場合があります。
意外にはまりやすい落とし穴だと思ったので、
初心者にもわかりやすいよう、図も載せておきました。
配列コピーによる動き
まずは、動きを確認しましょう。
変数aの配列を宣言し、変数bにコピーします。
var a = ['あ','い','う','え','お'];
var b = a;
その後、変数b[0]の内容を書き換えます。
b[0] = 'か';
この時、書き換えたいのは変数bの値ですが実際は
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は空の配列で宣言します。
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メソッドは指定された配列を連結して値を返すメソッドですが
引数を指定しない場合は、元の配列の複製を返します。
var a = ['あ','い','う','え','お'];
var b = a.concat();
b[0] = 'か';
console.log('変数a: ' + a); // ['あ','い','う','え','お']
console.log('変数b: ' + b); // ['か','い','う','え','お']
Array.slice()による複製
Arrayオブジェクトのsliceオブジェクトは配列の一部を取り出して値を返すメソッドですが、
開始を指定し、終了を省略すると元の配列の複製を返します。
var a = ['あ','い','う','え','お'];
var b = a.slice(0);
b[0] = 'か';
console.log('変数a: ' + a); // ['あ','い','う','え','お']
console.log('変数b: ' + b); // ['か','い','う','え','お']
...演算子による複製
ES6(ES2015)で追加されたスプレッド構文を使用すると、配列をそのまま複製してくれます。
ただし、ES6をサポートしたブラウザのみ対応です。
※ 2020年1月時点では、IE11は非サポートです。
var a = ['あ','い','う','え','お'];
var b = [...a]
b[0] = 'か';
console.log('変数a: ' + a); // ['あ','い','う','え','お']
console.log('変数b: ' + b); // ['か','い','う','え','お']
まとめ
「=(イコール)」を使って変数の値をコピー(代入)する際には
その変数がなんの値を保持しているのか注意しましょう。