JavaScriptの関数の謎を解決できません


16

私は、Javascriptのカーテンシーンの背後にあるものを理解しようとしています。組み込みオブジェクト、特にオブジェクト関数、およびそれらの間の関係の作成を理解することに固執しています。

Array、Stringなどのすべての組み込みオブジェクトがObjectの拡張(継承)であると読んだとき、Objectは作成された最初の組み込みオブジェクトであり、残りのオブジェクトはそれを継承すると仮定しました。しかし、オブジェクトは関数によってのみ作成できることを知ったとき、それは意味がありませんが、関数も関数のオブジェクトにすぎません。鶏と鶏のジレンマのように聞こえ始めました。

他の非常に紛らわしいことは、console.log(Function.prototype)関数を印刷console.log(Object.prototype)するのに、印刷するときにオブジェクトを印刷する場合です。Function.prototype関数がオブジェクトであることが意図されていたのになぜですか?

また、Mozillaのドキュメントによれば、すべてのjavascript functionFunctionオブジェクトの拡張ですが、あなたconsole.log(Function.prototype.constructor)が再び関数である場合はそうです。では、何かを使用して自分自身を作成するにはどうすればよいでしょう(Mind = blown)。

最後にFunction.prototype、関数ですが、doesをconstructor使用Function.prototype.constructorして関数にアクセスできます。つまりFunction.prototypeprototypeオブジェクトを返す関数です


関数はオブジェクトであるため、 Function.prototype関数であり、内部フィールドを持つことができることます。そのため、その構造を調べるときにプロトタイプ関数を実行しないでください。最後に、オブジェクトや関数は、おそらくエンジン内ではなくJavaScriptから、特別な参照のように作成されているので、Javascriptを解釈エンジンがあることを覚えているFunction.prototypeと、Object.prototypeちょうどエンジンによって特別な方法で解釈される可能性があります。
ウォルフラット

1
標準に準拠したJavaScriptコンパイラを実装する場合を除き、このようなことを心配する必要はありません。あなたが何か有用なことを成し遂げたいと思っているなら、あなたはコースから外れています。
ジャレッド・スミス

5
参考までに、「鶏と鶏のジレンマ」の英語での通常のフレーズは、「鶏と卵の問題」、つまり「鶏と卵のどちらが先ですか?」です。(もちろん、答えは卵です。卵子は、ニワトリの数百万年前から存在していました。)
エリックリッパー

回答:


32

私はJavascriptのカーテンシーンの背後にあるものを理解しようとしていますが、組み込みオブジェクト、特にオブジェクトと関数の作成とそれらの間の関係を理解することに固執しています。

それは複雑で、誤解されやすく、非常に多くの初心者のJavascriptの本が間違っているので、読んだすべてを信頼しないでください。

私は1990年代および標準化委員会でMicrosoftのJSエンジンの実装者の1人でしたが、この答えをまとめるのに多くの間違いを犯しました。(私はこれに15年以上取り組んでいませんが、おそらく許されるでしょう。)それはトリッキーなものです。しかし、プロトタイプの継承を理解すると、それはすべて理にかなっています。

Array、Stringなどのすべての組み込みオブジェクトがObjectの拡張(継承)であると読んだとき、Objectは作成された最初の組み込みオブジェクトであり、残りのオブジェクトはそれを継承すると仮定しました。

クラスベースの継承について知っていることをすべて捨てることから始めます。JSはプロトタイプベースの継承を使用します。

次に、「継承」の意味について、非常に明確な定義を頭に置いてください。C#、Java、C ++などのオブジェクト指向言語に慣れている人は、継承はサブタイピングを意味すると考えていますが、継承はサブタイピングを意味しません。 継承とは、あるもののメンバーが別のもののメンバーでもあることを意味しますます。それは必ずしもそれらのものの間にサブタイプ関係があるという意味ではありません!型理論における多くの誤解は、違いがあることに気付いていない人々の結果です。

しかし、オブジェクトは関数によってのみ作成できることを知ったとき、それは意味がありませんが、関数も関数のオブジェクトにすぎません。

これは単純に偽です。一部のオブジェクトは、一部の関数を呼び出しても作成されません。一部のオブジェクトは、JSランタイムによって何も作成されずに作成されます。鶏が産んでいない卵があります。これらは、ランタイムの起動時に作成されたばかりです。new FF

ルールが何であり、多分それが助けになると言ってみましょう。

  • すべてのオブジェクトインスタンスにはプロトタイプオブジェクトがあります。
  • 場合によっては、そのプロトタイプはnull
  • オブジェクトインスタンスのメンバーにアクセスし、オブジェクトにそのメンバーがない場合、オブジェクトはそのプロトタイプに従うか、プロトタイプがnullの場合は停止します。
  • prototype通常、オブジェクトのメンバーはオブジェクトのプロトタイプではありません
  • むしろ、prototype関数オブジェクトFのメンバーは、以下によって作成されたオブジェクトのプロトタイプになるオブジェクトです。new F()
  • 一部の実装では、インスタンス__proto__は実際にプロトタイプを提供するメンバーを取得します。(これは現在非推奨です。依存しないでください。)
  • 関数オブジェクトは、prototype作成時に割り当てられる最新のデフォルトオブジェクトを取得します。
  • もちろん、関数オブジェクトのプロトタイプはFunction.prototypeです。

まとめましょう。

  • のプロトタイプObjectFunction.prototype
  • Object.prototype オブジェクトプロトタイプオブジェクトです。
  • のプロトタイプObject.prototypenull
  • のプロトタイプFunctionFunction.prototype-これは、Function.prototype実際にプロトタイプが存在するまれな状況の1つFunctionです!
  • Function.prototype は関数プロトタイプオブジェクトです。
  • のプロトタイプFunction.prototypeObject.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 CarCar.prototypehonda「のプロトタイプチェーン上のオブジェクトと等しい」という意味です。

はい、そうです。 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 ReptileReptile.prototype等しい」という意味lizardです。

はい、そうです。 lizardのプロトタイプはReptile.prototypeですので、完了です。これは本当です。

さて、どうでしょう

print(lizard.constructor == Reptile);

lizardで構築されnew Reptileたので、これも真であると思うかもしれませんが、間違っているでしょう。それを理由。

  • lizard持ってconstructor財産を?いいえ。したがって、プロトタイプを見ます。
  • のプロトタイプlizardReptile.prototype、ですAnimal
  • Animal持っていますconstructor財産を?いいえ。それでプロトタイプを見てください。
  • のプロトタイプAnimalObject.prototypeでありObject.prototype.constructor、ランタイムによって作成され、に等しくなりObjectます。
  • したがって、これはfalseを出力します。

私たちはReptile.prototype.constructor = Reptile;そこのある時点で言っておくべきでしたが、私たちは覚えていませんでした!

すべてがあなたにとって意味があることを確認してください。それでもわかりにくい場合は、いくつかのボックスと矢印を描画します。

他の非常に紛らわしいことは、console.log(Function.prototype)関数を印刷console.log(Object.prototype)するのに、印刷するときにオブジェクトを印刷する場合です。Function.prototype関数がオブジェクトであることが意図されていたのになぜですか?

関数プロトタイプは、呼び出されるとを返す関数として定義されますundefined。これFunction.prototypeFunctionプロトタイプであることは既に知っていますが、奇妙なことです。したがって、それ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呼び出し可能かどうかを示すプロパティをすべてのオブジェクトに追加します。それはそれと同じくらい簡単です。


9
最後に、短く簡潔でわかりやすいJavaScriptの説明!優秀な!どうすれば私たちの誰が混乱するのでしょうか?:)真面目な話ですが、オブジェクトがルックアップテーブルであることについての最後の部分は本当に重要です。そこ狂気へ方法がある---それはまだある ...狂気
グレッグBURGHARDT

4
@GregBurghardt:最初は複雑に見えますが、複雑さは単純なルールの結果です。すべてのオブジェクトにはがあり__proto__ます。__proto__オブジェクトのプロトタイプのはnullです。__proto__のです。関数プロトタイプ自体を除くすべての関数オブジェクトには、関数プロトタイプがあります。 そして、関数プロトタイプは関数です。これらのルールはすべて簡単で、初期オブジェクトのグラフのトポロジを決定します。new X()X.prototype__proto__ObjectFunction
エリックリッパー

6

あなたはすでに多くの優れた答えを持っていますが、私はこのすべてがどのように機能するかについてのあなたの答えに短く明確な答えをしたいだけです、そしてその答えは:

マジック!!!

本当に、それだけです。

ECMAScript実行エンジンを実装する人々は、ECMAScriptのルールを実装する必要がありますが、実装内でそれらを順守する必要はありません。

ECMAScript仕様では、AはBから継承しますが、BはAのインスタンスですか?問題ない!最初にプロトタイプポインターを使用してAを作成し、AのNULLインスタンスとしてBを作成してから、その後Aのプロトタイプポインターを修正してBを指すようにします。簡単です。

あなたは言うが、待って、ECMAScriptのプロトタイプポインターを変更する方法はありません!しかし、ここに問題があります。このコードはECMAScriptエンジンでは実行さていません。このコード ECMAScriptエンジンです。それはないエンジン上で実行されているECMAScriptのコードを持っていないことをオブジェクトの内部へのアクセス権を持っています。要するに、それは何でもやりたいことができます。

ちなみに、本当にしたい場合は、これを1回行うだけで済みます。その後、たとえば、内部メモリをダンプし、ECMAScriptエンジンを起動するたびにこのダンプをロードできます。

ECMAScriptエンジン自体がECMAScriptで記述されていたとしても(Mozilla Narcissusの場合のように実際には)、これらすべてが依然として適用されることに注意してください。それでも、というのECMAScriptコード実装エンジンはまだそれがされてエンジンへのフルアクセスがあるの実装を、それは、もちろん、それは、エンジンへのアクセスはありませんが、上で実行します


3

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プロパティの初期値です。


今ではありません。ECMA6では、クラスを作成し、それらからオブジェクトをインスタンス化できます。
ncmathsadist

2
@ncmathsadist ES6クラスは単なる構文上のシュガーであり、セマンティクスは同じです。
ハムザファトミ

1
あなたのsarcasmモニカはそれ以外の場合は、このテキストは本当に初心者にはかなり不透明です。
ロバートハーベイ

真実、病気は後で追加し、掘る必要があります
ユアン

1
あれ?ドキュメントから明確ではないことを指摘するために
ユアン

1

次のタイプには、JavaScriptのすべての値が含まれます。

  • boolean
  • number
  • undefined(単一の値を含むundefined
  • string
  • symbol (参照によって比較される抽象的な一意の「もの」)
  • object

JavaScriptのすべてのオブジェクト(つまり、すべて)には、一種のオブジェクトであるプロトタイプがあります。

プロトタイプには、一種のオブジェクトである関数が含まれています1

オブジェクトにはコンストラクターもあります。コンストラクターは関数であるため、一種のオブジェクトです。

入れ子

すべて再帰的ですが、JavaScriptコードとは異なり、JavaScript関数を呼び出さずにオブジェクトを作成できるため、実装は自動的にそれを実行できます(オブジェクトは実装が制御するメモリにすぎないため)。

多くの動的型付け言語のほとんどのオブジェクトシステムは、このような循環型2です。例えば、Pythonで、クラスはオブジェクトであり、クラスのクラスがあるtypeので、type従ってそれ自体のインスタンスです。

最良のアイデアは、言語が提供するツールを使用することであり、それらがどのようにそこに到達したかについてあまり考えないことです。

1関数は呼び出し可能なため、かなり特殊であり、不透明なデータ(その本体および場合によってはクロージャー)を含むことができる唯一の値です。

2実際には、それ自体で後方に曲がった、より拷問された分岐リボンですが、「円形」で十分です。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.