概要
JavaScriptで配列コピーをする際、
「=(イコール)」によるコピーでは意図した動作にならない場合があります。
意外にはまりやすい落とし穴だと思ったので、
初心者にもわかりやすいよう、図も載せておきました。
配列コピーによる動き
まずは、動きを確認しましょう。
変数aの配列を宣言し、変数bにコピーします。
vara=['あ','い','う','え','お'];varb=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は空の配列で宣言します。
vara=['あ','い','う','え','お'];varb=[];for(vari=0;i<a.length;i++){b[i]=a[i];}b[0]='か';console.log('変数a: '+a);// ['あ','い','う','え','お']console.log('変数b: '+b);// ['か','い','う','え','お']
Array.concat()によるコピー
Arrayオブジェクトのconcatメソッドは指定された配列を連結して値を返すメソッドですが
引数を指定しない場合は、元の配列のコピーを返します。
vara=['あ','い','う','え','お'];varb=a.concat();b[0]='か';console.log('変数a: '+a);// ['あ','い','う','え','お']console.log('変数b: '+b);// ['か','い','う','え','お']
Array.slice()によるコピー
Arrayオブジェクトのsliceオブジェクトは配列の一部を取り出して値を返すメソッドですが、
開始を指定し、終了を省略すると元の配列のコピーを返します。
vara=['あ','い','う','え','お'];varb=a.slice(0);b[0]='か';console.log('変数a: '+a);// ['あ','い','う','え','お']console.log('変数b: '+b);// ['か','い','う','え','お']
まとめ
「=(イコール)」を使って変数の値をコピーする際には
その変数がなんの値を保持しているのか注意しましょう。