「JavaScriptの配列は『参照渡し(call-by-reference)』」というネット上に大量に存在する間違った記述を訂正するエントリ。
結論から先に言うと
JavaScriptにおいて、関数の引数として配列を与えた場合、『参照の値渡し』になります。『参照の値渡し』は、『参照渡し(call-by-reference)』ではなく『値渡し(call-by-value)』に分類されます。
参考エントリ
以下の解説が非常にわかりやすいです。
G-chan Square - [javascript] javascriptの関数で引数に配列を渡すと、それは本当に参照渡しか?
G-chan Square - じゃ、「参照渡し」ってなんだ?
簡単に端折ると、関数の引数として変数を与える場合、
- 値の値渡し(プリミティブ型変数の値をそのまま渡す)
- 値の参照渡し(プリミティブ型変数の参照を渡す)
- 参照の値渡し(参照型変数のもつ値を値として渡す)
- 参照の参照渡し(参照型変数自体の参照を渡す)
の4パターンを考えることができて、プログラム言語用語における『参照渡し(call-by-reference)』はパターン2とパターン4を指すのだけれど、パターン3を『参照渡し(call-by-reference)』と呼んでしまっている人が存在する、というお話。JavaScriptの配列に関しては、このパターン3に該当します。
誤解の原因
『参照渡し(call-by-reference)』という用語の誤解
参照渡し/値渡しという区別は、
foo(a);
foo(a[i]);
において、「a」「a[i]」が左辺値として扱われるか右辺値として扱われるか
のことです。たとえ、変数「a」や配列の要素「a[i]」が保持する値が「オブジェクトへの
G-chan Square - じゃ、「参照渡し」ってなんだ?
参照」であろうが、
foo(a);
foo(a[i]);
のことを、「参照渡し(call-by-reference)」とは呼びません。
というわけで、「JavaScriptにおいて配列は参照型である」ということを根拠に「配列は『参照渡し(call-by-reference)』である」とするのは間違いです。
不十分なテスト
ネット上でよく見かけたのは、「引数として与えた配列の要素を関数内で変更することができたから、これは『参照渡し(call-by-reference)』である」という主張です。
function f(x) {
x[0] = 1;
}
var a = new Array(0, 0, 0);
f(a);
console.log(a);
を実行したら
[1, 0, 0]
と出力されたから、ちゃんと配列の要素が変更されているじゃないか、『参照渡し(call-by-reference)』だ、というわけです。
しかし、このテストによってわかるのは、「配列データ(例では[0, 0, 0])のコピーが関数に与えられているわけではない」ということ、つまりパターン1ではないということだけです。「JavaScriptにおいて配列は参照型である」ということがわかっているなら、既にパターン1でないことは明らかなので、テストとして意味がありません。依然としてパターン3かパターン4かの判別ができていないので、この段階で『参照渡し(call-by-reference)』と決定づけることはできません。
パターン3かパターン4かを判別するためには、引数として与えられた配列(の参照型変数)の参照先を、関数内で変更した際の挙動を調べる必要があります。例えば以下のコードでテストできます。
function f(x) {
x = new Array(1, 0, 0);
}
var a = new Array(0, 0, 0);
f(a);
console.log(a);
これを実行すると
[0, 0, 0]
と出力されるので、パターン3の『参照の値渡し』であることがわかります。
最後に
参考エントリでも言われていることですが、新しい言語を学習する際などは、『参照渡し(call-by-reference)』という記述を見ても鵜呑みにせず、『参照の値渡し』である可能性を疑ってテストコードで確認するのが無難かと思います。
また、自分で『参照渡し(call-by-reference)』に関して記述する場合は、「参照型変数を渡す」という意味で解釈されないように『call-by-reference』を併記したり、より詳細に『参照の参照渡し』などと表記するのが良いかと思ったのですが、面倒ですねこれ。
最後の最後に、『call-by-reference』を『参照渡し』と訳した人を全力で呪いましょう。