オブジェクトの生成について考える

最初に注意

自分はつい最近JavaScriptをかじったばかりの,ただの初級者です.以下の文章は単に自分はこう考えている,というだけで,誤りが含まれている可能性は十分にあります.もし読まれる方がいたら,その点を十分にご理解ください.また誤りの指摘は大歓迎です.

オブジェクト最初の一歩

さて,JavaScriptでPointオブジェクトを作ってみましょう.Pointオブジェクトはxとyをプロパティに持ちます.ついでにtoStringメソッドも定義しましょう.

var point = new Object(); // ①
point.x = 1; // ②
point.y = 2;
point.toString = function(){
  return "(" + this.x + "," + this.y + ")" ;
}; // ③

document.write(point.toString());

①でオブジェクトをセットアップします.JavaScriptのオブジェクトは連想配列です.②で連想配列にKeyとValueの組をセットします.JavaScriptではこんな風に,プロパティの追加・削除が簡単に出来ます.③も同様ですが,こちらは関数リテラルにしてみました.JavaScriptでは関数もデータ(オブジェクト)の一種です.関数リテラルは名前を持たない関数(匿名関数)なので,使用するためには関数に変数をバインドする必要があります.今回は連想配列のKeyであるtoStringをバインドしてみました.
……どうでしょうか?なんか納得いかないですか?はい,その気持ちはよく分かります.これまでの常識ではPointオブジェクトを作るにはPointクラスが必要でした.それがよりによってObject?プロパティを後から追加することでPointオブジェクトとしての条件は満たしたけど,でもやっぱりこれをPointオブジェクトとは呼びづらいという抵抗感がありますよね.それに初期化もめんどくさいし……

コンストラクタ関数

オブジェクトの初期化に,コンストラクタ関数を使用することが出来ます.ではコンストラクタ関数を使って書き直してみましょう.

function Point(x, y)
{
  this.x = x;
  this.y = y;
  this.toString = function(){ 
    return "(" + x + "," + y + ")" ;
  };
}

var point = new Point(1,2);
document.write(point.toString());

おっ!なにやらよく見かける形式になりましたね.たしかに事前にコンストラクタ関数を定義しておくことで,オブジェクトの初期化の部分がすっきり書けます.だったらコンストラクタ関数+αをクラスと考えてもいいんじゃないの?
いや,ちょっと待ってください.今の式をもう少し詳しく見てみましょう.

オブジェクト生成演算子(new)

もう一度ここに着目してみてください.

var point = new Point(1,2);

ところでコンストラクタ関数ってなんでしょう?通常の関数との違いってあるのでしょうか?Point関数を例にとると以下の特徴があるように見えます.

  1. 処理の中でthisを使用している
  2. returnがない
  3. new演算子の後に使用している

このうち1と2については,単にコンストラクタ関数の役割がオブジェクトの初期化であるというだけのことです.意味があるかどうかは別にして,コンストラクタ関数の中でthisを使わないこともreturnでオブジェクトを返すことも出来ます.逆に通常の関数でthisを使用することもreturnがない関数を作ることも可能です.コンストラクタという単語は役割をあらわすただの形容詞に過ぎません.
重要なのはnew演算子のほうです.new演算子の働きってなんでしょう?ここでJavaScript第3版の86ページから引用します.

new演算子の働きを簡単に説明しましょう。まずプロパティを定義せずに新しいオブジェクトを生成します.次に、指定されたコンストラクタ関数を呼び出し、指定された引数を渡し、新たに生成されたオブジェクトをthisキーワードの値に代入して渡します。次に、コンストラクタ関数の中で、thisキーワードを使ってこの新しいオブジェクトを自由に初期化します。

このことをプログラムで表現してみます(ただし最後に,今回の文章とは全く関係のない,異質な文が入っています.詳細は次回説明しますので,とりあえず今回は読み飛ばしてください.)

var point = new Object();
Point.apply(point,[1,2]);
point.__proto__ = Point.prototype;

applyメソッドがポイントですね.これが,指定されたコンストラクタ関数を呼び出し〜(中略)〜この新しいオブジェクトを自由に初期化します,までの一連の作業を全部行なってくれます.これだと分かりづらいかもしれないのでちょっと書き換えると

var point = new Object();
point.m = Point;
point.m(1,2);
delete point.m;
point.__proto__ = Point.prototype;

なんだか冒頭の『オブジェクト最初の一歩』と大差ないと思いませんか?

自分が言いたかったこと

あえて誤解を恐れず言い切ってしまえば,コンストラクタ関数はただの初期化関数だし,new演算子シンタックスシュガーです.JavaScriptのオブジェクトはすべてObjectコンストラクタによって生成されます.JavaScriptのオブジェクトはプロパティの追加・削除が自由に行なえます.言い換えるとJavaScriptにはクラスという概念がないので,ある時点のオブジェクトのプロパティセットこそが,オブジェクトの性質を物語るすべてになります.

次回は...

__proto__チェーンによるプロパティ検索システムとか,Prototypeオブジェクトとか...
クラスの継承って聞くけどオブジェクトの継承って聞かないよね?とか...

おまけ(ひとりごと)

new演算子シンタックスシュガーとか言ってしまうと,new Object()の説明に困るんだよな.まぁ,このへんが初級者の限界ってことで(^^;;