※jdk1.6.0_01のjrunscriptを使ってます。
素朴な疑問です。
JavaScriptでオブジェクトを継承するのに、以下のように関数オブジェクトのprototypeプロパティに継承元オブジェクトを参照させる手法はよく見かけます。
Base = { prop1 : 'abc' }; function Klass() {}; Klass.prototype = Base; o1 = new Klass(); print(o1.prop1); // abc
一方、単純に「じゃぁprototypeというプロパティに継承元を設定させれば見に行くのだから、オブジェクトリテラルで生成したオブジェクトのprototypeプロパティに指定してもOK?」と思って試すと、上手く行きません。
Base = { prop1 : 'abc' }; o1 = {}; o1.prototype = Base; print(o1.prop1); // 'undefined' 'prop1' in o1; // false 'prototype' in o1; // true
何でなんだろう?ということでECMA262の仕様書をぽちぽちと拾い読みしてみました。
適当に読んでると混乱しますが、ECMA262の仕様書を読んでいると同じ"prototype"という語が二種類の表記で出てきます。
(1) "[[Prototype]] property" という表記(二重Bracket) (2) "prototype property" という表記(prototypeのフォントが異なる)
(1)の表記の場合、内部で管理している構造体の"Prototype"というプロパティを指します。JavaScriptのソースコード上でのprototypeとは別物になります。
(2)の表記の場合が、通常JavaScriptコードで出てくる"prototype"プロパティになります。大概"オブジェクト.prototype"という形でアクセスされます。
今回は(1)を "Prototype" とダブルクォート + 頭大文字で表記し、(2)を prototype と小文字だけで表記します。(二重BracketがWiki書式に引っ掛かってしまうため)
15.2.5 Properties of Object Instances
Object instances have no special properties beyond those
inherited from the Object prototype object.
オブジェクトリテラルで生成したオブジェクトは "new Object()" された結果と同様で、特別なプロパティは存在しません。つまり、冒頭の疑問で示した
o1.prototype = Base;
というコードは、継承元を示す意味のではなく、通常のプロパティと同じ意味でのprototypeプロパティに値を設定していたわけです。
Object o1 extends Base
じゃなくて
o1['prototype'] = Base
だった、みたいな。
ちなみにオブジェクトリテラルでの生成が "new Object()" であるのは以下の記述で知りました。
11.1.5 Object Initialiser Syntax ObjectLiteral : { } { PropertyNameAndValueList } ... The production ObjectLiteral : { } is evaluated as follows: 1. Create a new object as if by the expression new Object(). 2. Return Result(1).
JavaScriptのオブジェクトのプロパティには、内部的に幾つかの「属性」を持つようです。ECMA262の「8.6.1 Property Attributes」にその一覧があります。以下に簡単にまとめます。
例えばJavaScriptのオブジェクトの基本であるObjectオブジェクト自身はprototypeプロパティを持っていますが、しかしReadOnly属性が設定されるため、prototypeをJavaScriptのソースコードから変更することは出来ません。
15.2.3.1 Object.prototype
The initial value of Object.prototype is the Object prototype object (15.2.4).
This property has the attributes { DontEnum, DontDelete, ReadOnly }.
もちろん prototype プロパティが参照している先のオブジェクトにはアクセスできますので、
Object.prototype = YourKlass;
はNG(jrunscript上では特に異常は出ないが、printしてみると無視されている事がわかる)でも、
Object.prototype.your_func = function() {...};
はOKで、Prototype.jsなどはこれを活用しています・・・というか、していたと記憶しています。(このせいで、Prototype.jsはグローバルオブジェクトを"汚染"するから云々、という話をPrototype.jsが出た当初、見かけました。)
話を元に戻しますと、関数オブジェクトの prototype プロパティはJavaScriptのソースコードから変更可能です。というのは、DontDelete属性しか設定されないからです。(*1)
15.3.5.2 prototype The value of the prototype property is used to initialise the internal [[Prototype]] property of a newly created object before the Function object is invoked as a constructor for that newly created object. This property has the attribute { DontDelete }.
関数オブジェクトがコンストラクタとして実行される時、newされたオブジェクトの "Prototype" プロパティを初期化する為に使われる、と書いてあります。
ここで JavaScript/constructor を見返してみますと、コンストラクタ起動時の挙動で次のようにECMA262では説明されていました。
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). ^^^^^^^^^^^^^^^^^^^^^^
まさにその通りで、関数オブジェクトのprototypeプロパティがオブジェクトの時は、新規作成したオブジェクトの "Prototype" 属性にセットすると書かれています。
これでようやく、ECMA262の記述上で、冒頭の疑問が解決されかつ、関数オブジェクトのprototypeプロパティがどのように扱われるのか追えるようになりました。
コメント