コンストラクタとプロトタイプを混同していた
一般にwindowのプロパティとして参照可能な「Array」とかをナイーブにプロトタイプとして考えていたけど、あれは実際コンストラクタだったかもしれない。JavaScriptにおけるプロトタイプとコンストラクタについて、Arrayの特殊性を題材に整理してみる。
予備知識として、JavaScriptのArrayは特殊な実装が行われている。
Array.prototype.constructorはArrayに戻る。そしてArray.constructorはFunction。これは普通のプロトタイプチェーン。
しかしObject.getPrototypeOf([])はFunctionであり、Array.prototypeではない
通常:Object.getPrototypeOf(1) === Number.prototype
図式化すると、Object.getPrototypeOf([[Object]]) === [[Constructor]].prototype
つまりArray.prototype.constructor(=Array)はただの「配列オブジェクトを返却する関数」であり厳密なコンストラクタではないといえる。
逆の視点から言えば、Array.prototypeも厳密なプロトタイプとして成立しているのではなく、「ある関数からの返り値として設定されているオブジェクト(の規格)」と言う意味しか持たないようにも見える。
以上をまとめると、「コンストラクタとは『オブジェクトを返却する関数』であり、プロトタイプはコンストラクタが返却するオブジェクトの規格を決定する。」と言うこと。通常の場合「コンストラクタのプロトタイプ」は、「生成するObjectのプロトタイプ」と一致するが、Arrayのようにそうでない場合もある。
ちなみに、アロー関数とfunction宣言について
アロー関数もfunctionもconstructorは一緒で、Function
Object.getPrototypeOfから取得できるprototypeも共通でFunction.prototype
functionはFunctionのサブタイプ宣言だが、アロー関数はそうではない。
アロー関数はprototypeプロパティを明示的には持たないが、functionキーワードで宣言した関数は持つ。
thisキーワードはアロー関数スコープでは変更されず、functionスコープでは変更される。
アロー関数にはそもそもthisをバインドできない
Function.constructorはFunctionに戻る
このことを踏まえると、「関数がprototypeプロパティを持つ」≒「thisを制約する」と言うことのような気もする。アロー関数はFunctionをプロトタイプに持つが、super()をコールしておらず、thisを参照できない。よって、prototypeも持っていない。
Arrayは関数宣言されているがコンストラクタではない関数。Array.prototype(プロパティ)は、厳密なコンストラクタを持たず、便宜的に単なる関数をコンストラクタに指定しているだけのオブジェクト。そしてアロー関数は独自のprototypeを形成できず、そのためにコンストラクターになり得ない関数。
一般的にはコンストラクタ宣言でFunctionのサブタイプを作る(間接的には新規にObject.prototypeを継承したオブジェクトを作成する)ことで新たなprototypeを形成し、そのプロトタイプと生成オブジェクトを関連づけることでプロトタイプチェーンが成立しているように見える。しかしArrayコンストラクタは、宣言時に作り出したプロトタイプと、自身のプロトタイプを関連づけていない。