JavaScriptのプロトタイプオブジェクトを徹底的に理解しましょう

JavaScriptのプロトタイプオブジェクトを徹底的に理解しましょう

1. プロトタイプとは何ですか?

プロトタイプは JavaScript における継承の基礎であり、JavaScript の継承はプロトタイプ継承に基づいています。

1.1 関数プロトタイプオブジェクト

JavaScript では、関数 A を作成すると (つまり、関数を宣言すると)、ブラウザはメモリ内にオブジェクト B を作成し、各関数にはデフォルトでこのオブジェクトを指すプロパティ prototype が設定されます (つまり、prototype プロパティの値はこのオブジェクトです)。このオブジェクト B は関数 A のプロトタイプ オブジェクト、つまり単に関数のプロトタイプです。デフォルトでは、プロトタイプ オブジェクト B には関数 A を指すプロパティ コンストラクターがあります (つまり、コンストラクター プロパティの値は関数 A です)。

次のコードを見てください。

<本文>
    <script type="text/javascript">
    	/*
    		関数を宣言すると、デフォルトで prototype と呼ばれるプロパティが設定されます。そして、ブラウザは特定のルールに従ってオブジェクトを自動的に作成します。このオブジェクトはこの関数のプロトタイプ オブジェクトであり、prototype プロパティはこのプロトタイプ オブジェクトを指します。このプロトタイプオブジェクトには、この関数を実行するコンストラクタと呼ばれるプロパティがあります。			
			注: デフォルトでは、プロトタイプ オブジェクトにはコンストラクターという 1 つの属性のみがあります。その他は Object から継承されるため、今のところ考慮する必要はありません。
		*/
	    関数Person(){
	    	
	    }	    
    </スクリプト>
</本文>

次の図は、関数を宣言した後に何が起こるかを示しています。

1.2 コンストラクタを使用したオブジェクトの作成

関数がコンストラクターとして使用され (理論上は、どの関数もコンストラクターとして使用できます)、new を使用してオブジェクトが作成されると、オブジェクトには、コンストラクターのプロトタイプ オブジェクトを指すデフォルトの非表示プロパティが設定されます。 この非表示のプロパティは通常 [[prototype]] で表されますが、このプロパティに直接アクセスすることはできません。

次のコードを見てください。

<本文>
    <script type="text/javascript">
	    関数Person(){
	    	
	    }	
        /*
        	コンストラクターを使用してオブジェクトが作成されると、非表示のプロパティ [[prototype]] がオブジェクトに自動的に追加され、このプロパティはコンストラクターのプロトタイプ オブジェクトを指します。
        */
      	var p1 = 新しい Person();
    </スクリプト>
</本文>

次の図をご覧ください。

例:

1. 上の図からわかるように、Person コンストラクターは p1 オブジェクトの作成に使用されますが、オブジェクトの作成後、p1 オブジェクトは実際には Person コンストラクターとは何の関係もありません。p1 オブジェクトの [[ prototype ]] プロパティは、Person コンストラクターのプロトタイプ オブジェクトを指します。

2. new Person() を使用して複数のオブジェクトを作成すると、すべてのオブジェクトが同時に Person コンストラクター関数のプロトタイプ オブジェクトを指すようになります。

3. このプロトタイプ オブジェクトにプロパティとメソッドを手動で追加すると、オブジェクト p1、p2、p3... はプロトタイプに追加されたプロパティとメソッドを共有するようになります。

4. p1 内の属性名にアクセスすると、それが p1 オブジェクト内に見つかった場合は直接返されます。 p1 オブジェクト内にオブジェクトが見つからない場合は、p1 オブジェクトの [[prototype]] プロパティが指すプロトタイプ オブジェクト内を直接検索し、見つかった場合は返します。 (プロトタイプ内に見つからない場合は、プロトタイプのプロトタイプ、つまりプロトタイプ チェーンを探し続けます。これについては後で説明します)。

5. p1 オブジェクトを通じて属性名が追加された場合、プロトタイプ内の属性名は p1 オブジェクトに対して保護されます。 つまり、p1 のプロトタイプのプロパティ名にアクセスする方法はありません。

6. p1 オブジェクトはプロトタイプ内の属性名の値を読み取ることしかできず、プロトタイプ内の属性名の値を変更することはできません。 p1.name = "李四"; はプロトタイプの値を変更しませんが、p1 オブジェクトにプロパティ名を追加します。

次のコードを見てください。

<本文>
    <script type="text/javascript">
	    関数Person(){    	
	    }
      	// Person.prototype を使用するとプロトタイプ オブジェクトに直接アクセスできます // Person 関数のプロトタイプ オブジェクトにプロパティ名を追加し、その値は "Zhang San" になります
	    Person.prototype.name = "張三";
	    Person.prototype.age = 20;

	   	var p1 = 新しい Person();
	   	/*
	   		p1 オブジェクトの name プロパティにアクセスします。p1 オブジェクトに name プロパティを明示的に追加していませんが、p1 の [[prototype]] プロパティが指すプロトタイプには name プロパティがあるため、ここで name プロパティにアクセスできます。
	   		それは価値があります。
	   		注意: 現時点では、p1 で削除されたオブジェクトのみを削除できるため、name 属性は p1 オブジェクトを通じて削除できません。
	   	*/
	   	alert(p1.name); // 張三 var p2 = new Person();
	   	alert(p2.name); // Zhang San はプロトタイプから両方を見つけたので、同じです。

	   	アラート(p1.name === p2.name); // 真

	   	// プロトタイプの値は変更できないため、このメソッドは新しい属性名を p1 に直接追加し、その後、プロトタイプの属性は p1 ではアクセスできなくなります。
	   	p1.name = "李斯";
	   	アラート("p1: " + p1.name);
	   	// p2 には name 属性がないため、p2 はプロトタイプ内の属性にアクセスします。	
	   	alert("p2:" + p2.name); // 張三</script>
</本文>

2. プロトタイプに関連するいくつかのプロパティとメソッド

2.1 プロトタイププロパティ

​ prototype はコンストラクター関数内に存在し (実際、どの関数にも存在しますが、コンストラクター関数でない場合は prototype を気にする必要はありません)、このコンストラクター関数の prototype オブジェクトを指します。

前の図を参照してください。

2.2 コンストラクタプロパティ

コンストラクタプロパティはプロトタイプオブジェクトに存在し、コンストラクタを指します。

次のコードを見てください。

<script type="text/javascript">
	関数Person(){
	}
	alert(Person.prototype.constructor === Person); // 真
	var p1 = 新しい Person();
  	//instanceof 演算子を使用してオブジェクトの型を決定します。  
  	//typeof は通常、単純な型と関数を取得するために使用されます。参照型では通常、instanceof が使用されます。これは、typeof が参照型に対して常に object を返すためです。
	alert(p1 instanceof Person); // true
</スクリプト>

必要に応じて、Person.prototype プロパティを Person のプロトタイプ オブジェクトとして使用して新しいオブジェクトを指定できます。

しかし、この時点で問題が発生します。新しいオブジェクトのコンストラクター プロパティは、Person コンストラクターを指していません。

次のコードを見てください。

<script type="text/javascript">
	関数Person(){
		
	}
	// オブジェクトリテラルを Person のプロトタイプに直接割り当てます。すると、このオブジェクトのコンストラクタプロパティはPerson関数を指しなくなります。Person.prototype = {
		名前:"志玲",
		年齢:20
	};
	var p1 = 新しい Person();
	alert(p1.name); // 志玲 alert(p1 instanceof Person); // true
	alert(Person.prototype.constructor === Person); //false
  	// コンストラクターが重要な場合は、次のような行を Person.prototype に追加する必要があります。
  	/*
  	Person.プロトタイプ = {
      	コンストラクター: Person // コンストラクターをPerson関数にリダイレクトします }
  	*/
</スクリプト>

2.3 __proto__ 属性 (注意: 両側に 2 つのアンダースコアがあります)

コンストラクターを使用して新しいオブジェクトを作成すると、デフォルトではオブジェクトにアクセスできないプロパティ [[prototype]] が作成されます。このプロパティは、コンストラクターのプロトタイプ オブジェクトを指します。

ただし、一部のブラウザでもこのプロパティ [[prototype]] にアクセスできます (Chrome および Firefox。Internet Explorer ではサポートされていません)。アクセス方法: p1.__proto__

ただし、不注意な操作によってこのオブジェクトの継承プロトタイプ チェーンが変更される可能性があるため、開発者はこの方法でアクセスしないようにする必要があります。

<script type="text/javascript">
	関数Person(){
		
	}
	// オブジェクトリテラルを Person のプロトタイプに直接割り当てます。すると、このオブジェクトのコンストラクタプロパティはPerson関数を指しなくなります。Person.prototype = {
		コンストラクタ: Person,
		名前:"志玲",
		年齢:20
	};
	var p1 = 新しい Person();

	alert(p1.__proto__ === Person.prototype); //true
	
</スクリプト>

2.4 hasOwnProperty() メソッド

ご存知のとおり、オブジェクトのプロパティにアクセスすると、そのプロパティはオブジェクト自体から取得される場合もあれば、このオブジェクトの [[prototype]] プロパティによって指されるプロトタイプから取得される場合もあります。

では、この物体の出所をどうやって特定するのでしょうか?

​ hasOwnProperty メソッドは、プロパティがオブジェクト自体から取得されるかどうかを判断できます。

<script type="text/javascript">
	関数Person(){
		
	}
	Person.prototype.name = "志玲";
	var p1 = 新しい Person();
	p1.sex = "女性";
  	//性別属性はp1属性に直接追加されるため、trueになります
	alert("sex プロパティはオブジェクト自体に属します: " + p1.hasOwnProperty("sex"));
  	// name属性はプロトタイプに追加されているのでfalseです
	alert("name プロパティはオブジェクト自体に属します: " + p1.hasOwnProperty("name"));
  	// age属性は存在しないので、これもfalseです
	alert("age プロパティはオブジェクト自体に存在します: " + p1.hasOwnProperty("age"));
	
</スクリプト>

したがって、hasOwnProperty メソッドを使用して、オブジェクト自体にオブジェクトが追加されているかどうかを判断できますが、プロパティが存在しない可能性があるため、プロトタイプに存在するかどうかを判断することはできません。

つまり、プロトタイプ内のプロパティと存在しないプロパティは false を返します。

プロトタイプにプロパティが存在するかどうかを判断するにはどうすればよいでしょうか?

2.5 オペレーター

in 演算子は、このオブジェクトにプロパティが存在するかどうかを判断するために使用されます。しかし、このプロパティを検索するときは、オブジェクト自体を検索します。オブジェクトが見つからない場合は、プロトタイプを検索します。つまり、プロパティがオブジェクトまたはプロトタイプのいずれかに存在する限り、true を返します。

<script type="text/javascript">
	関数Person(){
		
	}
	Person.prototype.name = "志玲";
	var p1 = 新しい Person();
	p1.sex = "女性";
	alert("sex" in p1); // オブジェクト自体が追加されているのでtrue
	alert("name" in p1); //プロトタイプに存在するのでtrue
	alert("age" in p1); //オブジェクトまたはプロトタイプに存在しないため、false
	
</スクリプト>

前の質問に戻ると、プロトタイプにプロパティが存在するかどうかを判断すると、次のようになります。

プロパティが存在するが、オブジェクト自体には存在しない場合は、プロトタイプに存在する必要があります。

<script type="text/javascript">
	関数Person(){
	}
	Person.prototype.name = "志玲";
	var p1 = 新しい Person();
	p1.sex = "女性";
	
	//プロトタイプの位置を決定する関数を定義する function propertyLocation(obj, prop){
		if(!(prop in obj)){
			alert(prop + "プロパティが存在しません");
		}そうでない場合、(obj.hasOwnProperty(prop)){
			alert(prop + "オブジェクト内にプロパティが存在します");
		}それ以外 {
			alert(prop + "オブジェクトがプロトタイプに存在します");
		}
	}
	プロパティの場所(p1、「年齢」)。
	プロパティの場所(p1、「名前」)。
	プロパティの場所(p1、「性別」)。
</スクリプト

3. プロトタイプモデルとコンストラクタモデルを組み合わせてオブジェクトを作成する

3.1 プロトタイプモデルオブジェクト作成の欠陥

プロトタイプ内のすべてのプロパティは共有されます。つまり、同じコンストラクター関数で作成されたオブジェクトがプロトタイプのプロパティにアクセスする場合、全員が同じオブジェクトにアクセスしていることになります。1 つのオブジェクトがプロトタイプのプロパティを変更すると、すべてのオブジェクトに反映されます。

しかし、実際に使用する際には、各オブジェクトのプロパティは一般的に異なります。張三の名前は張三、李四の名前は李四です。

**ただし、この共有機能はメソッド(プロパティ値は関数のプロパティ)に非常に適しています。 **すべてのオブジェクト共有方法が最適です。この機能は C# および Java でネイティブです。

3.2 コンストラクタモデルを使用してオブジェクトを作成する際の欠陥

各オブジェクトには、コンストラクターで追加されたプロパティとメソッドの独自のコピーがあり、それらは共有されません。この機能はプロパティには適していますが、メソッドには適していません。すべてのオブジェクトに対して、メソッドのコピーが 1 つあれば十分なので、人ごとに 1 つのコピーを持つ必要はなく、メモリの無駄やパフォーマンスの低下を招くことはありません。

<script type="text/javascript">
	関数Person() {
	    this.name = "李斯";
	    年齢は20歳です。
	    this.eat = 関数() {
	        alert("食べ終える");
	    }
	}
	var p1 = 新しい Person();
	var p2 = 新しい Person();
	//各オブジェクトには異なるメソッドがあります alert(p1.eat === p2.eat); //誤り
</スクリプト>

これを解決するには、次の方法を使用できます。

<script type="text/javascript">
	関数Person() {
	    this.name = "李斯";
	    年齢は20歳です。
	    this.eat = 食べる;
	}
  	関数 eat() {
	    alert("食べ終わりました");
    }
	var p1 = 新しい Person();
	var p2 = 新しい Person();
	//eat属性に同じ関数が割り当てられているため、trueになります
	アラート(p1.eat === p2.eat); //true
</スクリプト>

しかし、上記のソリューションには、カプセル化が不十分であるという致命的な欠陥があります。オブジェクト指向プログラミングを使用する目的の 1 つは、コードをカプセル化することです。このとき、パフォーマンスのために、オブジェクトからコードを抽出する必要があり、これは反人間的な設計です。

3.3 組み合わせモードを使用して上記の2つの欠陥を解決する

プロトタイプ パターンはメソッドのカプセル化に適しており、コンストラクター パターンはプロパティのカプセル化に適しています。2 つのパターンの利点を組み合わせたものが、複合パターンです。

<script type="text/javascript">
	//コンストラクタ関数内にプロパティをカプセル化する Person(name, age) {
	    this.name = 名前;
	    this.age = 年齢;
	}
	// メソッドをプロトタイプオブジェクトにカプセル化します。Person.prototype.eat = function (food) {
		alert(this.name + "爱吃" + food);
	}
	Person.prototype.play = function (playName) {
		alert(this.name + "爱玩" + playName);
	}
    
	var p1 = new Person("Li Si", 20);
	var p2 = new Person("张三", 30);
	p1.eat("リンゴ");
	p2.eat("バナナ");
	p1.play("志玲");
	p2.play("馮傑");
</スクリプト>

4. ダイナミックプロトタイプモードでオブジェクトを作成する

​ 前述の組み合わせモードも完璧ではなく、完璧ではないと感じる点が 1 つあります。コンストラクターとプロトタイプを別々に記述すると、常に不快感を覚えます。コンストラクターとプロトタイプを一緒にカプセル化して、動的プロトタイプ モードを実現する方法を見つける必要があります。

動的プロトタイプ モードでは、コンストラクター内のすべてのプロパティとメソッドがカプセル化され、コンストラクターとプロトタイプの両方を使用する利点を維持しながら、必要な場合にのみコンストラクター内でプロトタイプが初期化されます。

次のコードを見てください。

<script type="text/javascript">
	//コンストラクタの内部カプセル化プロパティ function Person(name, age) {
		//各オブジェクトは独自のプロパティを追加します。this.name = name;
	    this.age = 年齢;
	    /*
	    	属性 this.eat が関数かどうかを判断します。関数でない場合は、オブジェクトが初めて作成されたことが証明されます。
	    	次に、この機能をプロトタイプに追加します。
	    	関数の場合は、このメソッドがプロトタイプにすでに存在するため、追加する必要はありません。
	    	完璧!パフォーマンスとコードのカプセル化の問題を完全に解決します。
	    */
	    if(typeof this.eat !== "function"){
	    	Person.prototype.eat = 関数 () {
	    		alert(this.name + "食べること");
	    	}
	    }
	}
	var p1 = new Person("志玲", 40);
	p1.食べる();	
</スクリプト>

JavaScript のプロトタイプオブジェクトを徹底的に理解する方法についての記事はこれで終わりです。js プロトタイプオブジェクトの理解についてさらに詳しく知りたい方は、123WORDPRESS.COM の過去の記事を検索するか、以下の関連記事を引き続きご覧ください。今後とも 123WORDPRESS.COM をよろしくお願いいたします。

以下もご興味があるかもしれません:
  • JavaScriptは組み込みオブジェクトのプロトタイプメソッド実装を追加します
  • js のプロトタイプ、プロトタイプ オブジェクト、プロトタイプ チェーンの包括的な分析
  • js でプロトタイプオブジェクトを使用する際の注意点
  • JavaScript インスタンス オブジェクトでプロトタイプ メソッドをオーバーライドする方法の詳細

<<:  AES_ENCRYPT() と AES_DECRYPT() を使用して MySQL を暗号化および復号化する正しい方法の例

>>:  2つのLinuxサーバー間でファイルとフォルダを転送する手順

推薦する

MySQLクエリ文を書き換える3つの戦略

目次複雑なクエリとステップバイステップのクエリクエリステートメントを分割する共同クエリの分解問題のあ...

mysqlとnavicat間の接続を確立する際の1251エラーを解決する

コンピュータを再インストールし、最新バージョンのMySQLデータベースをインストールしました。その結...

MySQL クイックデータ比較テクニック

MySQL の運用と保守において、R&D の同僚が 2 つの異なるインスタンスのデータを比較...

MySQL 5.7.19 Winx64 ZIP アーカイブのインストールと使用に関する問題の概要

今日はMySQLのインストール方法を学びましたが、その過程でいくつか問題が発生しました。関連記事をい...

SQL グループ化により重複を削除し、他のフィールドで並べ替える

必要:あるフィールドの同一項目を結合し、別の時間フィールドで並べ替えます。例:初めに テーブルから都...

Spring Cloud での Docker デプロイメントに jib を使用する詳細な手順

ジブの紹介Jib は Google が開発した、Java アプリケーションの Docker および ...

deepin apt コマンドを使用して最新バージョンの docker をインストールする方法

ステップ1: Ubuntuソースを追加するルートに切り替える suルートソフトウェアソースファイルの...

MySQL sql_mode の分析と設定の説明

昨夜、MySQL データベースにデータセットを挿入したときにエラーが発生しました。データベースは容赦...

Windows での MySQL インストール チュートリアル (画像とテキスト付き)

MySQL インストール手順 MySQL は、スウェーデンの MySQL AB によって開発された...

MySQLデータのエクスポートとインポートに関する知識ポイントの簡単な分析

多くの場合、ローカル データベースのデータをエクスポートしたり、他のデータベースからデータをインポー...

Nginx リバース プロキシ学習例チュートリアル

目次1. リバースプロキシの準備1. LinuxシステムにTomcatをインストールする2. Tom...

MySQL 8.0 のメモリ消費の詳細な分析

目次1. innodb_buffer_pool_size 2. innodb_log_buffer_...

Vueはvue-quill-editorリッチテキストエディタを使用し、画像をサーバーにアップロードします。

目次1. 準備2. グローバルコンポーネント quill-editor を定義する1. テンプレート...