私はJavascriptのカーテンシーンの背後にあるものを理解しようとしていますが、組み込みオブジェクト、特にオブジェクトと関数の作成とそれらの間の関係を理解することに固執しています。
それは複雑で、誤解されやすく、非常に多くの初心者のJavascriptの本が間違っているので、読んだすべてを信頼しないでください。
私は1990年代および標準化委員会でMicrosoftのJSエンジンの実装者の1人でしたが、この答えをまとめるのに多くの間違いを犯しました。(私はこれに15年以上取り組んでいませんが、おそらく許されるでしょう。)それはトリッキーなものです。しかし、プロトタイプの継承を理解すると、それはすべて理にかなっています。
Array、Stringなどのすべての組み込みオブジェクトがObjectの拡張(継承)であると読んだとき、Objectは作成された最初の組み込みオブジェクトであり、残りのオブジェクトはそれを継承すると仮定しました。
クラスベースの継承について知っていることをすべて捨てることから始めます。JSはプロトタイプベースの継承を使用します。
次に、「継承」の意味について、非常に明確な定義を頭に置いてください。C#、Java、C ++などのオブジェクト指向言語に慣れている人は、継承はサブタイピングを意味すると考えていますが、継承はサブタイピングを意味しません。 継承とは、あるもののメンバーが別のもののメンバーでもあることを意味しますます。それは必ずしもそれらのものの間にサブタイプ関係があるという意味ではありません!型理論における多くの誤解は、違いがあることに気付いていない人々の結果です。
しかし、オブジェクトは関数によってのみ作成できることを知ったとき、それは意味がありませんが、関数も関数のオブジェクトにすぎません。
これは単純に偽です。一部のオブジェクトは、一部の関数を呼び出しても作成されません。一部のオブジェクトは、JSランタイムによって何も作成されずに作成されます。鶏が産んでいない卵があります。これらは、ランタイムの起動時に作成されたばかりです。new F
F
ルールが何であり、多分それが助けになると言ってみましょう。
- すべてのオブジェクトインスタンスにはプロトタイプオブジェクトがあります。
- 場合によっては、そのプロトタイプは
null
。
- オブジェクトインスタンスのメンバーにアクセスし、オブジェクトにそのメンバーがない場合、オブジェクトはそのプロトタイプに従うか、プロトタイプがnullの場合は停止します。
prototype
通常、オブジェクトのメンバーはオブジェクトのプロトタイプではありません。
- むしろ、
prototype
関数オブジェクトFのメンバーは、以下によって作成されたオブジェクトのプロトタイプになるオブジェクトです。new F()
。
- 一部の実装では、インスタンス
__proto__
は実際にプロトタイプを提供するメンバーを取得します。(これは現在非推奨です。依存しないでください。)
- 関数オブジェクトは、
prototype
作成時に割り当てられる最新のデフォルトオブジェクトを取得します。
- もちろん、関数オブジェクトのプロトタイプは
Function.prototype
です。
まとめましょう。
- のプロトタイプ
Object
はFunction.prototype
Object.prototype
オブジェクトプロトタイプオブジェクトです。
- のプロトタイプ
Object.prototype
はnull
- のプロトタイプ
Function
はFunction.prototype
-これは、Function.prototype
実際にプロトタイプが存在するまれな状況の1つFunction
です!
Function.prototype
は関数プロトタイプオブジェクトです。
- のプロトタイプ
Function.prototype
はObject.prototype
関数Fooを作成するとしましょう。
- のプロトタイプは
Foo
ですFunction.prototype
。
Foo.prototype
Fooプロトタイプオブジェクトです。
- のプロトタイプは
Foo.prototype
ですObject.prototype
。
私たちが言うとしましょう new Foo()
- 新しいオブジェクトのプロトタイプは
Foo.prototype
それが理にかなっていることを確認してください。描きましょう。楕円はオブジェクトインスタンスです。エッジは、__proto__
「のプロトタイプ」を意味するか、prototype
「のprototype
プロパティ」を意味します。
ランタイムがしなければならないことは、それらすべてのオブジェクトを作成し、それに応じてさまざまなプロパティを割り当てることです。それがどのように行われるかを見ることができると確信しています。
次に、知識をテストする例を見てみましょう。
function Car(){ }
var honda = new Car();
print(honda instanceof Car);
print(honda.constructor == Car);
これは何を印刷しますか?
さて、どういうinstanceof
意味ですか? honda instanceof Car
はCar.prototype
、honda
「のプロトタイプチェーン上のオブジェクトと等しい」という意味です。
はい、そうです。 honda
のプロトタイプはCar.prototype
ですので、完了です。これは本当です。
2番目のものはどうですか?
honda.constructor
存在しないため、プロトタイプを参照しCar.prototype
ます。Car.prototype
オブジェクトが作成されたときに、にconstructor
等しいプロパティが自動的に与えられたCar
ため、これは事実です。
これはどうですか?
var Animal = new Object();
function Reptile(){ }
Reptile.prototype = Animal;
var lizard = new Reptile();
print(lizard instanceof Reptile);
print(lizard.constructor == Reptile);
このプログラムは何を印刷しますか?
ここでも、「のプロトタイプチェーン上のオブジェクトlizard instanceof Reptile
とReptile.prototype
等しい」という意味lizard
です。
はい、そうです。 lizard
のプロトタイプはReptile.prototype
ですので、完了です。これは本当です。
さて、どうでしょう
print(lizard.constructor == Reptile);
lizard
で構築されnew Reptile
たので、これも真であると思うかもしれませんが、間違っているでしょう。それを理由。
lizard
持ってconstructor
財産を?いいえ。したがって、プロトタイプを見ます。
- のプロトタイプ
lizard
はReptile.prototype
、ですAnimal
。
Animal
持っていますconstructor
財産を?いいえ。それでプロトタイプを見てください。
- のプロトタイプ
Animal
はObject.prototype
でありObject.prototype.constructor
、ランタイムによって作成され、に等しくなりObject
ます。
- したがって、これはfalseを出力します。
私たちはReptile.prototype.constructor = Reptile;
そこのある時点で言っておくべきでしたが、私たちは覚えていませんでした!
すべてがあなたにとって意味があることを確認してください。それでもわかりにくい場合は、いくつかのボックスと矢印を描画します。
他の非常に紛らわしいことは、console.log(Function.prototype)
関数を印刷console.log(Object.prototype)
するのに、印刷するときにオブジェクトを印刷する場合です。Function.prototype
関数がオブジェクトであることが意図されていたのになぜですか?
関数プロトタイプは、呼び出されるとを返す関数として定義されますundefined
。これFunction.prototype
がFunction
プロトタイプであることは既に知っていますが、奇妙なことです。したがって、それFunction.prototype()
は合法であり、あなたがそれをするとき、あなたはundefined
戻ってきます。だからそれは機能です。
Object
プロトタイプは、この特性を持っていません。それは呼び出し可能ではありません。それは単なるオブジェクトです。
あなたconsole.log(Function.prototype.constructor)
が再び機能であるとき。
Function.prototype.constructor
ただでFunction
明らかに。そしてFunction
機能です。
では、何かを使用して自分自身を作成するにはどうすればよいでしょうか(Mind = blown)。
あなたはこれを過剰に考えています。必要なのは、ランタイムが起動時にオブジェクトの束を作成することです。オブジェクトは、文字列をオブジェクトに関連付ける単なるルックアップテーブルです。ランタイムが起動すると、それが関係しているすべては、数十空白のオブジェクトを作成し、その後割り当てを開始prototype
、__proto__
、constructor
彼らは彼らが作る必要があるとグラフを作るまで、およびので、各オブジェクトのプロパティにします。
上記の図を使用して、constructor
エッジを追加すると便利です。これは非常に単純なオブジェクトグラフであり、ランタイムで問題なく作成できることがすぐにわかります。
良い練習は自分でやることです。ここから始めましょう。my__proto__
「のプロトタイプオブジェクト」とmyprototype
「のプロトタイププロパティ」を意味するために使用します。
var myobjectprototype = new Object();
var myfunctionprototype = new Object();
myfunctionprototype.my__proto__ = myobjectprototype;
var myobject = new Object();
myobject.myprototype = myobjectprototype;
等々。プログラムの残りを埋めて、「実際の」Javascript組み込みオブジェクトと同じトポロジを持つオブジェクトのセットを作成できますか?そうすれば、非常に簡単です。
JavaScriptのオブジェクトは、文字列を他のオブジェクトに関連付ける単なるルックアップテーブルです。それでおしまい!ここには魔法はありません。すべてのオブジェクトをコンストラクターで作成する必要があるなど、実際には存在しない制約を想像しているため、結び目で縛られています。
関数は、追加機能を備えたオブジェクトであり、呼び出されます。したがって、小さなシミュレーションプログラムを実行して、.mycallable
呼び出し可能かどうかを示すプロパティをすべてのオブジェクトに追加します。それはそれと同じくらい簡単です。
Function.prototype
関数であり、内部フィールドを持つことができることます。そのため、その構造を調べるときにプロトタイプ関数を実行しないでください。最後に、オブジェクトや関数は、おそらくエンジン内ではなくJavaScriptから、特別な参照のように作成されているので、Javascriptを解釈エンジンがあることを覚えているFunction.prototype
と、Object.prototype
ちょうどエンジンによって特別な方法で解釈される可能性があります。