スポンサーリンク

JavaScriptの動かないコード (初級編) 関数に配列を渡すときのエラー


以下のJavaScriptコードが意図した動作をしないのは,なぜですか。(制限時間1分)


やりたい事:

  • ["こんにち","は"] という配列を作って,配列の要素を結合して表示。
  • ["こんにち","わ"] という配列を作って,配列の要素を結合して表示。
<input type="button" value="あいさつを表示" onClick="f()">

<script language="JavaScript">

function f()
{
	var arr1 = ["こんにち"];

	// 「こんにち」から,「こんにちは」を作る
	var arr2 = add_ha( arr1 );
	var str2 = arr2.join( "" );
	// こんにちは
	alert( str2 );


	// 「こんにち」から,「こんにちわ」を作る
	var arr3 = add_wa( arr1 );
	var str3 = arr3.join( "" );
	// こんにちわ
	alert( str3 );

	// alert( arr1.join( "" ) );
}

function add_ha( x )
{
	// 要素が増えた新しい配列 arr を作って返す
	var arr = x;
	arr.push( "は" );
	return arr;
}

function add_wa( x )
{
	// 要素が増えた新しい配列 arr を作って返す
	var arr = x;
	arr.push( "わ" );
	return arr;
}

</script>



答え



1つ目は「こんにちは」と表示されるが,2つ目は「こんにちはわ」と表示される。



上のコードでは,JavaScriptにおけるオブジェクトの扱い方が見逃されている。


オブジェクトは値渡しではなく,参照渡しである。

つまり,a がオブジェクトならば,

  • b = a; とした時に,b は a の新たなコピーではない。

a と b は同じ実体を指すようになる。見かけの名前が違うだけで,内容は常に同じ変数であるということ。
a に加えた変更は b にも影響し,逆もしかり。

  • f( a ) とした時に,関数 f() の内部に渡される引数は a の新たなコピーではない。

f() の内部で引数に加えた変更は,関数の呼び出し元の a にも影響する。

このように,オブジェクトを代入すると,ポインタとして同じ領域を指すようになるのである。新しい領域(新しいインスタンス)が生成されるのではない。


そして,配列はオブジェクトである。

var a = [ "a", "b" ];

という記法は,

var a = new Array( "a", "b" );

というコードに等しい。

だから,配列を別の変数に代入したり,関数の引数として渡したりする際には,オブジェクトとしての挙動が伴う。



これらを踏まえて冒頭のコードを見てみる。

関数 f() の中には

	var arr2 = add_ha( arr1 );

また,関数 add_ha() の中には

function add_ha( x )
{
	var arr = x;

という部分がある。


まず,オブジェクトは関数の引数になる際には参照渡しなので,
x は arr_1 と同じ実体を指す。


また,オブジェクトはイコールを使った代入文において参照渡しなので,
arr は x と同じ実体を指す。


関数から結果が戻る際の return arr; も参照渡しなので,
arr2 は arr と同じ実体を指す。


add_wa() の方でも同じ具合である。


するとなんと,

上のコード中で配列を表しているすべての変数( arr1, arr2, arr3, arr, x など )が,実は同じものを指していた

という事実が判明する。


add_ha() の中で x に加えられた変更はそのまま f() の中の arr1 にも影響し,arr1 の中身は [ "こんにち", "は" ] になる。

なので,arr1 を add_wa() に渡した結果は,[ "こんにち", "は", "わ" ] となる。
(上のコード中で配列を表す全ての変数の中身がこうなる。)



配列に限らず,オブジェクトならば全ての場合にこのような挙動が発生する。


対策としては,別個の実体として配列のコピーを作りたい時には,イコールによる代入文ではなく

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

のように,forループで走査しながら要素ごとにコピーすればよい。
どんなオブジェクトでもこれが基本になる。

Arrayの場合に限り

// 空配列の最後に a を連結
var b = [].concat( a );

のような方法もある。下記URL参照。

[JavaScript] オブジェクト、配列のコピー
http://d.hatena.ne.jp/reinyannyan/200...