web-dev-qa-db-ja.com

JavaScriptで関数の謎を解くことができない

私はJavaScriptのカーテンシーンの背後で理解しようとしています。組み込みオブジェクト、特にObjectFunctionの作成とそれらの間の関係を理解するのに行き詰っています。

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

他の非常に混乱することは、console.log(Function.prototype)の場合は関数を出力しますが、console.log(Object.prototype)を出力する場合はオブジェクトを出力します。 _Function.prototype_がオブジェクトであるはずの関数であるのはなぜですか?

また、Mozillaのドキュメントによると、すべてのjavascript functionFunctionオブジェクトの拡張ですが、console.log(Function.prototype.constructor)の場合、これも関数です。次に、何かを使用してそれを自分で作成する方法(マインド=吹き飛ばし).

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

16
Umair Abid

私はJavaScriptの舞台裏を理解しようとしていますが、組み込みオブジェクト、特にオブジェクトと関数、およびそれらの間の関係の作成を理解するのに行き詰っています。

複雑で誤解しやすく、JavaScriptの初心者向けの本の多くは誤解しているので、読んだ内容すべてを信頼しないでください。

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

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

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

次に、「継承」の意味を頭に明確に定義してください。 OO C#やJavaまたはC++のような言語は、継承はサブタイプを意味すると考えていますが、継承はサブタイプを意味するわけではありません。継承とは、あるもののメンバーが別のもののメンバーでもあることを意味します必ずしもとは限りませんそれらの間にはサブタイプの関係があることを意味しません型理論における多くの誤解は、違いがあることを人々が認識していない結果です。

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

これは単に誤りです。一部のオブジェクトは、一部の関数Fに対して_new F_を呼び出すことによって作成されたではありません。一部のオブジェクトは、JSランタイムによってまったく作成されません。 鶏が産んでいない卵があります。これらは、起動時にランタイムによって作成されました。

ルールとは何か、そしておそらくそれが役立つとしましょう。

  • すべてのオブジェクトインスタンスには、プロトタイプオブジェクトがあります。
  • プロトタイプはnullの場合もあります。
  • オブジェクトインスタンスのメンバーにアクセスし、オブジェクトにそのメンバーがない場合、オブジェクトはプロトタイプに従い、プロトタイプがnullの場合は停止します。
  • オブジェクトのprototypeメンバーは、通常、オブジェクトのプロトタイプではありませんではありません。
  • むしろ、関数オブジェクトFの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のいずれかです。

enter image description here

ランタイムがしなければならないことは、それらのオブジェクトをすべて作成し、それに応じてさまざまなプロパティを割り当てることだけです。それがどのように行われるかは、きっとわかると思います。

次に、知識をテストする例を見てみましょう。

_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と等しくなります。
  • したがって、これは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は関数です。

次に、何かを使用してそれを自分で作成する方法(マインド=吹き飛ばし).

これは考えすぎです。必要なことは、ランタイムが起動時に一連のオブジェクトを作成することだけです。オブジェクトは、文字列をオブジェクトに関連付ける単なるルックアップテーブルです。ランタイムが起動すると、数十個の空白のオブジェクトを作成し、次に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_プロパティをすべてのオブジェクトに追加します。それはそれと同じくらい簡単です。

32
Eric Lippert

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

マジック!!!

本当に、それだけです。

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ですが、もちろんエンジンにアクセスできません- 実行中

6
Jörg W Mittag

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

3
Ewan

次のタイプは、JavaScriptのすべての値を含みます。

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

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

プロトタイプには関数も含まれており、これも一種のオブジェクトです1

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

nested

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

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

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

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

2実際には、それ自体が後方に曲がった、拷問された枝分かれしたリボンのようなものですが、「円形」で十分です。

1
Challenger5