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 を使用して接続サーバーをバックアップできない場合の解決策

推薦する

JavaScript の基礎: スコープ

目次範囲グローバルスコープ関数のスコープもし、スイッチ、のために、その間ブロックスコープスコープチェ...

JavaScript で知らない Object.entries の使い方

目次序文1. 共通オブジェクトを反復処理するには for...of を使用します2. 通常のオブジェ...

CSS ポインターイベント属性の使用に関する詳細な説明

フロントエンド開発では、ユーザーと直接やり取りするため、ユーザーが操作がスムーズで快適だと感じ、ネイ...

Nginx と GeoIP モジュールを使用して IP の地域情報を読み取る方法

LinuxにGeoIPをインストールする yum で nginx-module-geoip をインス...

JavaScript を使用して動的な QQ 登録ページを作成する

目次1. はじめに1. 基本レイアウト2. 写真を自動的に切り替える3. コンテンツを追加する4. ...

SQL で行の最大値または最小値を取得する方法

元データと対象データSQL文を実装する(最大) 選択 店、 月、 最大(dz,fz,sp) が最大値...

MySQL 4.1/5.0/5.1/5.5/5.6の主な違い

バージョン間でのコマンドの違い: innodb ステータスを表示\G mysql-5.1 エンジン ...

MySQL マスタースレーブレプリケーションの実践の詳細説明 - GTID ベースのレプリケーション

GTIDベースのレプリケーション導入GTID ベースのレプリケーションは、MySQL 5.6 以降に...

デザイン協会: なぜ間違った場所を探したのですか?

数日前、バスで仕事に行きました。バスのカードリーダーの実際の使用シーンを実際に見て、カードリーダーの...

Linux での Nginx アンチホットリンクと最適化の実装コード

バージョン番号を非表示バージョン番号は非表示になっていません。セキュリティを強化するために、バージョ...

Centos8 (最小インストール) Python3.8+pip のインストール方法に関するチュートリアル

Python8のインストールを最小化した後、Python3.8.1をインストールしました。オンライン...

Dockerを使用してOracle_11gをインストールする方法

DockerでOracle_11gをインストールする1. oracle_11gイメージを取得する d...

ローカルの Windows リモート デスクトップから Alibaba Cloud Ubuntu 16.04 サーバーに接続する方法

ローカル Windows リモート デスクトップが Alibaba Cloud Ubuntu 16....

シンプルで簡単なJavaScript開発のためのSvelte実装原理の詳細な説明

目次デモ1フラグメントの作成スヴェルトコンポーネント状態を変更できるデモSvelte は長い間存在し...

VMware仮想マシンを使用してUbuntu 20.04をインストールする完全なチュートリアル

Ubuntu は比較的人気のある Linux デスクトップ システムです。最近、Ubuntu 20....