配列のコピーを作ったのにコピーできてない!? – JavaScriptの参照渡しの罠

JavaScriptのオブジェクト型は参照渡しです!

var a, b;
a = 1;
b = a;
b = 5;
console.log(b); // 5
console.log(a); // 1 (aは変わらない)

var arrayA, arrayB;
arrayA = ['佐藤', '鈴木', '高橋'];
arrayB = array_a;
arrayB[0] = '田中';

console.log(arrayB); // ['田中', '鈴木', '高橋']
console.log(arrayA); // ['田中', '鈴木', '高橋'] (arrayAも変わる)

さて、これを見て「?」となった方へ。

JavaScriptにはプリミティブ型とオブジェクト型が存在します。

プリミティブ型とは以下のものを指します。

  • 数値型
  • 文字列型
  • ブーリアン型
  • null型
  • undefined型

対してオブジェクト型は上記以外のもの、配列やオブジェクト、関数なんかもオブジェクト型にあたります。

そんなプリミティブ型とオブジェクト型ですが、大きな違いは

プリミティブ型は値渡し、オブジェクト型は参照渡しであるということです。

値渡しと参照渡し

値渡しは言葉の通りその値を変数に直接代入しています。

対して参照渡しはメモリ上の保存している場所(アドレス)を代入しているだけなんですね。

上の例の arrayA と arrayB は同じ配列を参照しているわけなので、arrayB のプロパティを上書きすると arrayA の方も変わってしまうという仕組みです。

・・

・・・

と、ここまではJavaScriptを少し勉強した人であれば「そんなの知ってるよ!」といった内容だと思います。

ではこんな場合はどうでしょうか?

var arrayA = [
	['あ', 'い', 'う', 'え', 'お'],
	['か', 'き', 'く', 'け', 'こ'],
	['さ', 'し', 'す', 'せ', 'そ'],
	['た', 'ち', 'つ', 'て', 'と']
];
var arrayB = [];

// 配列をコピー
for(var i = 0, length = arrayA.length; i < length; i++) {
	arrayB.push(arrayA[i]);
}

// コピーの配列の値を変更
arrayB[0][0] = 'な';

console.log(arrayB); // ?
console.log(arrayA); // ?

最初に書いた通り、配列は参照渡しなので、普通に代入するとコピーになりません。
なので、for文で回してコピーを作ってあげます。

結果は以下のようになります。

arrayB
	['な', 'い', 'う', 'え', 'お'],
	['か', 'き', 'く', 'け', 'こ'],
	['さ', 'し', 'す', 'せ', 'そ'],
	['た', 'ち', 'つ', 'て', 'と']

arrayA
	['な', 'い', 'う', 'え', 'お'],
	['か', 'き', 'く', 'け', 'こ'],
	['さ', 'し', 'す', 'せ', 'そ'],
	['た', 'ち', 'つ', 'て', 'と']

arrayBはちゃんとコピーを作ったはずなのに、arrayAの値も変わっていますね〜

なぜでしょう??

オブジェクト型は参照渡し

答えは配列などの「オブジェクト型は参照渡し」だからです。

これだけでピンとくる方も多いのではないでしょうか?

何度も言いますが、配列の変数にはメモリ上の保存している場所(アドレス)を代入しているだけなんですね。

それは配列の中の配列も同じです。

つまり完全なコピーを作るには、再帰的にコピーを作る処理をしてあげなければなりません。

// 配列をコピー
for(var i = 0, length = arrayA.length; i < length; i++) {
	arrayB.push(arrayA[i]);
	for(var j = 0, lengthSub = arrayA[i].length; j++) {
		arrayB[i].push(arrayA[i][j]);
	}
}

// コピーの配列の値を変更
arrayB[0][0] = 'な';

console.log(arrayB); // ?
console.log(arrayA); // ?
arrayB
	['な', 'い', 'う', 'え', 'お'],
	['か', 'き', 'く', 'け', 'こ'],
	['さ', 'し', 'す', 'せ', 'そ'],
	['た', 'ち', 'つ', 'て', 'と']

arrayA
	['あ', 'い', 'う', 'え', 'お'],
	['か', 'き', 'く', 'け', 'こ'],
	['さ', 'し', 'す', 'せ', 'そ'],
	['た', 'ち', 'つ', 'て', 'と']

ちゃんと考えれば当たり前のことなのですが、意外と参照渡しを覚えた直後とかに躓くところではないでしょうか?

ちなみに配列のコピーはこんなやり方もあります。

// 配列の連結を使ったハック的なやり方
arrayB = arrayB.concat(arrayA);

// jQueryを使ったやり方
arrayB = $.extend(true, [], arrayA);

おわりっ。

スポンサーリンク