誤り : JavaScript のオブジェクト型は参照型ではないというお話

コンピュータ科学の分野における 「参照型」 (reference type) というのは、「何らかの値を参照するデータ型」 のことだと思っていたのですが、実際の意味は 「参照によってのみアクセス可能なデータ型」 のようです。 そんなわけで前者が正しい定義だと思って書いたこの記事は間違っています、ごめんなさい。 JavaScript のオブジェクト型が参照型 (コンピュータサイエンス用語) である、ということはさておきこの記事内容は間違っているのでご注意ください。 もうちょっとちゃんとまとめてから書き直したいと思います。

JavaScript の型は、大別するとプリミティブ型と参照型に分けられる。 そしてオブジェクト型は参照型である」 と言われることが多々あります (そしてその説明は Java 経験者などにとってわかりやすいと思われます)。 例えば以下のような web ページでそう述べられています。

そういえば、『JavaScript 第 5 版』 にもそう書いていましたね。

しかし、JavaScript (ECMAScript) の言語仕様 (ECMA-262) によると、「オブジェクト型は参照型である」 という言説は正しくありません *1

JavaScript のオブジェクト型を参照型だと思っていてもさほど悪影響はないと思いますが、一応仕様上は違うんですよー、ってことを JavaJavaScript を対比しながらまとめておきます。 この記事を読んだ貴方は、今後 「JavaScript のオブジェクト型は参照型なんだよ」 なんて言わないようにしてくださいね!!

Java の場合

Java において、全ての変数と全ての式は、コンパイル時に決定される型を持っています。 型の一種である参照型 (reference type) には、class type と interface type、それから array type の 3 種類あります。

参照型の値は、参照値 (reference value) または単に参照 (reference) と呼ばれ、オブジェクトへの参照を表します。

// Test クラスを定義する
class Test { /* ... */ }

// class type (参照型の一種) の変数 t1
Test t1;
// 新しい Test オブジェクト (「オブジェクト A」 とする) を生成し, 
// t1 にそのオブジェクトへの参照を代入
t1 = new Test();
// 別の変数に同じオブジェクトへの参照を代入
Test t2 = t1;

上のようなコードを考えてみましょう。 t1t2 は、class type (参照型の一種) の変数の識別子です。 これらの変数の値は、Test オブジェクトへの 参照 です。 図を示すと以下のようになります。

オブジェクトを操作するには、オブジェクトへの参照を介する、というのが Java での様子です。

JavaScript の場合

一方、JavaScript (ECMAScript) におけるオブジェクトはどうなっているのでしょうか。

冒頭でも述べたように、JavaScript には (JavaScript プログラマが使用する範囲では) 参照型というものは仕様上存在しません *2。 よく 「オブジェクト型は参照型である」 と言われますが、オブジェクト型に属する値はオブジェクトそのもの であって、参照ではありません。

例として、Java と同じような以下のコードを考えてみましょう。

// Test コンストラクタを定義する
function Test() { /* ... */ }

// 変数 s1
var s1;
// 新しい Test オブジェクト (「オブジェクト B」 とする) を生成し, 
// s1 にそのオブジェクトを代入
s1 = new Test();
// 別の変数に同じオブジェクトを代入
var s2 = t1;

識別子 s1s2 は、同じオブジェクト (Object 型に属する値) に結び付けられます。 概念的な図を表すと次のようになります。

この図を見ると、識別子 s1 に結び付けられたオブジェクトと s2 に結び付けられたオブジェクトが別々のメモリ位置に存在しているように思えるかもしれませんが、この図は言語仕様を説明するための概念的なものですので、メモリ位置などの実装的なことはここでは無視してください。 2 つの変数は、両方とも 「オブジェクト B」 という同一のオブジェクトを値として持っている、と考えてください。

s1s2 も同じオブジェクトに結び付けられているので、変数 s1 が持っている値 (オブジェクト) を操作すると、s2 が持っている値もその影響を受けます。 (なぜなら両方とも同一の値だから。) 「このようになるのはオブジェクト型が参照型であるから」 とよく説明されますが、(言語仕様的には) 正しくありません。

s1.test = "aaaa";
print( s2.test ); // aaaa
s2.test = "bbbb";
print( s1.test ); // bbbb

とはいえ、オブジェクトという値をどのように実装レベルで表現するのか、という問題はあります。 最も一般的な方法は、やはり Java と同じような形で参照を使って実現する方法だと思います。 実装レベルでの様子を図示すると次のようになります。

結局 Java の reference type と同じような形になるので、「JavaScript のオブジェクト型は参照型である」 という言説はあながち間違いではないと思いますが、ECMAScript の言語仕様でも MozillaJavaScript リファレンスでも 「JavaScript のオブジェクト型は参照型である」 とは規定されていませんので、実装依存ということになります。 そんなわけで、「オブジェクト型は参照型」 という言説は正しくないのです、というお話なのでした。

「参照型」 と言ってはいけない最も大きな理由

さて、ここまで見てきたように 「オブジェクト型は参照型」 という言説は正しくないのですが、「Java の参照型と同じように見えるんだし、参照型と言ってもいいんじゃないか」 という意見もありそうです。 しかし、「参照型 (Reference type)」 というものが ECMA-262 において別に定義されているので、「参照型」 という言葉を使ってはいけません。

ECMAScript の参照型は、ECMAScript の言語仕様を規定するために存在するメタ的な型です。 ECMAScript 言語使用者が直接扱うことはありませんが、識別子と値の結びつきを表現するために使用されます。 識別子を評価した際に返ってくる値は常に参照型の値です。

参照型については以下の記事にわかりやすくまとめられていますので、参照してください。

*1:「オブジェクトの代入は参照渡し」 と書かれていることもありますが、それは 「参照型」 以上におかしいです。

*2:ECMAScript 言語仕様を規定するためのメタ的な型として参照型というものは存在します。 詳しくは後述。