JavaScript 参照渡しと値渡しの罠 vol.1

JavaScriptには構文体系を深く記した本がないため、トライアル&エラーしながら真摯に勉強し、それを個人的備忘録を兼ねて公開しています。今回は「値渡しと参照渡しの罠」について。


概要:数値型, 文字列型, 論理型 は「値渡し」、配列, オブジェクトは「参照渡し」。果たしてこの定説は本当でしょうか。関数型まで検証することで明らかにしたいと思います。


では、本題。1 〜 3 は当たり前のことですが、4以降の説明で意味を持つので省略せずに書くことにします。

1. 数値型の検証

var num_parent = 3;
var num_child  = num_parent;
num_parent = 5;
alert(num_child);

出力結果:3



2. 文字列型の検証

var str_parent = "もともとの文字列";
var str_child  = str_parent;
str_parent = "新しい文字列";
alert(str_child);

出力結果:もともとの文字列



3. 論理型(ブーリアン型)の検証

var bool_parent = true;
var bool_child  = bool_parent;
bool_parent = false;
if(bool_child){
	alert('bool_child is TRUE');
}else{
	alert('bool_child is FALSE');
}

出力結果:bool_child is TRUE



以上、文字列型, 数値型, 論理型では、変化するのは変数xxx_parentだけで、変数xxx_childは何も変わりません。疑問の余地はとくにないでしょう。

問題はオブジェクトや配列です。JavaScriptでは配列とオブジェクトはほとんど同値なので、わかりやすさのため、配列を使って検証してみます。


4. 配列の検証

var arr_parent;
var arr_child;
arr_parent = new Array('a', 'b', 'c');
arr_child = arr_parent;
arr_parent[0] = 'd';
alert(arr_child[0]);

出力結果:d


目を疑うかもしれませんが、間違いなく d がアラートされます。配列(オブジェクト)では変数arr_parentの要素が変化することで、arr_childの値も変化します

これはバグではなく、「配列(オブジェクト)は参照渡しされる」からです。


5. 関数型の検証
まずはじめにお断り。
・私自身、JavaScriptの関数は理解不足な点が数多いので、おかしななところがあればコメント or トラックバック して頂けると幸いです。[※1]
JavaScriptの関数のシンタックスは特殊なので馴染みがないと奇妙に見えるかもしれませんが、本旨を外れるので説明を省きます。

[※1] 真夜中ナビ 深夜営業のお店探しお問い合わせからでも結構です(笑)


5.1 無名関数の場合

var func_parent = function(){ alert('もともとの関数'); }
var func_child  = func_parent;
func_parent = function(){ alert('新しい関数'); }
func_child();
func_parent();

出力結果:もともとの関数 → 新しい関数

func_parent が変化しても、func_child は変化していません。先に見てきた結果から類推すると、無名関数の場合には「値渡し」されていると言えます。
=>テスト方法が間違っていたようです。参照渡しされています。(2007-02-02)


5.2 普通の関数の場合

function func_parent(){ alert('もともとの関数'); }
var func_child  = func_parent;
function func_parent(){ alert('新しい関数'); }
func_child();
func_parent();

出力結果:新しい関数 → 新しい関数

func_parent が変化すると、func_child も変化します。先に見てきた結果から類推すると、普通の関数の場合には「参照渡し」されていると言えます。


5.3 関数オブジェクトの場合

var func_parent = new Function("alert('もともとの関数')");
var func_child  = func_parent;
func_parent = new Function("alert('新しい関数')");
func_child();
func_parent();

出力結果:もともとの関数 → 新しい関数

func_parent が変化しても、func_child は変化していません。先に見てきた結果から類推すると、関数オブジェクトの場合には「値渡し」されていると言えます。new されており、紛れもなくオブジェクトのハズですが、参照渡しではないようです(謎)。
=>テスト方法が間違っていたようです。参照渡しされています。(2007-02-02)


/****** add 2007-02-02 Start

new されているときに、上記のテストでは一見すると、値渡しに見えますが、#ひかるさんのコメントの通りで確かに参照渡しされています。ひかるさんありがとうございます。


ということで、、

オブジェクトは参照渡しです。


ちなみに「マーボー豆腐は飲み物です」。

今の今まで食べ物と思っていました。若槻さんにも感謝!


【検証】
以下のように配列オブジェクトで確かめると、#ひかる さんのご指摘の通り、参照渡しされていることを確認できます。勉強になりました。

var arr_parent;
var arr_child;
var arr_parent = new Array('a', 'b');
arr_child = arr_parent;
arr_parent = new Array('c', 'd');
alert('配列arr_parentの0番目の要素は' + arr_parent[0] + ' 配列arr_childの0番目の要素は' + arr_child[0]);

add 2007-02-02 End ******/



以上が値渡し・参照渡しに関する検証です。この検証方法はごく簡単なもののためこれを持って、結論とするわけにはいきません。しかし、関数オブジェクトに限っては、(オブジェクトであるにもかかわらず)値渡しされること[※1][※2]には注意が必要なのかもしれません。


私のレベルでは結論が出せないので、JavaScriptの内部構造まで熟知されているような達人がいらっしゃれば、是非、ご教授いただきたいです。(高橋さん、IT戦記さんのような神レベルの方に伺いたい!)


[※1] 通常のオブジェクトは間違いなく、参照渡しされます。
[※2] 関数オブジェクトは必ず値渡しされるのか、ということは検証していません。



次回はオブジェクトが参照渡しされることによる典型的な設計ミスの例を書いてみたいと思います。