96395


JavaScriptは _ **クラスをベースとした**_ 他のオブジェクト指向言語と違い、 _ **プロトタイプベースのオブジェクト指向言語**_
と言って、JavaScriptのprototypeを解説している記事がありました。  
[【JavaScript入門】プロトタイプ(prototype)の使い方と継承まとめ！※SAMURAIエンジニアブログ※  
](https://www.sejuku.net/blog/47722)  
各種プログラミング言語を学んでいく過程で、JavaScriptのprototypeで躓きましたが、この文言を見て、どうしてもこだわっておきたい気持ちが強まりました。  
勿論、知り合いのエンジニアからも、その後のES6への移行で、JavaScriptにもクラスが実装されたことから、基本シンタックスシュガーであるクラス表記にてコーディングが行われることも伺っており、早めにクラス表記に対する学習を進めた方が良いということは分かりますが、是非、理解しておきたい内容ですので、質問させてください。

首題の通り、インスタンスはprototypeを持つのか？持たないのか？という点です。

この疑問に合わせて、その後に考察が続く、次のようなソースコードを作成しております。  
■ソースコード■

[code]

    function Fn(name, age) {
        this.name = name;
        this.age = age;
    }
    Fn.prototype.c = function() {
        console.log('c is called');
    }
    const tom = new Fn('Tom', 18);
    // Fn.c(); -- コンストラクター関数に直接cメソッドを実行してもエラー。
    console.log('======Fn.prototype.c();======');
    Fn.prototype.c();
    console.log('======tom.c();======');
    tom.c();
    console.log('★★★★★★tom.__proto__.cメソッド変更★★★★★★');
    tom.__proto__.c = function() {
        console.log('changed c is called');
    }
    console.log('======Fn.prototype.c();cメソッド変更後======');
    Fn.prototype.c();
    console.log('======tom.c();cメソッド変更後======');
    tom.c();
    const jason = new Fn('Jason', 21);
    console.log('======jason.c();cメソッド変更後======');
    jason.c();
    // jason.d(); -- dメソッド追加前に、インスタンスを呼び出してもエラー。
    console.log('★★★★★★tom.__proto__.dメソッド追加★★★★★★')
    tom.__proto__.d = function() {
        console.log('d is called');
    } 
    console.log('======Fn.prototype.c();dメソッド追加後======');
    Fn.prototype.d();
    console.log('======tom.c();dメソッド追加後======');
    tom.d();
    console.log('======jason.d();dメソッド追加後======');
    jason.d();
    console.log('★★★★★★tom.__proto__.cメソッド変更2回目★★★★★★');
    Fn.prototype.c = function() {
        console.log('second changed c is called');
    }
    console.log('======Fn.prototype.c();cメソッド2回目変更後======');
    Fn.prototype.c();
    console.log('======tom.c();cメソッド2回目変更後======');
    tom.c();
    console.log('======jason.c();cメソッド2回目変更後======');
    jason.c();
    
[/code]

実行結果は次のようになります。

■実行結果■

[code]

              ======Fn.prototype.c();======
    main.js:6 c is called
    main.js:12 ======tom.c();======
    main.js:6 c is called
    main.js:14 ★★★★★★tom.__proto__.cメソッド変更★★★★★★
    main.js:18 ======Fn.prototype.c();cメソッド変更後======
    main.js:16 changed c is called                                ←◆1
    main.js:20 ======tom.c();cメソッド変更後======
    main.js:16 changed c is called                                ←◆1
    main.js:23 ======jason.c();cメソッド変更後======
    main.js:16 changed c is called                                ←◆1
    main.js:26 ★★★★★★tom.__proto__.dメソッド追加★★★★★★
    main.js:30 ======Fn.prototype.c();dメソッド追加後======
    main.js:28 d is called                                        ←◆2
    main.js:32 ======tom.c();dメソッド追加後======
    main.js:28 d is called                                        ←◆2
    main.js:34 ======jason.d();dメソッド追加後======
    main.js:28 d is called                                        ←◆2
    main.js:36 ★★★★★★tom.__proto__.cメソッド変更2回目★★★★★★
    main.js:40 ======Fn.prototype.c();cメソッド2回目変更後======
    main.js:38 second changed c is called                         ←◆3
    main.js:42 ======tom.c();cメソッド2回目変更後======
    main.js:38 second changed c is called                         ←◆3
    main.js:44 ======jason.c();cメソッド2回目変更後======
    main.js:38 second changed c is called                         ←◆3
    
[/code]

これを受け、次のようなことが言えます。  
インスタンスの親であるコンストラクター関数のprototypeに追加したメソッドを、そのprototypeのリンクである__proto__経由でインスタンスで変更したところ、親ともう一つの子であるインスタンスにも影響がありました。（【実行結果】◆1）

また、コンストラクター関数のprototypeへのリンクである__proto__を用いてインスタンスでメソッドを追加したところ、親であるコンストラクター関数のprototypeと、もう一つの子であるインスタンスにも、追加したメソッドが有効でした。（【実行結果】◆2）

また、親であるコンストラクター関数のprototypeを通して、メソッドを変更したところ、それはしっかり、子であるインスタンスにも反映されました。（【実行結果】◆3）（念のため確認）

そもそも、私の方で次のような疑問を抱いておりました。

[javascript文法-
インスタンスに対するprototypeの位置づけ](https://ja.stackoverflow.com/questions/95897/javascript%e6%96%87%e6%b3%95-%e3%82%a4%e3%83%b3%e3%82%b9%e3%82%bf%e3%83%b3%e3%82%b9%e3%81%ab%e5%af%be%e3%81%99%e3%82%8bprototype%e3%81%ae%e4%bd%8d%e7%bd%ae%e3%81%a5%e3%81%91)

ここで得られた知見により、ご回答者様から頂いたご回答を受け、非推奨なやり方であることは承知の上、インスタンスのprototypeへのアクセスは、__proto__で行うものという認識で、上記の方法を取りました。

また、そもそも、この方法については「prototypeの影響範囲がプロトタイプチェーンに及ばないオブジェクト単独かどうかに関わる処理内容である」というものなのか？という疑問がありましたが、そうでなく、プロトタイプチェーン全範囲に影響を及ぼすものであるという解釈が出来るものと分かりました。

ここで躓くのが、前出の質問に載せてあるChatGPTからの回答です。  
対象となるソースコードは次のソースコードです。  
■ソースコード■

[code]

    function Fn4(a, b) {
      this.a = a;
      this.b = b;
    }
    
    /* prototypeに引き込みたい関数オブジェクトproto_method */
    function proto_method() {
        console.log("proto method is called");
    }
    
    /* ■コンストラクター関数オブジェクトのインスタンス化■ */
    const fn4 = new Fn4();
    
    /* ★4.コンストラクター関数Fn4のインスタンスfn4のprototypeにメソッドdを設定★ */
    fn4.prototype.d = function() {
       console.log("method d is called");
    }
    
    console.log('-----★method execution trial 4★-----')
    fn4.d();
    
[/code]

■ChatGPT回答■

>
> 1.インスタンスに対してprototypeを使用してメソッドを設定する：fn4.prototype.dというコードでは、fn4はインスタンスであり、インスタンス自体にはprototypeプロパティが存在しません。正しい方法は、コンストラクター関数Fn4のプロトタイプに対してメソッドを設定することです。  
>
> 2.メソッド呼び出しの誤り：fn4.d()というコードは、fn4はインスタンスであり、d()メソッドはプロトタイプに追加されているため、直接インスタンスから呼び出すことはできません。

追って質問： _ **Google
ChromeのConsoleで確認すると、インスタンスにも[[Prototype]]という属性が出力されますが、「インスタンス自体にはprototypeプロパティが存在しません。」とはどういう意味ですか？**_

>
> 申し訳ありません、誤った情報を提供しました。インスタンスも実際にはプロトタイプを持ち、[[Prototype]]と呼ばれる内部プロパティでプロトタイプオブジェクトへのリンクを保持しています。したがって、インスタンス自体にはプロトタイプへのリンクが存在します。
>
>
> [[Prototype]]はJavaScriptの内部的なプロパティであり、通常のコードからは直接アクセスすることができません。しかし、いくつかの環境やツール（たとえば、Google
> ChromeのConsoleなど）では、[[Prototype]]を表示することができる場合があります。
>
>
> 正しい情報としては、インスタンスはプロトタイプを持ち、通常はプロトタイプチェーンを通じてそのプロトタイプのメソッドやプロパティにアクセスできます。インスタンスが直接prototypeプロパティを持っているわけではありませんが、[[Prototype]]を介してプロトタイプにアクセスできる仕組みです。

※尚、ここで言われている、「fn4.d()というコードは、fn4はインスタンスであり、d()メソッドはプロトタイプに追加されているため、直接インスタンスから呼び出すことはできません。」につきましては、__proto__経由で、d()メソッドを追加することで解消されました。

[code]

    function Fn4(a, b) {
        this.a = a;
        this.b = b;
      }
      
      /* prototypeに引き込みたい関数オブジェクトproto_method */
      function proto_method() {
          console.log("proto method is called");
      }
      
      /* ■コンストラクター関数オブジェクトのインスタンス化■ */
      const fn4 = new Fn4();
      
      /* ★4.コンストラクター関数Fn4のインスタンスfn4のprototypeにメソッドdを設定★ */
      fn4.__proto__.d = function() {
         console.log("method d is called");
      }
      
      console.log('-----★method execution trial 4★-----')
      fn4.d();
    
[/code]

ここで、ChatGPTは、  
①インスタンス自体にはprototypeプロパティが存在しません。  
②正しい情報としては、インスタンスはプロトタイプを持ち、  
③インスタンスが直接prototypeプロパティを持っているわけではありません  
と言っています。

そこで、prototypeには、実体とリンクがあるという認識で解釈を行っていましたが、本件の冒頭のソースコードでは、リンクから実体の情報を操作できてしまいました。  
つまり、実体の情報を参照する機能だけのリンクではないと分かりました。

以上のことから、  
コンストラクター関数のインスタンスは、結局プロトタイプを持つのでしょうか？持たないのでしょうか？  
（プロトタイプには、prototypeと__proto__と[[Prototype]]がありますので、ここでは、ChatGPTが言わんとする"プロトタイプ"のことを指したい為、抽象化して"プロトタイプ"としています。）  
参照：[[[prototype]]とprototypeと__proto__について  
](https://teratail.com/questions/43567)

今回の現象については、リンクを通じて、実体の操作も行えたという理屈が成り立つとは考えています。

以上を踏まえまして、明瞭なご回答はありませんでしょうか？

以上、  
宜しくお願い致します。


CC BY-SA 4.0
2023-09-30T10:02:43.253
96390
2023-10-01T05:41:00.093
2023-09-30T14:10:44.350
49404
49404
question
javascript
JavaScript文法 インスタンスはprototypeを持っているんですか？持っていないんですか？


機能としてのプロトタイプと、実装としての`"prototype"`プロパティ、`"__proto__"`プロパティ、[[Protoype]]内部スロットがごちゃ混ぜになっているため、理解できていないのだと思います。きちんと理解するには、これらを別に考える必要があります。

これからの説明は[ECMAScript® 2023 language specification](https://262.ecma-
international.org/14.0/)に基づきます。これはJavaScriptの基盤言語であるECMAScriptの2023/10/1時点での最新の仕様書です。JavaScriptはECMAScriptの言語仕様を基盤に、ブラウザーなどで使用するDOM等のAPIを追加した物であり、全てのJavaScriptエンジンはECMAScriptの仕様に準拠する(または準拠目指す)ように作られています。そのため、ECMAScriptについての言語仕様として解説しますが、JavaScriptでも同じになります。また、この回答での表現と仕様書上の表現の対応表を以下に示します(仕様書上での太字の表現は地の文と厳密に区別されます)。

  * プリミティブ値 ... primitive value
  * オブジェクト ... object
  * コンストラクター ... costructor
  * プロトタイプ ... prototype
  * 関数 ... function
  * プロパティ ... property
  * インスタンス ... instance
  * `"prototype"`プロパティ ... **" prototype"** property
  * `"__proto__"`プロパティ ... **" **proto** "** property
  * [[Protoype]]内部スロット ... [[Prototype]] internal slot
  * `null` ... **null**
  * `nudefined` .. **undefined**

* * *

ECMASciprtの値(データ)はプリミティブ値かオブジェクトのいずれかです。全てのオブジェクトはプロトタイプを持っています。そのプロトタイプは他のオブジェクト、または、`null`
です。コンストラクターを`new`することで作成されるオブジェクトを、そのコンストラクターのインスタンスと呼びます。全てのインスタンスは例外なくオブジェクトです。よって、
**インスタンスも必ずプロトタイプを持っています。**

ここまでは良いですか？ここから、実装の話をします。

オブジェクトがどのようにしてプロトタイプを持つかです。そこで使われるのが[[Protoype]]内部スロットです。これはプロパティではありません。また[[Prototype]]というのも言語内部の表現であって、
_Prototype_
と言う名前の文字列やシンボルがあるというわけでもありません。内部スロットと言っても、通常の方法ではアクセスできないプロパティのようなもので、何らかのプリミティブ値かオブジェクトを示します。[[Protoype]]内部スロットの場合は必ずオブジェクトか`null`であり、これがそのオブジェクトのプロトタイプとなります。

※内部スロットは[[Prototype]]以外にも存在します。

あるオブジェクトに対してある名前(文字列かシンボル)のプロパティを取得([[Get]])するような動作が起きたとき、ECMAScirptは次のような方法で、プロパティを探します。

  1. そのオブジェクトが指定された名前のプロパティを持っている場合は、そのプロパティが示す値(プリミティブ値かオブジェクト)を返し、処理を終了します。そうでなければ、2.へ進みます。
  2. [[Protoype]]内部スロットが確認します。(この内部スロットはどのオブジェクトであっても必ず存在し、他のオブジェクトか`null`のいずれかです。)  
1\.
[[Protoype]]内部スロットが示す先がオブジェクトの場合は、検索対象のオブジェクトを[[Protoype]]内部スロットのオブジェクトに変えて、1.の処理に戻ります。  
2\. [[Protoype]]内部スロットが示す先が`null`の場合は、`undefined`を返して、処理を終了します。

[[Protoype]]内部スロットがオブジェクトの場合は、そのオブジェクトも必ず[[Protoype]]内部スロットを持っています。このように、[[Protoype]]内部スロットが`null`になるまで、その名前のプロパティを探していきます。この仕組みをプロトタイプチェーンと言います。

[[Protoype]]内部スロットはこのプロトタイプチェーンを実現するための実装です。つまり、[[Protoype]]内部スロットが示すオブジェクトそのものが、そのオブジェクトのプロトタイプになります。そして、[[Protoype]]内部スロットはプロパティではないため、その取得や変更には`Object.getPrototypeOf()`関数や`Ojbect.setPrototypeOf()`を使用する必要があります。

次に、コンストラクターを`new`したときに作られるインスタンスではどのようになっているのかについて説明しましょう。インスタンスもオブジェクトであると言いましたので、[[Protoype]]内部スロットを持っています。そして、この[[Protoype]]内部スロットは、自動的に、コンストラクターの`"prototype"`プロパティが示すオブジェクトになります。

コンストラクターの`"prototype"`プロパティとはなんであるかです。`function`キーワードや`new
Funcnion()`等の方法で作成した全ての関数は`"prototype"`プロパティを持ち、同時に作成された空のObject(`{}`)がセットさされます。このオブジェクトには任意のプロパティを追加することができ、別のオブジェクトへ変更することができます。

`"prototype"`プロパティのオブジェクトに関数を追加することで、インスタンスはその名前でその関数にアクセスできるようになります。これが、インスタンスメソッドの仕組みです。`Array`のインスタンスメソッドである`map()`を`Array.prototype.map()`と表現するのは、実際に、`Array`コンストラクターの`"protoype"`プロパティの`"map"`プロパティとしての設定された関数だからです。

また、別のコンストラクターの`"prototype"`プロパティをプロトタイプとして持つ([[Protoype]]内部スロットが別のコンストラクターの`"prototype"`プロパティを示す)オブジェクトに変更することで、コンスタトラクターの継承を実現出来ます。(実際の継承には、親のコンストラクターを関数として呼び出し必要なため、もう少し実装が必要です。)

クラスは、インスタンスメソッドや継承の処理について、上のことを自動的にやってくれる仕組みです。そのため、シンタックスシュガーにすぎないと言われています。ただ、クラスと同じ継承を関数で実装しようとすると、結構複雑なコードを書く必要があります。

ちょっと注意してほしいのは、コンストラクターは関数であり、関数もまたオブジェクトであるため、コンストラクター自身もプロトタイプを持ちます。これが何かというと、`Function`関数の`"prototype"`プロパティになります。これは、コンストラクターの`"prototype"`プロパティとは関係なく、独立して持っています。

最後に、`"__proto__"`プロパティについてです。`"__proto__"`プロパティは歴史的な経緯で残っているプロパティです。現在の仕様書では実装は必須ではなくなったため、`"__proto__"`プロパティがない(特別の意味を持たない)JavaScriptエンジンが存在する可能性もあります。主要なブラウザーのJavaScriptエンジンでも将来のバージョンでは廃止されるかも知れません。

`"__proto__"`プロパティは[[Protoype]]内部スロットを示します。`"__proto__"`プロパティと[[Protoype]]内部スロットは連携しており、どちらかを変えればもう一方も変わります。プロトタイプチェーンの話を[[Protoype]]内部スロットで説明しましたが、`"__proto__"`プロパティでも同じことが言えます。

何故こんなことになっているというと、昔のJavaScriptは今のように仕様をきちんと決まっておらず、各社が勝手にどんどん好きなように実装していくという形でした。プロトタイプチェーンを実現する場合でも、`__proto__`プロパティというただのプロパティを使えば、プロトタイプの仕組みをそのまま使えるし、取得や変更もただのプロパティアクセスになるため、プログラマーにとって直感的です。[こちらの情報](https://zenn.dev/qnighy/articles/1d96f2c0c662f6)によると初期のNetscape
Navigatorで既に実装されていて、IEのJScript(JavaScriptではない)以外ではデファクトスタンダードなっていたようです。ただ、`__proto__`プロパティは単純なプロパティとしてアクセスできるため、プロトタイプ汚染と言った問題が起きるため、仕様書ではレガシーでオプション扱いとなっています。

では、まとめです。

  * 全てのオブジェクトはプロトタイプを持ち、[[Prototype]]内部スロットが示すオブジェクト(`null`の場合もある)として実装されています。
  * インスタンスはオブジェクトでもあるので、プロトタイプを持ち、そのプロトタイプはコンストラクターの`"prototype"`プロパティが示すオブジェクトです。
  * 歴史的な経緯により、JavaScriptエンジンによっては、全てのオブジェクトは`"__proto__"`プロパティ持ち、[[Prototype]]内部スロットと同じオブジェクト(`null`の場合もある)を示します。

* * *

最後に、少しアドバイスです。インターネットにある情報のほとんどは虚偽情報が混ざっています。公式な情報以外は「間違いがある」可能性があります。もちろん、私の回答も間違いがあるかも知れません。色んなサイトを見たりすることは別にいいのですが、それら全てを鵜呑みにはしないでください。また、ChatGPTのようなAIはそのような「間違いがある」情報を含めて学習しており、何が正しいかまでは判断できません。それら大量にある情報から、似たような文脈を適当に取り出して、それっぽく言うだけです。そのため、ChatGPTは自信満々に嘘をつきます。

より正確に理解したいのであれば、公式の情報を参照してください。それ以外の情報は、それらを理解するための補助にしてください。スタック・オーバーフローの回答も、誰かのブログや記事も、どこかの会社の記事も全て信じてはいけません。AIが言う事なんて、もってのほかです。

JavaScriptのもっと信用すべき情報は仕様書です。ECMAScriptの仕様書です。[ECMA-262 - Ecma
International](https://www.ecma-international.org/publications-and-
standards/standards/ecma-262/)にあり、誰でも読めます。次に信用できるのは[MDN](https://developer.mozilla.org/ja/docs/Web/JavaScript)です。MDNには多くの企業やボランティアが協力して、ECMAScriptの言語仕様の説明や、JavaScriptとしての拡張されたAPIが書かれています。ただ、日本語版は翻訳が遅れている場合がありますので、最新情報を知りたい場合は英語版を確認してください。


CC BY-SA 4.0
2023-10-01T05:36:48.307
96395
2023-10-01T05:41:00.093
2023-10-01T05:41:00.093
7347
7347
96390
answer