【JavaScript 上級講座】prototypeオブジェクトと__proto__プロパティ(プロトタイプチェーン)
本講座では、JavaScriptの根幹を成すprototypeオブジェクトと __proto__プロパティを学習する。 恐らく一番大切な講義になるので、今までに増してゆっくり学習を進める。
コンストラクタ関数とメソッドの定義
002//コンストラクタ関数
003var Item = function () { ;
004 this.move = function () {
005 alert ( "item move" ) ;
006 }
007}
008//prototypeに定義
009Item.prototype.run = function () {
010 alert ( "item run" ) ;
011}
012</script>
当講座と次の講座の2回に分けて、prototypeチェーンについて学習する。 具体的に何を学習するのか、上記のサンプルプログラムを利用して解説する。
まず3行目でItemコンストラクタ関数を定義する。 Itemコンストラクタ関数はゲームに表示する画像をイメージしている。 Itemコンストラクタ関数の中で、move()関数を定義している。 これは普通の関数の定義である。間違っていない気がする。
9行目に注目する。9行目では、Itemコンストラクタ関数の prototypeオブジェクトにrunプロパティを追加して、 関数を代入している(prototypeオブジェクトは未学習であるが、気にする必要は無い)。
このプログラムを実行すると、Itemコンストラクタ関数は move()関数とrun()関数を持つ。
ここで問題が生じる。move()関数とrun()関数は別の方法で関数を定義しているが、 どちらが良い方法だろうか?
一般的に、prototypeオブジェクトに追加するrun()関数の定義の方が推奨される。
なぜ?
その理由を、本講座と次の講座で学習する。
Objectコンストラクタ関数とオブジェクトの関係
Objectコンストラクタとオブジェクトの関係を明確に理解する。
002//オブジェクトを作成
003var a = { } ;
004
005//オブジェクトを作成
006var b = new Object() ;
007</script>
008
3行目でオブジェクトを作成し、変数aへ代入する。 変数aが参照しているのは、オブジェクトである。
6行目でObjectコンストラクタ関数をnewして、オブジェクトを作成後、 変数bへ代入する。 変数bが参照しているのは、オブジェクトである。
Objectコンストラクタ関数 Objectコンストラクタ関数は 関数名Objectで定義されたコンストラクタ関数である。 (Objectとオブジェクトは異なる)
Objectは、先ほど見たItemコンストラクタ関数と同様、 コンストラクタ関数である。 コンストラクタ関数はオブジェクト製造機であった。 Objectコンストラクタ関数をnewすると、 オブジェクトが生成される。 Objectとオブジェクトは似ているので混同しやすいが、 別物である。JavaScriptでは、オブジェクトが主役である。
002//コンストラクタ関数
003var Iori = function () { } ;
004
005//オブジェクトを作成
006var a = new Iori () ;
007
008//オブジェクトを作成
009var b = new Object () ;
010</script>
オブジェクトとコンストラクタ関数の関係をもう一度確認する。
3行目でコンストラクタ関数Ioriを定義する。 コンストラクタ関数を定義する場合は、変数名を大文字で始める。 今回は、「Iori」である。 6行目でIoriコンストラクタ関数をnewして、変数aへ代入する。 変数aはオブジェクトを参照する。
9行目で、Objectコンストラクタ関数をnewして、変数bへ代入する。 変数bはオブジェクトを参照する。 変数aもbも、参照するものはオブジェクトである。
Ioriコンストラクタ関数から生成されたオブジェクトと、 Objectコンストラクタ関数から生成されたオブジェクトに違いは無いのだろうか?
そういう疑問がわきあがる。どちらもオブジェクトであるが、 オブジェクトのプロパティが異なる。 どのプロパティが異なるのか?という点については後ほど解説する。
オブジェクトとObjectコンストラクタ関数の関係
では、prototypeオブジェクトと__proto__プロパティを見る。
002//オブジェクトを作成
003var a = new Object() ;
004
005//オブジェクトとObjectの関係
006//[true]
007alert ( a.__proto__ === Object.prototype ) ;
008</script>
プログラムを実行すると「true」と表示する。
オブジェクトとコンストラクタ関数の関係を、 prototypeオブジェクトと__proto__プロパティについて解説する。 3行目でObjectコンストラクタ関数からオブジェクトを生成して、 変数aへ代入する。
変数 | 参照先 |
a | { __proto__ : { } } |
__proto__プロパティ オブジェクトを生成すると、 オブジェクトの中に__proto__プロパティが自動的に追加される。 __proto__プロパティには、別のオブジェクトへの参照が入っている
オブジェクトを生成する、__proto__プロパティが自動的に生成される。 __proto__はオブジェクトを参照する。
002//オブジェクトを作成
003var a = new Object() ;
004
005//オブジェクトとObjectの関係
006//[true]
007alert ( a.__proto__ === Object.prototype ) ;
008</script>
7行目で変数aが参照するオブジェクトの__proto__プロパティと、 Objectコンストラクタ関数のprototypeオブジェクトを比較する。 等しい。 コンストラクタ関数は「オブジェクト製造機」と解説したが、 オブジェクトを製造して、「はい、おしまい」ではない。 そうではなくて、オブジェクトとコンストラクタ関数は繋がっている。 オブジェクトの__proto__プロパティと、 コンストラクタ関数のprototypeオブジェクトは同じである。
コンストラクタ関数とオブジェクト オブジェクトの__proto__プロパティには、 コンストラクタ関数のprototypeへの参照が代入されている。
参照先 | Object | |
変数a | { __proto__ : { 【参照X】 } } |
{ prototype : { 【参照X】 } } |
プログラムのイメージ図である。必ずしも正確ではないが、分かりやすい。 変数aが参照するオブジェクトに__proto__プロパティが存在する。 __proto__プロパティが参照するオブジェクトを、 「参照X」と名づける。 一方Objectコンストラクタ関数にprototypeプロパティが存在する。 このprototypeプロパティが参照するオブジェクトは「参照X」と名づけたオブジェクトである。 オブジェクトの__proto__プロパティとObjectのprototypeプロパティは、 同一のオブジェクトを参照する。
参照先 | Object | |
変数a | { __proto__ : 参照X } |
{ prototype : 参照X } |
参照X | { } |
正確に記述するなら、このようになる。 __proto__プロパティとprototypeプロパティに入っている値は参照である。 __proto__プロパティとprototypプロパティに「参照X」が入っている。 「参照X」はオブジェクトを参照する。 「参照X」の先にはオブジェクトが存在する。
002//オブジェクトを作成
003var a = new Object() ;
004
005//オブジェクトとObjectの関係
006//[true]
007alert ( a.__proto__ === Object.prototype ) ;
008</script>
7行目で、変数aが参照するオブジェクトの __proto__とObjectコンストラクタ関数のprototypeオブジェクトを比較する。 同じなので、「true」と表示する。
では、同じようなサンプルプログラムを見る。
002//コンストラクタ関数
003var Iori = function () { }
004
005//オブジェクトを作成
006var a = new Iori () ;
007
008//オブジェクトとIoriの関係
009//[true]
010alert ( a.__proto__ === Iori.prototype ) ;
011</script>
プログラムを実行すると「true」と表示する。
Ioriコンストラクタ関数を利用して、 コンストラクタ関数とオブジェクトの関係を復習する。
3行目で、Ioriコンストラクタ関数を定義する。 6行目でIoriコンストラクタ関数をnewしてオブジェクトを生成し、 変数aへ代入する(オブジェクトへの参照を変数aに代入する)。
参照先 | Iori | |
変数a | { __proto__ : { 【参照X】 } } |
{ prototype : { 【参照X】 } } |
変数aが参照するオブジェクトに__proto__プロパティが存在して、 オブジェクトへの参照が入っている。 このオブジェクトを「参照X」と名づける。 Ioriコンストラクタ関数にprototypeプロパティが存在して、 オブジェクトへの参照が入っている。 参照先のオブジェクトは「参照X」と名づけたオブジェクトである。
参照先 | Iori | |
変数a | { __proto__ : 参照X } |
{ prototype : 参照X } |
参照先 | |
参照X | { } |
こちらのイメージの方が正解に近い。 変数aが参照するオブジェクトの__proto_プロパティに、 「参照X」が入っていると仮定する。 この場合、Ioriコンストラクタ関数のprototypeプロパティも 「参照X」が入っている。 「参照X」の参照先は別のオブジェクトである。
それにしても、表を見るとオブジェクトばかりである。 JavaScriptはオブジェクトで形成されている。
002//コンストラクタ関数
003var Iori = function () { }
004
005//オブジェクトを作成
006var a = new Iori () ;
007
008//オブジェクトとIoriの関係
009//[true]
010alert ( a.__proto__ === Iori.prototype ) ;
011</script>
10行目でオブジェクトの__proto__プロパティと Ioriコンストラクタ関数のprototypeプロパティを比較する。 「true」と表示する。
コンストラクタ関数とObjectの関係
コンストラクタ関数とObjectコンストラクタ関数の関係を見る。
002//コンストラクタ関数
003var Iori = function () { };
004
005//[true]
006alert (
007 Iori.prototype.__proto__
008 ===
009 Object.prototype
010) ;
011</script>
プログラムを実行すると「true」と表示する。
コンストラクタ関数と、Objectコンストラクタ関数の関係を見る。 関係がある。独自に定義したコンストラクタ関数とObjectコンストラクタ関数は 繋がっている。
3行目でIoriコンストラクタ関数を定義する。 7行目で、Ioriコンストラクタ関数のprototypeオブジェクトの中にある __proto__プロパティと、Objectコンストラクタ関数のprototypeオブジェクトを比較する。 同じなので、「true」と表示する。
オブジェクトと__proto__プロパティ オブジェクトは、__proto__プロパティを保持する。
Ioriコンストラクタ関数には、prototypeプロパティが自動的に追加され、 そのprototypeプロパティは、オブジェクトを参照する。 そのオブジェクトの中に、__proto__プロパティが存在する。 オブジェクトの中には、__proto__プロパティが自動的に作られる。
Iori | Object |
{ prototype : { __proto__ : { 【参照X】 } } } |
{ prototype : { 【参照X】 } } |
正確ではないが分かりやすいイメージ図である。 Ioriコンストラクタ関数内には、prototypeオブジェクトが自動的に生成される。 prototypeオブジェクトの中には、__proto__プロパティが存在する。 この__proto__プロパティにオブジェクトが入っている。 そのオブジェクトは、Objectコンストラクタ関数のprototypeと同じである。
- オブジェクトの中には__proto__がある
- コンストラクタ関数には、prototypeがある
この2点が重要である。
002//コンストラクタ関数
003var Iori = function () { };
004
005//[true]
006alert (
007 Iori.prototype.__proto__
008 ===
009 Object.prototype
010) ;
011</script>
7行目で、Ioriコンストラクタ関数のprototypeオブジェクトの__proto__が参照する オブジェクトと、Objectコンストラクタ関数のprototypeオブジェクトを比較する。 同じなので「true」と表示する。
プロトタイプチェーン
002//コンストラクタ関数
003var Iori = function () { };
004
005//IORI型オブジェクト
006var a = new Iori () ;
007
008//[true]
009alert (
010 a.__proto__
011 ===
012 Iori.prototype
013) ;
014//[true]
015alert (
016 Iori.prototype.__proto__
017 ===
018 Object.prototype
019) ;
020</script>
プログラムを実行すると「true」「true」と表示する。
いままでの学習の復習である。そして、次へのステップアップ的サンプルである。 オブジェクトとコンストラクタ関数の関係、 コンストラクタ関数とObjectコンストラクタ関数の関係を見る。
3行目でIoriコンストラクタ関数を定義して、 6行目でオブジェクトを生成する。 変数aが参照するオブジェクトはIoriコンストラクタ関数から生成された。
10行目で変数aのオブジェクトの__proto__プロパティと、 Ioriコンストラクタ関数のprototypeオブジェクトを比較する。 「true」が表示する。 変数aが参照するオブジェクトは、Ioriコンストラクタ関数から生成された。 そのオブジェクトとIoriコンストラクタ関数は、 __proto__プロパティとprototypeプロパティで繋がっている。
参照先 | Iori | |
変数a | { __proto__ : { 【参照X】 } } |
{ prototype : { 【参照X】 } } |
002//コンストラクタ関数
003var Iori = function () { };
004
005//IORI型オブジェクト
006var a = new Iori () ;
007
008//[true]
009alert (
010 a.__proto__
011 ===
012 Iori.prototype
013) ;
014//[true]
015alert (
016 Iori.prototype.__proto__
017 ===
018 Object.prototype
019) ;
020</script>
16行目でIoriコンストラクタ関数のprototypeオブジェクトの中の__proto__プロパティと、 Objectコンストラクタ関数のprototypeプロパティを比較する。 同じものなので「true」が表示する。
Ioriのように 独自定義のコンストラクタ関数とObjectコンストラクタ関数は、 __proto__プロパティとprototypeプロパティで繋がっている。
オブジェクト | Iori | Object | |
変数 a |
__proto__ : { 【参照X】 } |
prototype : { 【参照X】 __proto__ : { 【参照Y】 } } |
prototype : { 【参照Y】 } |
正確ではないが分かりやすいイメージ図である。 まず、変数aが参照するオブジェクトが存在する。 そのオブジェクトの__proto__プロパティにオブジェクトが入っている。 このオブジェクトを「参照X」と名づけよう。
さて、変数aが参照するオブジェクトはIoriコンストラクタ関数から生成された。 Ioriコンストラクタ関数にprototypeオブジェクトが存在する。 このprototypeオブジェクトは、「参照X」と名づけたオブジェクトと同じである。
さて、参照Xと名づけたオブジェクトの中に__proto__プロパティが存在する。 オブジェクトの中には__proto__プロパティが存在するからである。 __proto__プロパティは別のオブジェクトを参照する。 そのオブジェクトを「参照Y」と名づける。 このオブジェクトはObjectコンストラクタ関数のprototypeオブジェクトと同じである。
002//コンストラクタ関数
003var Iori = function () { };
004
005//IORI型オブジェクト
006var a = new Iori () ;
007
008//[true]
009alert (
010 a.__proto__
011 ===
012 Iori.prototype
013) ;
014//[true]
015alert (
016 Iori.prototype.__proto__
017 ===
018 Object.prototype
019) ;
020</script>
9行目と15行目で、ダイアログを表示する。 2回「true」と表示して、プログラムは終了する。
さて、このプログラムを変形する。
010 a.__proto__
011 ===
012 Iori.prototype
013) ;
014//[true]
015alert (
016 Iori.prototype.__proto__
017 ===
018 Object.prototype
019) ;
12行目と16行目の
Iori.prototype の部分は同じである。
- a.__proto__===Iori.prototype
- Iori.prototype.__proto__===Object.prototype
「1」の「a.__proto__」の部分を「2」へ代入する。
- a.__proto__.__proto__===Object.prototype
このプログラムが成立する。
002//コンストラクタ関数
003var Iori = function () { };
004
005//IORI型オブジェクト
006var a = new Iori () ;
007
008//[true]
009alert (
010 a.__proto__.__proto__
011 ===
012 Object.prototype
013) ;
014</script>
プログラムを実行すると「true」と表示する。
解説する。3行目でIoriコンストラクタ関数を定義する。 6行目でIoriコンストラクタ関数をnewして、 変数aへオブジェクトを代入する。9行目で、 変数aが参照するオブジェクトの__proto__プロパティが参照するオブジェクトの __proto__が参照するオブジェクトと、 Objectコンストラクタ関数のprototypeオブジェクトを比較する。 同じなので、「true」が表示する。
Objectコンストラクタ関数のprototypeオブジェクトの__proto__プロパティ
これまでの学習でプロトタイプチェーンを学習した。
プロトタイプチェーン オブジェクトの__proto__プロパティを辿ると、Objectコンストラクタ関数の prototypeオブジェクトへたどりつく
オブジェクトには__proto__プロパティが存在する。 __proto__プロパティにはオブジェクトが入っている。 そのオブジェクトにも__proto__プロパティが存在する。 このように__proto__プロパティをたどる事ができる。 これをプロトタイプチェーンと言う。 JavaScriptのオブジェクトはオブジェクト単体で存在するのではなく、 どこかのオブジェクトと繋がっている。
さて、プロトタイプチェーンはObjectコンストラクタ関数のprototypeオブジェクトまで繋がる。 ここで考えたい。全てのオブジェクトは__proto__プロパティを持っている。 Objectコンストラクタ関数のprototypeオブジェクトもオブジェクトである。 当然、__proto__プロパティを持っている。 では、Objectコンストラクタ関数のprototypeオブジェクトの__proto__は何を参照するのだろうか?
002//[object]
003alert (
004 Object.prototype
005) ;
006//[null]
007alert (
008 Object.prototype.__proto__
009) ;
010</script>
プログラムを実行すると「object」「null」と表示する。
3行目でObjectコンストラクタ関数のprototypeプロパティを表示する。 prototypeプロパティにはオブジェクトへの参照が入っている。 3行目を実行すると「object」と表示する。
7行目でObjectコンストラクタ関数のprototypeプロパティの__proto__プロパティを表示する。 「null」と表示する。 つまり・・・この通りである。 プロトタイプチェーンは、 Objectコンストラクタ関数のprototypeプロパティの__proto__プロパティで終了する。 これ以上はどこにも繋がらない。
まとめ
Objectコンストラクタ関数 Objectコンストラクタ関数は 関数名Objectで定義されたコンストラクタ関数である。 (Objectとオブジェクトは異なる)
__proto__プロパティ コンストラクタ関数からオブジェクトを生成すると、 オブジェクトの中に自動的に__proto__プロパティが生成される。 __proto__プロパティには、オブジェクトへの参照が入っている
コンストラクタ関数とオブジェクト オブジェクトの__proto__プロパティには、 コンストラクタ関数のprototypeへの参照が代入されている。
オブジェクトと__proto__プロパティ オブジェクトは、__proto__プロパティを保持する。
- オブジェクトの中には__proto__がある
- コンストラクタ関数には、prototypeがある
オブジェクト | Iori | Object | |
変数 a |
__proto__ : { 【参照X】 } |
prototype : { 【参照X】 __proto__ : { 【参照Y】 } } |
prototype : { 【参照Y】 __proto__ : null } |
- a.__proto__.__proto__===Object.prototype