JavaScriptのconstructor、prototype、__proto__

前置き

「開眼! JavaScript(オライリー)」を読みました。やや物足りない内容でしたが、constructor、prototype、__proto__といった組み込みプロパティについての理解を整理する良い機会にはなりました。それ以外のメモは、こちら

凡例

var foo = { prop: 3 };
var bar = 3;
var buz = true;
  • 変数の値がオブジェクトなら、矢印でオブジェクトの箱(青い線)を指す
  • 変数がプリミティブ値なら、箱の中に値を書く
  • 値がundefinedなら、? と書く
  • オブジェクトの箱の {} は、プレーンなオブジェクトを意味する

sample

プレーンじゃないオブジェクトというのは、関数と配列のことです。() なら関数オブジェクトを、[] なら配列オブジェクトを意味します。

constructorプロパティ

var foo = { prop: 3 };

すべてのオブジェクトは、constructorプロパティを持ちます。

foo

constructorプロパティは、文字通り、そのオブジェクトのコンストラクタにバインドされます。つまり、関数オブジェクトを指します。この例の場合、fooが指すオブジェクトはリテラルで定義されているので、そのコンストラクタはObject()です。

foo2

constructorプロパティはenumerableではないので、hasOwnProperty()や、for-in文では見えません。Node.jsで確認してみましょう。

> var foo = { prop: 3 };
undefined
>foo.propertyIsEnumerable('constructor');
false
> foo.hasOwnProperty('constructor');
false
> for ( var p in foo ) console.log(p);
prop
undefined
> 'constructor' in foo;
yes

組み込み関数だろうとユーザ定義関数だろうと、全ての関数は、Function()をコンストラクタとします。Function()自身のconstructorプロパティも、Function()へバインドされます。

var Foo = function() {};
var Bar = function Bar() {};

constructor

prototypeプロパティ

var Foo = function() {};
var Bar = function Bar() {};

すべての関数オブジェクトは、prototypeプロパティを持ちます。このプロパティもenumerableではないのですが、hasOwnProperty()には見えるようです。

> var Foo = function() {};
undefined
> Foo.propertyIsEnumerable('prototype');
false
> Foo.hasOwnProperty('prototype');
true
> for ( var p in Foo ) console.log(p);
undefined
> 'prototype' in Foo;
true

関数のprototypeプロパティは、ある特徴を持ったオブジェクトにバインドされます。その特徴とは…

  • constructorプロパティが、その関数にバインドされている
  • そのタイプは、関数がFunctionなら()、Array()なら[]、それ以外なら{}

prototype

不思議なことに、Function.prototypeが指すオブジェクトは、関数オブジェクトなのにprototypeプロパティを持ちません。これは特殊なオブジェクトで、Node.jsでは、Emptyと名付けられています。関数なので呼び出すことはできますが、newすると怒られます。曰く「Empty()はコンストラクタじゃないぞ」。

> Function.prototype
[Function: Empty]
> 'prototype' in Function.prototype
false
> Function.prototype()
undefined
> new Function.prototype()
TypeError: function Empty() {} is not a constructor
    at repl:1:2
    at REPLServer.self.eval (repl.js:110:21)
    at Interface.<anonymous> (repl.js:239:12)
    at Interface.EventEmitter.emit (events.js:95:17)
    at Interface._onLine (readline.js:202:10)
    at Interface._line (readline.js:531:8)
    at Interface._ttyWrite (readline.js:760:14)
    at ReadStream.onkeypress (readline.js:99:10)
    at ReadStream.EventEmitter.emit (events.js:98:17)
    at emitKey (readline.js:1095:12)

また、例えばFoo.prototypeが指すオブジェクトのconstructorプロパティがFoo()を指しているので、このオブジェクトが new Foo(); により生成されたような印象を受けますが、そうではありません。Foo()を定義したとき、暗黙裡に Foo.prototype = { constructor: Foo }; 的なコードが実行されただけで、Foo()は一度も呼ばれてません。

__proto__プロパティ

すべてのオブジェクトが持つプロパティが、constructorの他にもう一つあります。それが __proto__プロパティです。このプロパティは、そのオブジェクトが生成されたときにコンストラクタのprototypeプロパティが指していたオブジェクトへバインドされます。

var Foo = function() {};
var f = new Foo();

proto

__proto__ は特殊なプロパティなので、専用の矢印(2重点線)で図示しました。というのも、すべてのオブジェクトは、自身の__proto__プロパティが指すオブジェクトのプロパティとメソッドを継承するのです。つまり、この矢印は継承元を指します。

> var Foo = function() {};
undefined
> Foo.prototype.name = "Mr.Foo";
'Mr.Foo'
> var f = new Foo();
undefined
> var ff = new Foo();
undefined
> f.name;
'Mr.Foo'
> ff.name;
'Mr.Foo'

__proto__プロパティが指すオブジェクトも __proto__プロパティを持っているので、この継承関係は連鎖します。これをプロトタイプチェーンと呼びます。プロトタイプチェーンは、__proto__プロパティがnullになるまで続きます。ちなみに、Object()のprototypeプロパティが指すオブジェクトの__proto__プロパティはnullです(変更しない限り)。

proto2

ややこしい話しですが、FooやBar(あるいはFunctionやArray)などの関数もオブジェクトなので、__proto__プロパティとプロトタイプチェーンを持ちます。しかし関数オブジェクトのプロトタイプチェーンは、その関数から生成される(newされる)オブジェクトのプロトタイプチェーンとは無関係(別系統)です。

funcproto

まとめ

全部を1つの図に描くと、こんな感じになります。

var Foo = function() {};
var Bar = function Bar() {};
var a = [];
var f = new Foo();
Bar.prototype = f;
var b = new Bar();

all

最後に一言。継承元は、「newされた時点で」コンストラクタのprototypeプロパティが指していたオブジェクトです。prototypeプロパティは別のオブジェクトへ自由に再バインドすることができるので、同じコンストラクタから生成されたオブジェクトでも、継承元が異なる可能性があります(たぶん、これはbad practiceだと思いますが)。

Last modified:2014/03/27 22:33:40
Keyword(s):
References:[メモ ~ 開眼! JavaScript] [言語Tips]
This page is frozen.