JavaScriptの基本と誤解

オブジェクト、クラス、コンストラクター

非常に抽象的なオブジェクト指向プログラミング言語であるECMAScriptは、オブジェクトを操作します。 プリミティブもありますが、必要に応じてオブジェクトに変換されます。 オブジェクトはプロパティのコレクションであり、それに関連付けられたプロトタイプオブジェクトもあります。 プロトタイプはオブジェクトまたはヌル値です。
JavaScriptには使い慣れたクラスはありませんが、特定のアルゴリズムに従ってオブジェクトを生成するコンストラクター関数があります(new演算子を参照)。

代表的な委任継承


古典的な継承は、人々が先祖の遺伝子を継承する方法に非常に似ています。 いくつかの基本的な機能があります:人々は歩く、話すことができます...そして、それぞれの人に特徴的な機能があります。 人々は自分自身を変更することはできません-クラス(ただし、独自のプロパティを変更できます)および祖父母、母親、父親は、子供や孫の遺伝子に動的に影響を与えることはできません。 すべてが非常に地上的です。

ここで、遺伝子の継承が地球と同じではない別の惑星を想像してください。 彼らの子孫の遺伝子を変えることができる「テレパシー継承」を持つ突然変異体があります。
例を見てみましょう。 父は祖父から遺伝子を継承し、息子は祖父から継承する父から遺伝子を継承します。 各変異体は自由に変異し、その子孫の遺伝子を変更できます。 たとえば、祖父は緑の肌の色を持ち、父はその色を継承し、息子もその色を継承しました。 そして突然、祖父は「グリーンに行くのはうんざりだ-離陸したい」と判断し、混乱して(クラスのプロトタイプを変更した)、「テレパシーで」この突然変異を父と息子に広めた。 その後、父は「祖父は、古い時代に完全に動いた」と考え、遺伝子の色を緑色に戻し(クラスのプロトタイプを変更し)、息子に「テレパシー」で色を配りました。 父と息子は緑、祖父は青です。 今、祖父がどんなに頑張ろうとも、父と息子は色を変えません。父はプロトタイプで色を定めており、息子は最初に父のプロトタイプを継承するからです。 今、息子は「私は自分の色を黒に変え、子孫に父から色を継承させます」と決め、子孫に影響を与えない彼自身の財産を規定しました。 などなど。

コード内のすべてを説明しましょう:
var Grandfather = function () {}; //  Grandfather Grandfather.prototype.color = 'green'; var Father = function () {}; //  Father Father.prototype = new Grandfather(); //  ,        var Son = function () {}; //  Son Son.prototype = new Father(); //  var u = new Grandfather(); //  "" Grandfather var f = new Father(); //  "" Father var s = new Son(); //  "" Son //    console.log([u.color, f.color, s.color]); // ["green", "green", "green"] //         Grandfather.prototype.color = 'blue'; console.log([u.color, f.color, s.color]); // ["blue", "blue", "blue"] //          Father.prototype.color = 'green'; //     : // Grandfather.prototype.color = 'green'; console.log([u.color, f.color, s.color]); // ["blue", "green", "green"] //   Grandfather.prototype.color = 'blue'; console.log([u.color, f.color, s.color]); // ["blue", "green", "green"] //             s.color = 'black'; //   ,      console.log([u.color, f.color, s.color]); // ["blue", "green", "black"] var SonsSon = function () {}; //  SonsSon SonsSon.prototype = new Son(); //  var ss = new SonsSon(); //  "" SonsSon //      console.log([u.color, f.color, s.color, ss.color]); // ["blue", "green", "black", "green"] 

読む:
JavaScriptのOOP:継承
プロトタイプ、__ proto__、コンストラクター、およびそれらのチェーンを写真で扱います

プロトタイプチェーン、指定された名前のプロパティを取得


プロトタイプチェーンは、継承と共有プロパティを整理するために使用されるオブジェクトの有限チェーンです。

JavaScriptでは、各オブジェクトには独自のプロパティ(所有プロパティ)とプロトタイプオブジェクトへのリンクがあり、プロトタイプには独自のプロパティとプロトタイプへのリンクがあり、プロトタイププロトタイプには独自のプロパティとプロトタイプへのリンクなどがあり、プロトタイプリンクまでnullではありません-この構造はプロトタイプのチェーンと呼ばれます。
オブジェクトのプロパティにアクセスしようとすると(ピリオドまたは括弧を使用)、ポインターが名前で検索されます。まず、独自のプロパティのリストからそのような名前のポインターがあるかどうか(チェックされている場合は返されます)、そうでない場合は、独自のプロトタイプで検索が行われます(存在する場合は戻ります)、存在しない場合は、プロトタイプのプロトタイプで検索が行われ、プロトタイプのプロトタイプがnullになるまで、この場合は未定義を返します。

一部のJavaScript実装では、__ proto__プロパティを使用して、プロトタイプチェーン内の次のオブジェクトを表します。

読み取りプロパティの検索は、次の関数で説明できます。
 function getProperty(obj, prop) { if (obj.hasOwnProperty(prop)) return obj[prop] else if (obj.__proto__ !== null) return getProperty(obj.__proto__, prop) else return undefined } 

たとえば、2つのプロパティ(x、y)とprintメソッドを含む単純なPoint 2Dクラスを考えます。 上記の定義を使用して-オブジェクトを構築します。
 var Point = { x: 0, y: 0, print: function () { console.log(this.x, this.y); } }; var p = {x: 10, __proto__: Point}; //  'x'    : /* px */ getProperty(p, 'x'); // 10 //  'y'    __proto__  - Point /* py */ getProperty(p, 'y'); // 0 //  print    __proto__  - Point /* p.print() */ getProperty(p, 'print').call(p); // 10 0 
結果の関数を直接call代わりにcallを使用した理由を以下に説明します。

実際、 Pointはもう1つのプロパティがあり、これは親のObject.prototypeのプロトタイプへの参照であり、 Pointの場合はObject.prototypeObject.prototypeます。
たとえば、これは、プロトタイプのチェーン全体が最初の例でどのように見えるかです。
  /* SonsSon <- Son <---- Father <- Grandfather <-- Object <-- null */ console.log(ss.__proto__.__proto__.__proto__.__proto__.__proto__.__proto__ === null); 

__proto__、プロトタイプ、新しい演算子


上記の「低レベルコード」がありましたが、今度はすべてがどのように動作するかを見てみましょう。
 function Point(x, y) { //  Point this.x = x; this.y = y; } Point.prototype = { //   print: function () { console.log(this.x, this.y); } }; var p = new Point(10, 20); //    p.print(); // 10 20 

前のコードで、私たちが何を指しているのかを少なくとも知っていた場合、すべてが何らかの形で混乱していました。

すべての「魔法」は、新しい演算子にあります。 JavaScriptの作成者であるBrendan Eichは、JavaScriptをC ++、Javaなどの従来のオブジェクト指向言語に見せたいと考えていたため、新しい演算子が追加されました。 仕組みを見てみましょう。

new演算子は、自由に関数と関数引数( new F(arguments...) )を持ち、次のアクションを実行します。

1. F.prototypeを参照する単一のF.prototypeプロパティを持つ空のオブジェクトを作成します
2. thisが以前に作成されたオブジェクトであるコンストラクターFを実行します
3.オブジェクトを返します
new演算子の動作をエミュレートするNew関数を作成します。
 function New (F, args) { /*1*/ var n = {'__proto__': F.prototype}; /*2*/ F.apply(n, args); /*3*/ return n; } 

Pointを使用して前の例を変更します。
 function Point(x, y) { //  Point this.x = x; this.y = y; } Point.prototype = { //   print: function () { console.log(this.x, this.y); } }; var p1 = new Point(10, 20); p1.print(); // 10 20 console.log(p1 instanceof Point); // true //  : var p2 = New(Point, [10, 20]); p2.print(); // 10 20 console.log(p2 instanceof Point); // true 

プロトタイプチェーン


最初の例では、次の構造を使用してプロトタイプチェーンを構築しましたFather.prototype = new Grandfather()
 var Grandfather = function () {}; //  Grandfather Grandfather.prototype.color = 'green'; var Father = function () {}; //  Father Father.prototype = new Grandfather(); //  ,        var Son = function () {}; //  Son Son.prototype = new Father(); //  

これで、新しい演算子の動作がわかり、ここで何が行われているかを理解できます- new Grandfather()展開します。
 Father.prototype = { __proto__: { //  Grandfather color: 'green', __proto__: Object.prototype } }; 

さて、 new Father()呼び出すとnew Father()このオブジェクトを取得します(すぐにオブジェクトを展開します):
 Son.prototype = { __proto__: { //  Father __proto__: { //  Grandfather color: 'green', __proto__: Object.prototype } } } 

sオブジェクト(Sonインスタンス)のコードの最後にあるものを見てみましょう
 { color: 'black', //      __proto__: { //  Son __proto__: { //  Father color: 'green', //     __proto__: { //  Grandfather color: 'blue', //      __proto__: Object.prototype } } } } 

なぜ、 Father.prototype = new Grandfather()がプロトタイプチェーンを構築するのに最適なオプションではないのですか?
Grandfatherのコンストラクターを呼び出さなければならないため、余分なプロパティを混ぜて、 alertなどの余分なメソッドを呼び出すことができます。 この問題を回避するには、偽のコンストラクターを使用します。
 function inherit (object, parent) { function F(){}; //   F.prototype = parent.prototype; //     object.prototype = new F(); //       return object; //     }; 

使用例:
 var Grandfather = function () {}; //  Grandfather Grandfather.prototype.color = 'green'; var Father = function () {}; //  Father inherit(Father, Grandfather); //   

Grandfatherコンストラクターは実行されません。 Grandfatherコンストラクターを実行する必要がある場合は、callまたはappyを使用して呼び出します
 var Father = function () { //  Father Grandfather.call(this); }; 


Instanceof演算子


 if (p instanceof Point) { // ... } 

instanceof演算子は、チェーンプロトタイプと非常に密接に関連しています。 プロトタイプチェーンを使用して判定をレンダリングし、指定されたオブジェクト「p」がコンストラクタ「Point」によって生成されたかどうかをチェックしません。 この時点でしばしば混乱が生じます。

instanceof演算子は、objとconstructor:( obj instanceof constructor )の2つのオブジェクトで動作しobj instanceof constructor 。 constructor.prototypeから開始して、プロトタイプチェーンを実行し、次の等式obj.__proto__ === constructor.prototypeをチェックしますobj.__proto__ === constructor.prototype場合、trueを返します。

コードで説明します。
 function isInstanceOf(obj, constructor) { if (obj.__proto__ === constructor.prototype) return true; else if (obj.__proto__ !== null) return isInstanceOf(obj.__proto__, constructor) else return false } 

上記の例を考えてみましょう:
 function Point(x, y) { //  Point this.x = x; this.y = y; } Point.prototype = { //   print: function () { console.log(this.x, this.y); } }; var p = new Point(10, 20); //    /* {} instanceof Object */ console.log(isInstanceOf({}, Object)); // true /* p instanceof Point */ console.log(isInstanceOf(p, Point)); // true /* p instanceof Object */ console.log(isInstanceOf(p, Object)); // true ,  Object     (Point.__proto__ === Object.prototype) /* p instanceof Array */ console.log(isInstanceOf(p, Array)); // false ,  Array     

このプロパティ


これは大きな間違いです。

多くの人は、プログラミング言語のthisキーワードがオブジェクト指向プログラミングに密接に関連しているという事実に慣れています。つまり、コンストラクターによって生成された現在のオブジェクトを示します。 ECMAScriptでは、これは生成されたオブジェクトの定義だけに限定されません。
JavaScriptでは、これは呼び出しの形式で呼び出し元によって決定されます。 これに含まれるものを決定するルール(簡単な方法で説明します):
1.メソッドが直接呼び出された場合( new, call, apply, bind, with, try catch )、これはメソッド名の左側のポイントの前にあるオブジェクトになります。
2.ポイントがない(関数が直接呼び出される)場合、これは、環境および「use strict」に応じて、undefined、null、またはwindow(global)に設定されます。
3.式がリンクではなく値である場合、段落2が適用されます

例:
 var foo = { bar: function () { console.log(this); } }; var bar = foo.bar; bar(); // this === global (2) foo.bar(); // this === foo (1) (foo.bar)(); // this === foo     (1) //       -  (foo.bar = foo.bar)(); // this === global (3) (false || foo.bar)(); // this === global (3) (foo.bar, foo.bar)(); // this === global (3) function foo() { function bar() { console.log(this); } bar(); // this === global (2) } 

getProperty(p, 'print').call(p)例を思い出してくださいgetProperty(p, 'print').call(p)理由は、このルールのために手動でこれを指定したからです。 それ以外の場合、印刷機能はこれとして受け取ります-ウィンドウ。

これらの演算子とメソッドは、 this: new, call, apply, bind, with, try catch値を制御することができますthis: new, call, apply, bind, with, try catch (これらはすべて多かれ少なかれ明確で、影響はありません)。

これについての詳細:
ECMA-262-3の微妙さ。 パート3:これ

未定義、null、void


null-null、空、存在しないリンクを表すプリミティブ値
undefined-各変数がデフォルトで受け取るプリミティブ値(変数に値がない場合)
voidは、式を実行し、常に未定義を返す演算子です(つまり、呼び出し時に角かっこは必要ありません)

おわりに


1. JavaScriptにはクラスがありません-コンストラクターがあります
2.プロトタイプチェーン-JavaScriptのすべての継承のベース
3.オブジェクトのプロパティは、プロトタイプチェーンを使用して取得されます。
4. __proto__は、プロトタイプコンストラクター(プロトタイプ)への参照です。
5. new演算子は、F.prototypeを参照する単一のプロパティ__proto__で空のオブジェクトを作成し、これが以前に作成されたオブジェクトであるコンストラクターFを実行し、オブジェクトを返します
6. instanceof演算子は、指定されたオブジェクトがObjectsConstoructorコンストラクターによって生成されたかどうかをチェックしません;その判定のために、プロトタイプチェーンを使用します
7. JavaScriptでは、これの値は呼び出しの形式で呼び出し元によって決定されます
8. voidは演算子であり、関数ではありません。 未定義、null-プリミティブ値

重要一部のJavaScript実装では、 __ proto__を直接変更することはできません;さらに、このプロパティは標準ではなく、すでに古くなっています。 プロトタイプへの参照を取得するには、 Object.getPrototypeOfを使用します 。 この記事では、ECMAScriptの「内部」を示すために(__proto__)使用しました。

記事は材料を使用しました


Dmitry Soshinkov dsCodeの ブログの記事
プロトタイプ継承の実際の仕組み (Christopher Chedeau)

Source: https://habr.com/ru/post/J120193/


All Articles