js のプロトタイプ、プロトタイプ オブジェクト、プロトタイプ チェーンの包括的な分析

js のプロトタイプ、プロトタイプ オブジェクト、プロトタイプ チェーンの包括的な分析

プロトタイプを理解する

作成するすべての関数にはプロトタイプ プロパティがあります。これは、特定の型のすべてのインスタンスで共有できるプロパティとメソッドを格納することを目的としたオブジェクトへのポインターです。次の例を考えてみましょう。

関数Person(){
}
Person.prototype.name = 'ccc'
人物.プロトタイプ.年齢 = 18
Person.prototype.sayName = 関数 (){
 コンソールにログ出力します。
}

var person1 = 新しいPerson()
person1.sayName() // --> ccc

var person2 = 新しいPerson()
person2.sayName() // --> ccc

console.log(person1.sayName === person2.sayName) // --> true

プロトタイプオブジェクトを理解する

上記のコードによれば、次の図がわかります。

次の 3 つの点を理解する必要があります。

  1. 新しい関数を作成すると、特定のルールセットに従って関数のプロトタイプ プロパティが作成され、関数のプロトタイプ オブジェクトを指します。つまり、Person(コンストラクタ)にはPerson.prototypeを指すプロトタイプポインタがある。
  2. デフォルトでは、各プロトタイプ オブジェクトにコンストラクター プロパティが作成されます。このプロパティは、プロトタイプ プロパティが配置されている関数へのポインターです。
  3. 各インスタンスには、コンストラクターのプロトタイプ オブジェクトを指すポインター (内部プロパティ) が内部に存在します。つまり、person1とperson2は両方とも内部プロパティ__proto__を持ちます(ECMAscriptでは、このポインタは[[prototype]]と呼ばれますが、スクリプトで[[prototype]]にアクセスする標準的な方法はありませんが、Firefox、IE、Chromeはすべて__proto__というプロパティをサポートしています)。これはPerson.prototypeを指します。

注: person1 インスタンスと person2 インスタンスとコンストラクターの間には直接的な関係はありません。

前述したように、[[prototype]] はすべての実装でアクセスできるわけではないので、インスタンスとプロトタイプ オブジェクトの間に関係があるかどうかをどのように確認すればよいでしょうか。ここで判断する方法は 2 つあります。

  • プロトタイプオンラインメソッド: isPrototypeOf()、例: console.log(Person.prototype.isPrototypeOf(person1)) // --> true
  • ECMAscript5 で追加された新しいメソッド: Object.getPrototypeOf()。このメソッドは [[prototype]] の値を返します。たとえば: console.log (Object.getPrototypeOf(person1) === Person.prototype) // --> true

インスタンスプロパティとプロトタイププロパティの関係

先ほど、プロトタイプには最初はコンストラクター プロパティのみが含まれており、これも共有されるため、オブジェクト インスタンスを通じてアクセスできることを説明しました。オブジェクトインスタンスを介してプロトタイプに格納された値にアクセスすることはできますが、オブジェクトインスタンスを介してプロトタイプ内の値を上書きすることはできません。インスタンスにプロパティを追加し、そのプロパティの名前がインスタンス プロトタイプのプロパティと同じである場合、そのプロパティはインスタンス上に作成され、プロトタイプのプロパティはマスクされます。次のように:

関数 Person() {}
Person.prototype.name = "ccc";
Person.prototype.age = 18;
Person.prototype.sayName = function() {
 コンソールにログ出力します。
};

var person1 = 新しい Person();
var person2 = 新しい Person();

person1.name = 'www' // person1 に名前属性を追加します person1.sayName() // --> 'www'————'インスタンスから'
person2.sayName() // --> 'ccc'————'プロトタイプから'

console.log(person1.hasOwnProperty('name')) // --> true
console.log(person2.hasOwnProperty('name')) // --> false

delete person1.name // --> person1 に新しく追加された name 属性を削除します person1.sayName() // -->'ccc'————'from prototype'

プロパティがインスタンス プロパティであるかプロトタイプ プロパティであるかをどのように判断するのでしょうか?ここで、hasOwnProperty() メソッドを使用して、プロパティがインスタンス内またはプロトタイプ内に存在するかどうかを検出できます。 (このメソッドはObjectから継承されます)

次の図は、上記の例の実装とプロトタイプの関係をさまざまな状況で詳細に分析したものです。(Personコンストラクタの関係は省略されています)

よりシンプルなプロトタイプ構文

前の例のように、プロパティとメソッドを追加するたびに、常に Person.prototype と入力できるとは限りません。不要な入力を減らすための、より一般的なアプローチは次のとおりです。

関数 Person(){}
Person.プロトタイプ = {
 名前: 'ccc',
 年齢: 18歳
 sayName: 関数 () {
 console.log(この名前)
 }
}

上記のコードでは、Person.prototype をオブジェクトリテラルとして作成された新しいオブジェクトに設定しています。最終結果は同じですが、1 つの例外があります。コンストラクター プロパティは、もはや Person を指していません。前述したように、関数が作成されるたびにそのプロトタイプ オブジェクトが同時に作成され、このオブジェクトは自動的にコンストラクター プロパティを取得します。しかし、使用する新しい構文では、デフォルトのプロトタイプ オブジェクトは基本的に完全に書き換えられるため、コンストラクター プロパティは新しいオブジェクトのコンストラクター プロパティ (Object コンストラクターを指す) になり、Person 関数を指すことはなくなります。この時点では、instanceof 演算子は正しい結果を返すことができますが、オブジェクトの型はコンストラクターを通じて判別できなくなります。次のように:

var person1 = 新しいPerson()
console.log(person1 インスタンスオブオブジェクト) // --> true
console.log(person1 instanceof Person) // --> true
console.log(person1.constructor === Person) // --> false
console.log(person1.constructor === Object) // --> true

ここでは、instanceof 演算子を使用して Object と Person をテストし、やはり true を返します。コンストラクター プロパティは、Person ではなく Object と等しくなります。コンストラクターが本当に重要な場合は、次のように記述できます。

関数 Person(){}
Person.プロトタイプ = {
 コンストラクタ: Person, // --> リセット名: 'ccc',
 年齢: 18歳
 sayName: 関数 () {
 console.log(この名前)
 }
}

しかし、これにより新たな問題が発生します。上記の方法でコンストラクター プロパティをリセットすると、[[Enumerable]] 属性が true に設定されます。デフォルトでは、ネイティブ コンストラクター プロパティは列挙可能ではありません。したがって、ECMAscript5 互換の JavaScript エンジンを使用する場合は、Object.defineProperty() を試すことができます。

関数 Person(){}
Person.コンストラクタ = {
 名前: 'ccc', 
 年齢: 18歳
 sayName: 関数(){
 console.log(この名前)
 }
}
// コンストラクターをリセットします。ECMAscript5 互換ブラウザーにのみ適用されます。Object.defineProperty(Person.constructor, "constructor", {
 列挙可能: false、 
 値: 人
})

プロトタイプのダイナミクス

プロトタイプ内の値を検索するプロセスは単一の検索であるため、プロトタイプ オブジェクトに加えた変更はインスタンスにすぐに反映されます。例えば:

関数 Person(){}
var person1 = 新しいPerson()
Person.prototype.sayHi = function(){
 コンソールログ('こんにちは')
}
person1.こんにちは()

上記のコードでは、まず Person インスタンスを作成して person1 に保存し、次に sayHi() メソッドを Person.prototype に追加します。 person1 は新しいメソッドが追加される前に作成されましたが、このメソッドに引き続きアクセスできます。その理由は、インスタンスとプロトタイプ間の接続が緩いためです。
プロトタイプにはいつでもプロパティとメソッドを追加できますが、それらはインスタンスにすぐに反映されます。しかし、プロトタイプ オブジェクト全体を書き直すと、状況は異なります。次のコードを見てください。

関数 Person(){}
var person1 = 新しいPerson()

Person.プロトタイプ = {
 名前: 'ccc',
 年齢: 18歳
 sayName: 関数(){
 console.log(この名前)
 }
}

person1.sayName() // --> エラー

分析については次の図を参照してください。

コンストラクターが呼び出されると、元のプロトタイプへの [[prototype]] ポインターがインスタンスに追加され、プロトタイプを別のオブジェクトに変更することは、コンストラクターと元のプロトタイプ間の接続を切断することと同じです。覚えておいてください: インスタンス内のポインターはプロトタイプのみを指し、コンストラクターは指しません。

プロトタイプチェーンを理解する

プロトタイプ チェーンは継承を実現するための主な方法です。基本的な考え方は、1 つの参照型に別の参照型のプロパティとメソッドを継承させることです。プロトタイプ チェーンを理解する前に、まずプロトタイプ、プロトタイプ オブジェクト、インスタンスの関係を整理する必要があります。各コンストラクターにはプロトタイプ オブジェクトがあり、プロトタイプ オブジェクトにはコンストラクターへのポインターが含まれ、インスタンスにはプロトタイプ オブジェクトへの内部ポインターが含まれます。プロトタイプ オブジェクトを別の型のインスタンスと同じにするとどうなるでしょうか?当然のことながら、このプロトタイプ オブジェクトには別のプロトタイプへのポインターが含まれます。まずコードを見て、次に画像を見てください。

関数SuperType(){
 this.property = true
}

SuperType.prototype.getSuperValue = 関数(){
 this.propertyを返す
}

関数サブタイプ(){
 this.subProperty = false
}

// スーパータイプを継承する
SubType.prototype = 新しい SuperType()

SubType.prototype.getSubValue = 関数 (){
 this.subPropertyを返す
}

var インスタンス = 新しいサブタイプ()
console.log(instance.getSuperValue()) // --> true

上記のコードは、SuperType と SubType の 2 つの型を定義します。各型には 1 つのプロパティと 1 つのメソッドがあります。

上の図を分析すると、インスタンスは SubType プロトタイプを指し、SubType プロトタイプは SuperType プロトタイプを指しています。 getSuperValue() メソッドはまだ SuperType.prototype にありますが、プロパティは SubType.prototype にあります。これは、property がインスタンス属性であるのに対し、getSuperValue() はプロトタイプ メソッドであるためです。 SubType.prototype は SuperType のインスタンスになったため、プロパティは当然そのインスタンス内に配置されます。また、SubType.prototype の元のコンストラクターが書き換えられたため、instance.constructor が SuperType を指すようになったことにも注意してください。
なぜ true が返されるのでしょうか?
分析: instance.getSuperValue() メソッドを呼び出すと、次の 3 つの検索手順が実行されます。

インスタンスを検索 SubType.prototypeを検索
ここでメソッドが見つかるまで、SuperType.prototype を検索します。プロパティまたはメソッドが見つからない場合、検索プロセスは常にプロトタイプ チェーンの最後まで 1 リンクずつ進み、そこで停止します。

デフォルトのプロトタイプを忘れないでください

すべての参照型はデフォルトで Object を継承し、この継承もプロトタイプ チェーンを通じて実装されることを知っておく必要があります。すべての関数のデフォルトのプロトタイプは Object のインスタンスであるため、デフォルトのプロトタイプには Object.prototype を指す内部ポインターが含まれます。そのため、すべてのカスタム型には toString() メソッドと valueOf() メソッドがあります。したがって、完全なプロトタイプ チェーンは次のようになります。
次の図、subType の内部を見てください。

詳細図:

つまり、SubType は SuperType を継承し、SuperType は Object を継承します。 Instant.toString() を呼び出す場合、実際に呼び出されるメソッドは Object.prototype に格納されているメソッドです。

プロトタイプとインスタンスの関係を決定する

プロトタイプ チェーンが非常に長い場合、プロトタイプとインスタンスの関係を決定する方法は 2 つあります。

instanceof 演算子を使用します。この演算子を使用してインスタンスとプロトタイプ チェーンに表示されるコンストラクターをテストする限り、結果は true を返します。

console.log(instance instanceof Object) // --> true
console.log(インスタンスインスタンスのSuperType) // --> true
console.log(インスタンス instanceof SubType) // --> true

Instantof 判定メソッドに似た isPrototypeOf() メソッドを使用します。プロトタイプがプロトタイプ チェーンに出現する限り、true を返します。

console.log(Object.prototype.isPrototypeOf(instance)) // --> true
console.log(SuperType.prototype.isPrototypeOf(instance)) // --> true
console.log(SubType.prototype.isPrototypeOf(instance)) // --> true

方法を慎重に定義する

サブタイプでは、スーパータイプのメソッドをオーバーライドしたり、スーパータイプに存在しないメソッドを追加したりする必要がある場合があります。ただし、いずれの場合でも、プロトタイプにメソッドを追加するコードは、プロトタイプを置き換えるステートメントの後に配置する必要があります。次のように:

関数SuperType(){
 this.property は true です。
}
SuperType.prototype.getSuperValue = 関数(){
 this.propertyを返す
}

関数サブタイプ(){
 this.subProperty = false;
}

// スーパータイプを継承する
SubType.prototype = 新しい SuperType()

// 新しいメソッドを追加します SubType.prototype.getSubValue = function(){
 this.subPropertyを返す
}
// スーパータイプのメソッドをオーバーライドします SubType.prototype.getSuperValue = function(){
 偽を返す
}

var インスタンス = 新しいサブタイプ()
console.log(instance.getSuperValue()) // --> false
var インスタンスSuper = 新しいスーパータイプ()
console.log(instanceSuper.getSuperValue()) // -> true

上記のコードでは、最初のメソッド getSubValue() が SubType に追加されています。 2 番目のメソッド getSuperValue() はプロトタイプ チェーンに既に存在するメソッドですが、このメソッドをオーバーライドすると元のメソッドがマスクされます。つまり、SubType のインスタンスを介して getSuperValue() が呼び出されると、再定義されたメソッドが呼び出されますが、SuperType のインスタンスを介して getSuperValue() が呼び出されると、元のメソッドが引き続き呼び出されます。もう 1 つのポイントは、継承がプロトタイプ チェーンを通じて実装されている場合、プロトタイプ チェーンが書き換えられ、プロトタイプ チェーンが切断されるため、オブジェクト変数を使用してプロトタイプ メソッドを作成することはできないということです。

プロトタイプチェーンの問題

継承がプロトタイプを通じて実装されると、プロトタイプは実際には別の型のインスタンスになるため、元のインスタンスのプロパティが現在のプロトタイプのプロパティになり、プロパティが共有されるようになります。次のコードを見てください。

関数SuperType(){
 this.colors = ['白', '青']
}

関数サブタイプ(){
}

// スーパータイプを継承する
SubType.prototype = 新しい SuperType()
var インスタンス1 = 新しいサブタイプ()
インスタンス1.colors.push('赤')

var インスタンス2 = 新しいサブタイプ()
console.log(instance1.colors) // -->["白", "青", "赤"]
console.log(instance2.colors) // -->["白", "青", "赤"]

サブタイプのインスタンスを作成する場合、スーパータイプのコンストラクターにパラメーターを渡すことはできません。実際、すべてのオブジェクト インスタンスに影響を与えずに、スーパータイプ コンストラクターにパラメーターを渡す方法は存在しません。したがって、実際にはプロトタイプ チェーンが単独で使用されることはほとんどありません。

上記は、js のプロトタイプ、プロトタイプ オブジェクト、プロトタイプ チェーンの図の詳細な内容です。js のプロトタイプ、プロトタイプ オブジェクト、プロトタイプ チェーンの詳細については、123WORDPRESS.COM の他の関連記事に注目してください。

以下もご興味があるかもしれません:
  • JavaScript のプロトタイプとプロトタイプチェーンの詳細な説明
  • JavaScriptプロトタイプチェーンの詳細な説明
  • JavaScript プロトタイプとプロトタイプチェーンの詳細
  • JavaScriptプロトタイプとプロトタイプチェーンを徹底的に理解する
  • JavaScript プロトタイプとプロトタイプチェーンの深い理解

<<:  Windows Server 2008 R2 リモート デスクトップのポート 3389 を変更する方法

>>:  MySQL が innobackupex を使用して接続サーバーをバックアップできない場合の解決策

推薦する

Docker-compose を使用して ELK クラスターを構築する方法

すべてのオーケストレーション ファイルと構成ファイルは、私の Github からアクセスできます。構...

XHTML: フレーム構造タグ

フレーム構造タグ <frameset></frameset>フレームを使用す...

vue-nuxt ログイン認証の実装

目次導入リンク始めるコードを読み進めてくださいプロキシ設定傍受を要求する異なるプレフィックスを持つイ...

MySQL インデックス データ構造の詳細な分析

目次概要インデックスデータ構造バイナリツリー赤黒木BツリーB+ツリーハッシュ索引InnoDB インデ...

HTML でフォームを中央揃えにする

以前、写真が与えられ、その写真スタイルに基づいてフォームを作成するという課題に遭遇しました。しかし、...

Tomcat が設定ファイルを外部に配置するためのソリューション

質問通常の開発では、プロジェクトを Tomcat にデプロイする場合、プロジェクトを war パッケ...

CSS でテキストカラーグラデーションを実装する 3 つの方法

Web フロントエンド開発のプロセスでは、UI デザイナーはグラデーション テキストを使用したデザイ...

Nginxポーリングアルゴリズムの基本的な実装方法の詳細な説明

ポーリングアルゴリズムの紹介多くの人が職場で nginx を使用しており、その設定に精通しています。...

JavaScript 非同期プログラミングにおける Promise の初期の使用法の詳細な説明

1. 概要Promise オブジェクトは、ES6 で提案された非同期プログラミングの仕様です。非同期...

シェルで文字列内のスペースや指定された文字を削除する方法

インターネット上には、正しい方法であっても、使用しても正しい結果が得られない方法が数多くあります。正...

MySqlのインストールとログインの詳細な説明

LinuxにMySQLがすでにインストールされているかどうかを確認する sudo service m...

vue+springbootでログイン認証コードを実現

この記事では、ログイン認証コードを実装するためのvue+springbootの具体的なコードを例とし...

Docker tomcatのメモリサイズを設定する方法

Docker に Tomcat をインストールする場合、大きなファイルをダウンロードするときなど、場...

Windows での Apache+Tomcat7 負荷分散構成方法の詳細な説明

準備Windows Server 2008 R2 Enterprise (2.40GH、8GB、64...