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...