tl;dr
- JavaScriptに参照渡し/値渡しなど存在しない
- 存在するのは変数に入る値の参照のみ
- 変数に値を代入すると参照が切り替わる
という様に考えれば不毛な議論を避けられるのではないかという妄想そしてタイトルは明らかな誇張表現
はじめに
よくJavaScript界隈で見られる変数に関する話題として、「値渡し/参照渡し」が上がりますが、そもそもJavaScriptにはC++のような参照渡しなど存在しないです。それなのにわざわざ値渡し、参照渡しと分類することで、勝手が違うC++の参照渡しと混同しかねないです。実際にそのような話題が人目に付く程度盛り上がったときには、大抵**「それはC++の参照渡しと違う」などと指摘が入り**、JavaScriptでの参照渡しと呼ばれていたものが**「参照渡しのようなもの」で片付けられる**のを何度も見たことがあります。
ここまでの自分はJavaScriptにはJavaScriptの参照渡しがあるのだから、わざわざC++の参照渡しを意識する必要なんてないのではないか?なんて思っていましたが、今回トレンド入りした記事を読んで改めて整理してみたら、そもそもJavaScriptに値渡し/参照渡しなんて概念ないのでは?と思うようになり、今この記事を書いています。
どういうことか
これから説明で"値の参照"という言葉を使いますが、これは例えるならたくさんある引き出しのどこに値をしまったかを表すメモのようなものです。そして"参照"という単語が出てきますが"参照渡し"と結びつけてしまうと理解しづらくなるので、いったん"参照渡し"のことは完全に忘れて読むと理解しやすいかと思います。
それから便宜上、"値の参照"にS1,S2などとラベルをつけますが、この"値の参照"に関する処理は僕たちには見えない深淵でひっそりと行われるので、普通にJavaScriptを書いていて"値の参照"を直接見る機会はないです。
変数に値を代入すると"値の参照"が変数に入る
var a = 10
というコードを実行すると、まず10という値が生成されます。値ができたのと同時に、その値をどこにしまったのかというメモである、"値の参照"ができ、その関係を図にするとこんな感じになります。
値の参照 | 値 |
---|---|
S1 | 10 |
そして変数にS1という"値の参照"が入ります。
console.log(a)
を実行すると、変数に入っている"値の参照"が取得され、そのS1から実際の値である10が取得されます。
変数に値を再代入すると"値の参照"が置き換わる
var a = 10
a = 100
console.log(a) // 100
というコードを実行すると100になるのは誰でもわかると思いますが、上の説明を踏まえると表はこんな感じになります。(実際に生成されるタイミングは違いますがみやすさのためにまとめています)
値の参照 | 値 |
---|---|
S1 | 10 |
S2 | 100 |
変数aにS1が入り、その後S2に置き換り、変数a→S2→100と取得され100が出力されます。
変数に変数を代入するとその変数に入ってる"値の参照"が入る
var a = 10
var b = a
console.log(b) // 10
"変数"が3回も出てきてわけがわからないので、このコードに合わせて書き直すと、「変数bに変数aを代入すると変数aに入ってる"値の参照"が入る」ということですね。
このコードも10が出力されるのは誰でもわかると思いますが、例によって小難しく説明すると、まず値が生成され、変数aにその値の参照が入ります。
値の参照 | 値 |
---|---|
S1 | 10 |
そして変数bに変数aを代入すると、変数aに入っていたS1が変数bにも入る。無駄に図にするとこんな感じになります。
変数 | 中身 |
---|---|
a | S1 |
b | S1 |
これで変数b→S1→10と出力されるのがわかると思います。
応用
ここまで説明した3つのパターンで、JavaScriptで参照渡しだとか値渡しだとか言われてきたものがすべて説明できるはずです。それと"値の参照"はいい加減鬱陶しく感じる人も現れていると思うので、ここから"値の参照"ではなく"参照"と書きます。まだ参照渡しと区別できない人は適宜読み替えると理解しやすいかもしれません。
「プリミティブ値は値渡し」と言われてきたもの
変数bに変数aを代入した後に変数aを上書きしたのに変数bは10のままだから、それは値渡しだ!と言われてきたものです。
var a = 10
var b = a
a = 100
console.log(b) // 10
ただこれは上で説明した「変数に値を再代入すると"値の参照"が置き換わる」と「変数に変数を代入するとその変数に入ってる"値の参照"が入る」から考えれば、変数aに100を代入した時点で変数aが持っている参照が新たに作られた100に変わるだけで、もともと変数aが持ち、変数bに代入された10の参照が変わるわけではないです。
「オブジェクトは参照渡し」と言われてきたもの
上のとは対象的に、別の変数に代入した後に元の変数aに代入しても置き換わるので、参照渡しだ!と言われてきたものです。
var a = { val: 10 }
var b = a
a.val = 100
console.log(b) // { val: 100 }
ただ上のコードとは決定的に違うことがもう1つあって、それは代入している対象は変数自体ではなく、その変数が持っている(参照の先にある)オブジェクトのプロパティです。つまり、「変数に値を再代入すると"値の参照"が置き換わる」が当てはまらず、2つの変数が同じオブジェクトを指しているままになり、ただ同じオブジェクトを操作してるだけになります。(プリミティブ値はプロパティ代入できないので逆のことはできないのですが)
それから変数自体に値を代入すれば「変数に値を再代入すると"値の参照"が置き換わる」が当てはまるので、もちろん値は変わりません。
var a = { val: 10 }
var b = a
a = { val: 100 }
console.log(b) // { val: 10 }
おわりに
ここで重大な発表があるのですが、実際のJavaScriptの仕様、実装まで確認していないので、ここで説明したことがすべて全く同じように動いてる保証はないです。
しかし応用で書いたように、実際の値、その値の参照、引数と分けて考えることによって、説明が付く部分が多かったので紹介しました。タイトルの誇張表現からここまで付き合ってくれた方はありがとうございました。
もし誤りがあれば指摘いただけるとありがたいです。
最後にここまで読んでくれた人ならすぐに分かるであろう問題を乗せておきます。暇な人は是非解いてみてください。
var obj = { arr: ['hoge'] }
console.log(obj) // { arr: [ 'hoge' ] }
var arr = obj.arr
obj.arr = []
console.log(obj) // ???
console.log(arr) // ???