C/C++ プログラマのための JavaScript 入門: new 演算子とインスタンス

no extension

さて, そろそろオブジェクトの話に入っていきましょう。 まずは new 演算子の話から。 例によって「プロローグ」で挙げた文献を頻繁に参照しています。 これらの文献を参照しながらご覧になることをお薦めします。

「データ型」の回では new 演算子を使った以下の式については軽く流しただけで終わってしまいました。

var point = new Object();

new 演算子は空のオブジェクトを生成し, (その参照を) this に格納して右オペランドの関数に渡します。 この「オブジェクト」はデータ型のオブジェクトとはニュアンスがちょっと違います。 紛らわしいですね。 ここで「オブジェクト」に関する用語を整理しておきましょう。

オブジェクト指向プログラミングにおける「オブジェクト」の実体は関連するデータやメソッドをパッケージした単位です。 そういう意味では変数もひとつの「オブジェクト」ですし関数も「オブジェクト」です。 もちろんデータ型としてのオブジェクトも「オブジェクト」です。 C++ における「オブジェクト」にはおおむね2種類あります。 クラス・オブジェクト(あるいは単にクラス)とインスタンス・オブジェクト(あるいは単にインスタンス)です。 まずオブジェクトの構造を定義するクラスがあって, そのクラスの実装例がインスタンスである, という感じです。 この分類でいくと JavaScript の new 演算子で生成されるのはインスタンスということになります。 new 演算子はインスタンスを生成し, そのデータ型がオブジェクト値である, というわけです。

では JavaScript で C++ のクラスに相当するものはなんでしょうか。 実は JavaScript には C++ のクラスに相当するものがありません。 その代わりにあるのがコンストラクタです。

new 演算子の右オペランドの関数(右オペランドは必ず関数でなければなりません)はコンストラクタと呼ばれています。 コンストラクタは new 演算子から渡されたインスタンス(this に格納されています)にプロパティやメソッドを追加し, インスタンスを初期化します。

function Pointer(x, y) {
  this.x = x;
  this.y = y;
}
var point = new Pointer(1, 2);
WScript.Echo("point.x =",  point.x); // 1
WScript.Echo("point.y =",  point.y); // 2

これは C++ とは包含関係が逆です。 C++ ではまずクラスがあってその中でオブジェクトの構造が定義されます。 C++ のコンストラクタはクラスで定義されるメソッドのひとつです。 JavaScript には C++ で言う意味のクラスはありませんが, 強いて言うならコンストラクタの中で定義されるのがクラスである, ということはできるかもしれません。 (故にコンストラクタとクラスを同義にみなす説明も多いです。 確かに継承などを説明する際にはクラスの概念を用いるほうがイメージしやすいですしね)

ところで new 演算子の右オペランドが関数でなければならないということは, 逆に言えばどんな関数でも new 演算子とともに使えばコンストラクタになるのでしょうか。 実はなります。 以下に例を示します。

function square(x) { return x*x; }
var sq = square(2);
WScript.Echo("type of sq =",  typeof sq); // number
var sq = new square(2);
WScript.Echo("type of sq =",  typeof sq); // object

普通の関数をコンストラクタとして使うと, 結果は(関数の内容に関わらず)オブジェクト値になります。 コーディングする上では通常の関数とコンストラクタ用の関数を区別するため, 通常の関数は識別子の先頭文字を小文字に, コンストラクタ用の関数は大文字にする例が多いようです。

逆にコンストラクタ用に作った関数を普通に(new 演算子なしで)実行したらどうなるでしょう。

function Pointer(x, y) {
  this.x = x;
  this.y = y;
}
var point = Pointer(1, 2);
WScript.Echo("type of point =",  typeof point); // undefined
WScript.Echo("x =",  x); // 1
WScript.Echo("y =",  y); // 2

この場合 this には Global インスタンス(への参照)が格納されています。 Global インスタンスはスクリプト実行時にひとつだけ生成されます。 全てのグローバル変数およびグローバル関数は Global インスタンスのプロパティまたはメソッドです。

オブジェクトをリテラルで表現する方法があります。 この場合はどうやってインスタンスを生成しているのでしょう。

var point = { x:1 , y:2 };
WScript.Echo("point.constructor =",  point.constructor); // Object() { ... }

この例をみると, Object コンストラクタで初期化されたインスタンスにプロパティが追加されているようです。 (constructor は Object コンストラクタで定義される組み込みのプロパティで, コンストラクタ自身が格納されています)

コンストラクタを使えばオブジェクト指向プログラミングの特徴のひとつである「集約」を実現することができます。 また不完全ながら「カプセル化」もできます (ただし個々のプロパティは Private にできないのでカプセル化としては不完全ですが)。 しかしこのままでは「継承」ができません。 JavaScript でオブジェクトの継承を行う方法は次回説明しましょう。