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は文字列の連結、インターセプション、置換、位置検索操作を実装しています

MySQL 文字列の連結、インターセプト、置換、および検索位置。よく使用される文字列関数:関数例示す...

MySQL 8.0.22 のインストールと設定のグラフィックチュートリアル

MySQL8.0.22のインストールと設定(超詳細)参考までに、具体的な内容は次のとおりです。みなさ...

MYSQL における char と varchar の違い

CHAR 型と VARCHAR 型は似ていますが、主に格納場所、末尾のスペース、取得方法が異なります...

MySQL のテーブル内のレコード数を制限する方法

目次1. トリガーソリューション2. パーティションテーブルソリューション3. 一般的な表領域ソリュ...

Linux CentOS でスケジュールされたバックアップ タスクを設定する方法

実装準備 # ファイルパスをバックアップする必要があります: /opt/apollo/logs/ac...

分散監視システムZabbixはSNMPとJMXチャネルを使用してデータを収集します

前回の記事では、Zabbix のパッシブ、アクティブ、Web 監視に関するトピックについて学習しまし...

重複リクエストを削除するAxiosのソリューションについての簡単な説明

目次1. 重複したリクエストをキャンセルする2. すべてのリクエストをクリーンアップするこのソリュー...

この記事はJavaScriptの変数とデータ型を理解するのに役立ちます

目次序文:親切なヒント:変数1. 免責事項2. 譲渡3. 2つの小さな文法上の詳細変数の命名規則なぜ...

GNU Parallelの具体的な使用法

それは何ですか? GNU Parallel は、1 台以上のコンピュータでコンピューティング タスク...

Dockerfile を使用して Node.js サービスをデプロイする方法

Dockerfileを初期化するプロジェクトの名前が express であると仮定して、expres...

Vue で Excel ストリーム ファイルをダウンロードし、ダウンロード ファイル名を設定する方法

目次概要1. URL経由でダウンロード2. aタグのダウンロード属性とblobコンストラクタを組み合...

HTML 言語百科事典

123WordPress.com-HTML noscriptオブジェクトolオプションPパラントプレ...

HTML でよく使用されるエスケープ文字の概要

HTML でよく使用されるエスケープ文字をまとめると次のようになります。 &nbsp; 改行...

MySQL自動シャットダウン問題への対処の実践記録

最近、あるプロジェクトを手伝ったのですが、MySQL マシンがしばらくすると自動的に停止し続けました...

JS 開発効率を上げる4つの超実践的なヒント

目次1. 短絡判定2. オプション連鎖演算子 (?) 3. ヌル合体演算子 (??) 4. 終了関数...