JavaScriptはオブジェクトについて参照渡しだなんて、信じない

  • 156
    いいね
  • 12
    コメント
この記事は最終更新日から1年以上が経過しています。

おはようございますの人もいれば、こんにちはの人もいて、こんばんはの人もいれば、スラマッパギの人もいますね。ということで本日はどうも、まとめてスラマッパギ。
えーと、今日はちょっとした発表があります。といっても本当にちょっとしたものなので、ちょっとだけ耳を傾けてお聞きください。ええ、JavaScriptの代入についてです。そう、値渡しだの参照渡しだの猫ダマシだの「村長だ、ワシ」だの、そんな話です。
ということで、ちょっとこれから始めようかなと、ちょっと思います。

さっそくだが、見よ!この値渡しを!

array_sample1
var a, b;
a = ["hoge", "fuga"];
b = a; // 値渡し
b; // => ["hoge", "fuga"]
b[0] = "hogera";
b; // => ["hogera", "fuga"]
a; // => ["hogera", "fuga"] 値渡し!
object_sample1
var a, b;
a = {hoge: "hogehoge", fuga: "fugafuga"};
b = a; // 値渡し
b; // => {hoge: "hogehoge", fuga: "fugafuga"}
b.hogera = "hogerahogera";
b; // => {hoge: "hogehoge", fuga: "fugafuga", hogera: "hogerahogera"}
a; // => {hoge: "hogehoge", fuga: "fugafuga", hogera: "hogerahogera"} 値渡し!

どうですか、見事な値渡しでしょう。
お、すばらしいですか。ありがとうございます。
最近練習した甲斐がありました。

「おいおい、それどう見ても参照渡しだろう。」

あ!あなたは!またあなたですか!
いつもしつこい人だ…そして今日も出会ってしまうとは…運の悪い…!
いや?むしろ今日は少し都合がいい。どうせならちょっとこのコードについて、説明してもらいましょうか!

array_sample2
var a, b;
a = ["hoge", "fuga"];
b = a; // 参照渡し?
b = [];
b; // => []
a; // => ["hoge", "fuga"] ←!?!!?!???!?!?
object_sample2
var a, b;
a = {hoge: "hogehoge", fuga: "fugafuga"};
b = a; // 参照渡し?
b = {};
b; // => {}
a; // => {hoge: "hogehoge", fuga: "fugafuga"} ←!??!??!?!?!!?!?!?!?!?

参照渡しって言うならこれはどういうことなんですか!?!?
あ、どうせまた「仕様だ」とか言うんでしょう!
いつもそうやってごまかして、今日はそこらへんしっかり答えてもらいますからってあの、どこ行くんですか!
まだ質問終わってないんですけど!あのー!

ほら、値渡しじゃないですか!

取り乱しました。本題に戻ります。
結果を見てもらうと分かると思います。
言った通り、JSは基本的に 参照の値渡し なんです!!!

つ、つまり、どういうことだってばよ…?

まあ、JSの変数って、もはやポインタみたいなものなんですよね。
値がそのまま入っているわけじゃないんですよ。
何が入っているかって、参照先のアドレスなんですね。
つまり、

a = [0, 1, 2];

この1文、なにをしているかって、
(1) 配列[0, 1, 2]を生成して、メモリのどこかに置く。
(2) そのメモリのアドレスをaに入れる。
ということなんですよ!
だから、

b = a;

この1文がなにをしているかって、
(1) aに保存されているアドレスをbに入れる。
ですよね。簡単ですね。

さて、それでは今、aとbが等価であるかと言われたら、等価なんですけど等価でないと言いたいところです。
"=="演算子なり"==="演算子で比較したらtrueを返しますが、それはあくまで aに保存されているアドレスを参照してみて見つかったオブジェクトと、bに保存されているアドレスを参照してみて見つかったオブジェクトが等価だった だけであって、 aとbが同じオブジェクトである ということではないと思うのです。
まあ、JSの実装を調べてないので詳しくは知らないんですけれども(小声)

だから、あくまで

b = [];

というのは、
(1) 配列[]を生成して、メモリのどこかに置く。
(2) そのメモリのアドレスをbに入れる。
というだけのことであって、この動作の中で bに保存されているアドレスを参照する ということ起きていません。起きているのはbへの代入だけです。
bへの代入ですから、もちろん aへの代入をしているわけでもない ですし、 bに保存されているアドレスは参照されていないのでそのアドレスに存在するオブジェクトが変更されることもない わけです。
よって、この文のせいでaまで影響が及ぶことはないということになります。

ただ、

b[0] = "hogera";

みたいな場合はちょっと違います。
まず
(1) 文字列"hogera"を生成して、メモリのどこかに置く。
というのは同様なんですが、次のステップが違います。
何が違うかって、今までbへの代入は、単純に変数bへの代入でしたが、今回の場合は、 bに保存されているアドレスを参照してみて見つかったオブジェクトの、添字0に対応する部分 への代入となります。
つまり、今まで起きていなかった bに保存されているアドレスを参照する という動作が行われるわけです。そして参照先のオブジェクトの一部に対して代入を行います。
bに保存されているアドレスとaに保存されているアドレスは同一のものなので、この動作によって初めて aの参照先のオブジェクトにも変更が及ぶ ことになります。
変数aからしたら知ったこっちゃないですね。aはアドレスを持ってるだけで、そのアドレスが書き換えられるわけでもないですからね。
「変数aさん!出番よ!」と呼ばれたときに「あ〜、はいはい」とか内心思いながら自分の体に刻まれたアドレスを見ながらそのアドレスまで出かけて、「俺を何回同じとこ行かせたら気が済むんだよ、マジで」とかぼやきながらやっと着いたと思ったら「この前となんか違うじゃねえか!」みたいな。誰だよこいつをいつの間に変えやがってとか思ってるんでしょうね。犯人はbですよ!aさん!

だから、こんなことも!

謎は解けましたか、皆さん。
ここで、さらにマジックを見せてあげましょう。ハイッ!

magic_preparation
var a, b;
a = 0;
b = a; // 値渡し!
b.hoge = "hoge";

さて、何が起こるでしょうか、皆さん!
きっと皆さんならお分かりですよね!それでは結果を見てみましょう!

magic_result
a.hoge; // => undefined

あ、あれ?

magic?
b.hoge; // => undefined

え、ええ!?!?!?ゑゑゑゑ?!?!???!??

あ、いや、ちょっと、まあ、場の空気をね、和ませようかなって、ははっ。
いやいや、まあ、僕別に実装自体は何も調べてないし結果から考察しただけなので、まあ、この結果からすればまあプリミティブは単純な値渡しなんじゃないでしょうか?
それか動的にメンバを定義できないとかなんじゃないですかね!
あ、そうそう、仕様ですよ!そう、うん。
仕様です。

それではーーーー!!!!