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

推薦する

VMware kali仮想マシン環境の設定方法

1|0 カーネルをコンパイルする(1)uname -rコマンドを実行してカーネルバージョンを表示しま...

HTML ページ スタイルの !-- -- の機能は何ですか?

主に低バージョンのブラウザ向け<!-- --> は HTML コメント タグです。上位バ...

Dockerコンテナ内で2つのプロセスを開始するときのDockerfile実装コード

最近、cronスケジュールタスク用のdockerを作りたいと思っており、Dockerfileで次のよ...

Nginx 構成 クロスドメイン リクエスト Access-Control-Allow-Origin * 詳細な説明

序文403 クロスオリジン エラーが発生しNo 'Access-Control-Allow-...

Webpack3+React16コード分割の実装

プロジェクトの背景最近、webpackのバージョンが古いプロジェクトがあります。 リーダー層では今の...

iframeを透明にするパラメータ

<iframe src="./ads_top_tian.html" all...

docker-machineの使い方の詳しい説明

Docker-machineはDockerが公式に提供しているDocker管理ツールです。これは d...

ドラッグアンドドロップでVueユーザーインターフェースを生成する方法

目次序文1. 技術原理1.1 レイアウト1.2 コンポーネント1.3 ステータス1.4 イベント1....

tomcat デプロイメント プロジェクトの実装と IDEA との統合

目次Tomcat でプロジェクトを展開する 3 つの方法プロジェクトをwebappsディレクトリに直...

MySQL データベースに基づくデータ制約の例と 5 つの整合性制約の紹介

非準拠データがデータベースに入るのを防ぐために、ユーザーがデータを挿入、変更、削除、その他の操作を行...

Windows Server 2008R2、2012、2016、2019 の違い

目次共通バージョンの紹介共通バージョンのダウンロードアドレスとインストール以下に簡単な違いを示します...

Vueプロジェクトでスケルトンスクリーンを使用する方法

現在、アプリケーション開発は基本的にフロントエンドとバックエンドに分離されています。主流のフロントエ...

Linux で pyenv をインストールする方法

前提条件gitをインストールする必要があるインストール手順1. リモートリポジトリからpyenvをク...

jQuery ステップ進行軸プラグインの実装コード

毎日のjQueryプラグイン - ステップ進捗軸 ステップ進捗軸ツール系のサイトでは入門チュートリア...