2007-11-13

JavaScript におけるオブジェクト

メソッド呼び出し o.f() を考えてみる.JavaScript ではメソッドはオブジェクトのプロパティで型が関数であるものを言う.ただし,メソッドは言語仕様としては存在しない(追記参照).単に外見がそう見えるというだけの話である.

オブジェクト指向を実装した言語を考える場合に,メソッド呼び出しと継承との関係が非常に重要である.この関係が存在しないオブジェクト指向言語はありえない.JavaScript のメソッドはプロパティであるのだから,オブジェクトからプロパティを取得するところに JavaScript のオブジェクト指向サポートの本質が隠れているに違いない.そこで仕様書からプロパティの取得部分を引用する.以下で [[XXX]] は内部(隠し)プロパティであることを示す.

8.6.2.1 [[Get]] (P)

O の [[Get]] メソッドがプロパティ名 P で呼出されると,次のステップがとられる:

  1. O が P という名前のプロパティを持っていなければ,ステップ 4 へ進む.
  2. そのプロパティの値を取得する.
  3. Result(2) を返す.
  4. O の [[Prototype]] が null ならば,undefined を返す.
  5. [[Prototype]] の [[Get]] メソッドを,プロパティ名 P で呼び出す.
  6. Result(5) を返す.

この仕様から何が言えるだろうか.

  • オブジェクト指向におけるインスタンスが作成できる.各オブジェクトに個別にメソッドをもたなくても,オブジェクトを 1 つ用意してメソッドを定義して各オブジェクトの [[Prototype]] に指させることで共通のメソッド群を持つ複数のオブジェクト,つまりインスタンスを作成できる.
  • オブジェクト指向における継承を実現できる.ステップ 5 で発生する再帰によって,複数のメソッド群の線形探索が可能になっている.また,線形探索によりメソッドのオーバーライドが実現されている.

ではオブジェクトの [[Prototype]] を設定するにはどうしたらよいのだろうか.o.prototype = { ... } としてしまいそうだが,これではだめである.内部 [[Prototype]] プロパティは .prototype ではない.内部 [[Prototype]] プロパティを設定するには new 式を用いる.

o = new O() としたいが,O は何だろうか.仕様によると,O は内部 [[Construct]] プロパティを持つオブジェクトでなければならない(11.2.2 new 演算子).内部 [[Construct]] プロパティを持つオブジェクトとは JavaScript では関数オブジェクトのみである.いくつかの標準オブジェクト(Object, Array など)も関数オブジェクトである.このような関数オブジェクトはコンストラクタと呼ばれる.

o = new O() とすると,内部 [[Construct]] プロパティ(メソッド)が呼ばれる(11.2.2 new 演算子).内部 [[Construct]] メソッドでオブジェクトが生成され,生成されたオブジェクトの内部 [[Prototype]] プロパティに O.prototype の値が設定される(13.2.2 [[Construct]]).

O.prototype に設定するプロパティは,メソッドのように各オブジェクト(インスタンス)に共有されるものであることに注意する必要がある(数,文字列は結果的に共有さ れない—immutable なので変更しようとすると代入することになり,差し替わる).O.prototype にインスタンス変数を定義してはいけない(クラス変数になってしまう).o = new O() とすると,オブジェクトが生成されたあとに,そのオブジェクトを this として関数 O() が呼び出される.つまり O はコンストラクタとして機能する.このコンストラクタの中で作成したプロパティがインスタンス変数となる.

こうして JavaScript ではオブジェクトシステムが実現されている.

(2012/5/31 追記) 「メソッドは言語仕様としては存在しない」と書いたが、そんなこともない。o.f() により o.f にセットされた関数を呼び出すと、o が隠れた引数として設定され、呼ばれた側から this により参照できる。ちなみにメソッド呼び出しで無い関数呼び出し g() では、this はグローバルオブジェクト(window)を指す。

0 件のコメント:

コメントを投稿