私はJavaScriptのカーテンシーンの背後で理解しようとしています。組み込みオブジェクト、特にObjectとFunctionの作成とそれらの間の関係を理解するのに行き詰っています。
Array、Stringなどのすべての組み込みオブジェクトがObjectからの拡張(継承)であることを読んだとき、Objectは最初に作成される組み込みオブジェクトであり、残りのオブジェクトはそれから継承すると想定しました。しかし、オブジェクトは関数によってのみ作成できるが、関数は関数のオブジェクトにすぎないことを知った場合、意味がありません。鶏と鶏のジレンマのように聞こえ始めました。
他の非常に混乱することは、console.log(Function.prototype)
の場合は関数を出力しますが、console.log(Object.prototype)
を出力する場合はオブジェクトを出力します。 _Function.prototype
_がオブジェクトであるはずの関数であるのはなぜですか?
また、Mozillaのドキュメントによると、すべてのjavascript function
はFunction
オブジェクトの拡張ですが、console.log(Function.prototype.constructor)
の場合、これも関数です。次に、何かを使用してそれを自分で作成する方法(マインド=吹き飛ばし).
最後に、_Function.prototype
_は関数ですが、_Function.prototype.constructor
_を使用してconstructor
関数にアクセスできます。つまり、_Function.prototype
_は、prototype
オブジェクトを返す関数です
私はJavaScriptの舞台裏を理解しようとしていますが、組み込みオブジェクト、特にオブジェクトと関数、およびそれらの間の関係の作成を理解するのに行き詰っています。
複雑で誤解しやすく、JavaScriptの初心者向けの本の多くは誤解しているので、読んだ内容すべてを信頼しないでください。
私は1990年代と標準化委員会でMicrosoftのJSエンジンの実装者の1人であり、この答えをまとめる際にいくつかの間違いを犯しました。 (私は15年間以上これに取り組んでいないので、おそらく許されるでしょう。)それはトリッキーなものです。しかし、プロトタイプの継承を理解すれば、それはすべて理にかなっています。
Array、Stringなどのすべての組み込みオブジェクトがObjectからの拡張(継承)であることを読んだとき、Objectは最初に作成される組み込みオブジェクトであり、残りのオブジェクトはそれから継承すると想定しました。
クラスベースの継承について知っているすべてのものを捨てることから始めます。 JSはプロトタイプベースの継承を使用します。
次に、「継承」の意味を頭に明確に定義してください。 OO C#やJavaまたはC++のような言語は、継承はサブタイプを意味すると考えていますが、継承はサブタイプを意味するわけではありません。継承とは、あるもののメンバーが別のもののメンバーでもあることを意味します必ずしもとは限りませんそれらの間にはサブタイプの関係があることを意味しません型理論における多くの誤解は、違いがあることを人々が認識していない結果です。
しかし、オブジェクトは関数によってのみ作成できるが、関数は関数のオブジェクトにすぎないことを知った場合、意味がありません。
これは単に誤りです。一部のオブジェクトは、一部の関数F
に対して_new F
_を呼び出すことによって作成されたではありません。一部のオブジェクトは、JSランタイムによってまったく作成されません。 鶏が産んでいない卵があります。これらは、起動時にランタイムによって作成されました。
ルールとは何か、そしておそらくそれが役立つとしましょう。
null
の場合もあります。prototype
メンバーは、通常、オブジェクトのプロトタイプではありませんではありません。prototype
メンバーは、new F()
によって作成されたオブジェクトのプロトタイプになるオブジェクトです。__proto__
_メンバーを取得します。 (これは現在非推奨です。それに依存しないでください。)prototype
に割り当てられた新しいデフォルトのオブジェクトを取得します。Function.prototype
_です。まとめましょう。
Object
のプロトタイプは_Function.prototype
_ですObject.prototype
_はオブジェクトプロトタイプオブジェクトです。Object.prototype
_のプロトタイプはnull
ですFunction
のプロトタイプは_Function.prototype
_です-これは、_Function.prototype
_が実際にFunction
のプロトタイプであるまれな状況の1つです!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
が自動的に与えられたため、これはtrueです。
これはどうですか?
_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
_を使用して作成されているため、これもtrueと表示されると思うかもしれませんが、間違っています。それを理由にしてください。
lizard
にはconstructor
プロパティがありますか?いいえ。したがって、プロトタイプを調べます。lizard
のプロトタイプは_Reptile.prototype
_、つまりAnimal
です。Animal
にはconstructor
プロパティがありますか?いいえ。プロトタイプです。Animal
のプロトタイプは_Object.prototype
_であり、_Object.prototype.constructor
_はランタイムによって作成され、Object
と等しくなります。ある時点で_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
は関数です。
次に、何かを使用してそれを自分で作成する方法(マインド=吹き飛ばし).
これは考えすぎです。必要なことは、ランタイムが起動時に一連のオブジェクトを作成することだけです。オブジェクトは、文字列をオブジェクトに関連付ける単なるルックアップテーブルです。ランタイムが起動すると、数十個の空白のオブジェクトを作成し、次に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
_プロパティをすべてのオブジェクトに追加します。それはそれと同じくらい簡単です。
あなたはすでに多くの優れた答えを持っていますが、私はこのすべてがどのように機能するかについてあなたの答えに短く明確な答えを与えたいと思います、そしてその答えは:
マジック!!!
本当に、それだけです。
ECMAScript実行エンジンを実装する人はimplement ECMAScriptのルールを適用する必要がありますが、-abideによるものではありませんwithinの実装。
ECMAScript仕様では、AはBから継承すると説明されていますが、BはAのインスタンスですか?問題ない!最初にNULL
のプロトタイプポインターでAを作成し、AのインスタンスとしてBを作成します。次に、Aのプロトタイプポインターを修正して、後でBを指すようにします。簡単な簡単。
あなたは言うが、待って、ECMAScriptでプロトタイプポインターを変更する方法はありません。しかし、これが問題です。このコードは実行されていませんon ECMAScriptエンジン、このコードis ECMAScriptエンジンです。 doesエンジンで実行されているECMAScriptコードにはないオブジェクトの内部にアクセスできます。つまり、必要なことは何でもできます。
ちなみに、本当に必要な場合は、これを一度だけ実行する必要があります。その後、たとえば内部メモリをダンプして、ECMAScriptエンジンを起動するたびにこのダンプをロードできます。
ECMAScriptエンジン自体がECMAScriptで記述されていても(たとえば、実際にはMozilla Narcissusの場合のように)、これはすべて当てはまることに注意してください。それでも、implementsエンジンがエンジンに完全にアクセスできるECMAScriptコードはimplementingですが、もちろんエンジンにアクセスできません- 実行中。
ECMA仕様1から
ECMAScriptには、C++、Smalltalk、Javaなどの適切なクラスが含まれていませんが、オブジェクトにストレージを割り当てるコードを実行してオブジェクトを作成し、プロパティに初期値を割り当てることによってオブジェクトのすべてまたは一部を初期化するコンストラクターをサポートしています。コンストラクターを含むすべての関数はオブジェクトですが、すべてのオブジェクトがコンストラクターであるとは限りません。
どうすればもっとはっきりするかわかりません!!! </sarcasm>
さらに下を見ると:
プロトタイププロトタイプは、ECMAScriptで構造、状態、および動作の継承を実装するために使用されるオブジェクトです。コンストラクターがオブジェクトを作成すると、そのオブジェクトは、プロパティ参照を解決する目的で、コンストラクターに関連付けられたプロトタイプを暗黙的に参照します。コンストラクターに関連付けられたプロトタイプは、プログラム式constructor.prototypeによって参照でき、オブジェクトのプロトタイプに追加されたプロパティは、継承を通じて、プロトタイプを共有するすべてのオブジェクトによって共有されます。
したがって、プロトタイプはオブジェクトですが、必ずしも関数オブジェクトではないことがわかります。
また、この興味深いtitbitがあります。
http://www.ecma-international.org/ecma-262/8.0/index.html#sec-object-objects
Objectコンストラクタは、%Object%組み込みオブジェクトであり、グローバルオブジェクトのObjectプロパティの初期値です。
そして
Functionコンストラクタは、%Function%組み込みオブジェクトであり、グローバルオブジェクトのFunctionプロパティの初期値です。
次のタイプは、JavaScriptのすべての値を含みます。
boolean
number
undefined
(単一の値undefined
を含む)string
symbol
(参照によって比較される抽象的な「もの」を抽象化)object
JavaScriptのすべてのオブジェクト(つまりすべて)には、一種のオブジェクトであるプロトタイプがあります。
プロトタイプには関数も含まれており、これも一種のオブジェクトです1。
オブジェクトには、関数であるコンストラクターも含まれているため、一種のオブジェクトです。
これはすべて再帰的ですが、JavaScriptコードとは異なり、JavaScript関数を呼び出さなくてもオブジェクトを作成できるため、実装はそれを自動的に実行できます(オブジェクトは実装が制御する単なるメモリであるため)。
多くの動的型付け言語のほとんどのオブジェクトシステムは循環型です2 このような。たとえば、Pythonでは、クラスはオブジェクトであり、クラスのクラスはtype
であるため、type
はそれ自体のインスタンスです。
最良のアイデアは、言語が提供するツールを使用することであり、どのようにそこに到達したかについてあまり考えないことです。
1関数は呼び出し可能であり、不透明なデータ(本体とクロージャ)を含むことができる唯一の値であるため、かなり特殊です。
2実際には、それ自体が後方に曲がった、拷問された枝分かれしたリボンのようなものですが、「円形」で十分です。