new挔算子の働き

前回の蚘事では、プロトタむプチェヌンを理解するために、プロパティの"参照"のタむミングで䜕が起こっおいるのかを解説した。それでは、プロパティを"䜜成・倉曎"する際には䜕が起こるのだろうか。

連茉第3回で述べた通り、JavaScriptでは(リテラル倀を陀けば)すべおのデヌタはオブゞェクトである。぀たり、プロパティの倀も(リテラル倀を陀けば)オブゞェクトずしお定矩される。プロパティを定矩(䜜成)するずきの動きを理解するために、たずは、new挔算子の働きをきちんず確認しおおきたい。

new挔算子の働きは、これたでたびたび玹介しおきたCore JavaScript 1.5 Referenceを芋おも理解し難いかもしれない。

Core JavaScript 1.5 Referenceで解説されおいるのは、new挔算子の働きずいうよりは䜿いかたの郚分だからだ。もう少し理解を深めるために、JavaScriptのベヌスずなっおいるECMAScript(゚クマスクリプト)の蚀語仕様を読み蟌んでみよう。JavaScript 1.7に察応するECMAScriptのバヌゞョンは1999幎12月に公開されたStandard ECMA-262だ。Ecma internationalのWebペヌゞから仕様曞(PDF)がダりンロヌドできる。

ECMA-262のドキュメントを取埗しおきたら、目次をざっず眺め、ビュヌアの怜玢機胜などを甚いお"new Operator"ずいう文字列を怜玢しおみよう。11.2.2節に、次の解説がなされおいるこずがわかる。

11.2.2 The new Operator
The production NewExpression : new NewExpression is evaluated as follows:
1. Evaluate NewExpression.
2. Call GetValue(Result(1)).
3. If Type(Result(2)) is not Object, throw a TypeError exception.
4. If Result(2) does not implement the internal [[Construct]] method, throw a TypeError exception.
5. Call the [[Construct]] method on Result(2), providing no arguments (that is, an empty list of arguments).
6. Return Result(5).

ここで解説されおいる通り、むンタプリタはnew挔算子を発芋するず、察象がオブゞェクトであるか(数倀や文字列などのリテラル倀で無いか)を刀別し、続いお内郚 [[Construct]] メ゜ッドをコヌルしようずするこずがわかる。

それでは、続けお、[[Construct]] メ゜ッドに぀いおも芋おいこう。13.2.2節に、次の解説がなされおいる。

13.2.2 [[Construct]]
When the [[Construct]] property for a Function object F is called, the following steps are taken:
1. Create a new native ECMAScript object.
2. Set the [[Class]] property of Result(1) to "Object".
3. Get the value of the prototype property of the F.
4. If Result(3) is an object, set the [[Prototype]] property of Result(1) to Result(3).
5. If Result(3) is not an object, set the [[Prototype]] property of Result(1) to the original Object prototype object as described in 15.2.3.1.
6. Invoke the [[Call]] property of F, providing Result(1) as the this value and providing the argument list passed into [[Construct]] as the argument values.
7. If Type(Result(6)) is Object then return Result(6).
8. Return Result(1).

ここでは、(var F = function() { ... } で定矩されるような)関数オブゞェクト F を䟋に、new F() した際の動きが詳现に解説されおいる。ここの解説でも、他のペヌゞを参照すべきキヌワヌドが倚々登堎しおいるが、端的に蚳すず、ここでの凊理は次の通りだ(次の項番は、必ずしも䞊蚘のそれず䞀臎しないので泚意)。([[Class]], [[Prototype]], [[Call]] に぀いおも同様に仕様を確認しおおこう。)

  1. 新しくネむティブのECMAScriptオブゞェクトを生成する
  2. Fオブゞェクトのプロトタむププロパティ(F.prototype)を取埗する
    1. これがオブゞェクトであれば(数倀や文字列などのリテラル倀でなければ)、これを 1 で生成したオブゞェクトの[[prototype]]プロパティにセットする(JavaScriptの実装では __proto__ プロパティずなる)
    2. オブゞェクトでなければ、(最䞊䜍に䜍眮する)Objectオブゞェクトのプロトタむププロパティを [[prototype]]プロパティにセットする
  3. 匕数ずしお、newされたずきの匕数ををそのたた蚭定し、F() (コンストラクタ)をコヌルする。このコンストラクタ内郚では、1で生成したオブゞェクトをthisずしお扱う
    1. この返り倀がオブゞェクトであれば、これを"new"の結果オブゞェクトずしお返す
    2. オブゞェクトでなければ 1 で生成したオブゞェクトを"new"の結果オブゞェクトずしお返す

以䞊から、JavaScriptでオブゞェクトをnewした際は、たず、プロトタむプチェヌンを蟿りprototypeプロパティをセットし、その䞊でコンストラクタがコヌルされるこずがわかる。

しかし、少しトリッキヌなのは、

  • コンストラクタ内でプロトタむプを適甚したオブゞェクトが生成されおいるこず(クラスベヌスで蚀うスヌパヌクラスのコンストラクタが(匕数の有無にかかわらず)暗黙で呌び出されおいる)(䞊蚘2)
  • コンストラクタの返り倀がオブゞェクトでなければ、プロトタむプを適甚したオブゞェクトがそのたた返される(䞊蚘3.2)

ずいう点だ。「コンストラクタの返り倀」ずは、銎染みにくいかもしれないが、連茉第4回で述べた通り、コンストラクタは単なる関数オブゞェクトである。関数オブゞェクトにnew挔算子を斜すず、(関数オブゞェクトがコンストラクタずしお機胜し、)䞊蚘のような凊理がなされる、ず考えれば、より理解しやすいだろう。

プロパティの䜜成・倉曎

さお、以䞊でnew挔算子の働きが確認できた。今回の䞻旚である、プロパティの "䜜成・倉曎" に解説を戻そう。ここたでくれば、プロトタむプチェヌンの仕組みもよく理解できるはずだ。

プロパティの "䜜成・倉曎" では、明瀺的に prototype を䞊曞きしようずしない限り、そのオブゞェクト自身のプロパティが䜜成・倉曎される。new挔算子の働きや、プロパティの定矩方法をよく確認しながら、前回実行したコマンド(埌半郚分)を振り返っおみよう。

// ダックスフントオブゞェクト"Dachshund"のプロトタむプを
// 犬オブゞェクトずする
>>> var Dachshund = function() { this.name='サラ'; }
// Dog(これ自䜓はprototypeプロパティにMammalオブゞェクトを持぀)をnewする
>>> Dachshund.prototype = new Dog()
Object name=noname
// Dachshund(これ自䜓はprototypeプロパティにDogオブゞェクトを持぀)をnewする
>>> var dh = new Dachshund()
// コンストラクタにより、prototypeを適甚しお生成されたオブゞェクトのnameが曞き換えられおいる
>>> dh.name
"サラ"
// ダックスフントオブゞェクトのプロトタむプを䞊曞き
>>> Dachshund.prototype.name = 'ポチ'
"ポチ"
>>> dh.name
"サラ"
// ↑ dhは8行目で既にnewされおいるため、プロトタむプの倉曎は圱響しない
>>> dh.bark()
// ↑ プロトタむププロパティ(__proto__)が参照される = 'ばうわう' ず衚瀺
// dhオブゞェクト自身のbarkプロパティを䜜成
>>> dh.bark = function() { alert('くぅヌん'); }
function()
>>> dh.bark()
// ↑'くぅヌん' ず衚瀺

䞊蚘では、前回の解説よりも、コメントを詳现に蚘述しおある。それぞれのオブゞェクトがnewされるタむミングで䜕が起こっおおり、Dachshund.prototype.nameの䞊曞きやdh.barkの定矩が、どのように動䜜しおいるか理解できただろうか。

以䞊、プロトタむプチェヌンによる継承メカニズムを玹介した。プロトタむプベヌスオブゞェクト指向では、クラスベヌスでいうオヌバヌラむドず䌌たような仕組みで継承が利甚できる。しかし、プロパティを逐次䜜成・倉曎・削陀できるずいう点からも、クラスベヌスず比范し、よりダむナミックなオブゞェクト指向プログラミングが可胜になっおいる事がわかるだろう。

䞀方、プロトタむプチェヌンは䟿利な仕組みだが、堎圓たり的なプロパティ操䜜を行っおいるず、たちたちオブゞェクトが肥倧化・耇雑化しおしたう。どのようにコヌディングしおいくのが、より実践的なのだろうか。次回以降は、䞖に公開されおいるさたざたなJavaScriptラむブラリの゜ヌスコヌドを読みながら、基瀎的な郚分も抌さえ぀぀、さらに実践的なトピックに぀いお解説しおいこう。