JavaScriptプロトタイプチェーン(2)プロトタイプチェーンとプロパティシャドウィング

ラベル:

前の関連記事:JavaScriptプロトタイプチェーン(1)プロトタイプチェーン図示ツール


Object Playgroundのオブジェクトビジュアライザーの「this」を使ってプロトタイプチェーンをグラフで表して、オブジェクトのプロパティの関数オブジェクトとその関数を呼び出したときのthisの中身をグラフのエッジ(つまりプロトタイプチェーン)をたどって確認します。

オブジェクトビジュアライザーの「this」のプロパティにオブジェクトを代入して図示する


前回同様Object Playgroundのビデオの7:10頃にでてくる例を使います。
var parent = {
    get: function fn() {
         return this.val;
    },
    val: 42
}
var child = Object.create(parent);
child.val = 3.14159

parent.get(); //→42
child.get(); //→3.14159
7行目のObject.create()でparentオブジェクトから生成されたchildオブジェクトは、parentオブジェクのgetプロパティにvalプロパティの値を返すfn関数オブジェクトをもっています。

parentオブジェクトにはvalプロパティに42をもっていますが、childオブジェクトは8行目で自分自身のvalプロパティ3.14159を作っています。

その結果10行目のparent.get()ではparentオブジェクトのvalプロパティが使われて42が返ってくるのに対して、11行目のchild.get()ではchildオブジェトのvalプロパティの3.14159が返ってきます。

まずparentオブジェクトをオブジェクトビジュアライザーで図示してみます。

図示したいオブジェクトをオブジェクトビジュアライザーの「this」のプロパティにすると図示されます。
this.parent = parent;
thisにparentプロパティを作ってそこにparentオブジェクトを代入しました。


これでthisのparentプロパティのオブジェクトになっているparentオブジェクトがthis.parentとして図示されました。

this.parentノードをみるとparentオブジェクトがgetプロパティに関数fn()、valプロパティに値42をもっていることがわかります。

thisノードのparentプロパティの{Object.prototype}はthis.parentがObject.prototypeオブジェクトであることを示します。

つまりthis.parentノードの最下段にあるObject.prototypeのことを表しています。

今度は同様にしてchildオブジェクトを図示してみましょう。
this.child = child;
this.childノードが図に追加されました。

this.childノードの一番下の項目のthis.child.<prototype>がエッジでparentオブジェクトのノードと繋がっているのは、childオブジェクトのプロトタイプがparentオブジェクトだからです。

7行目のObject.create()でparentオブジェクトからchildオブジェクトを生成するということは、parentオブジェクトをプロトタイプとしてchildオブジェクトを生成するということだからです。

this.child.<prototype>には名前がないのでthisオブジェクトのノードのchildプロパティの値は{<anon>}(anonymous=無名)になっています。

このように図示するとオブジェクトがそれぞれ関係していることがよくわかります。

グラフのエッジの矢印をたどってプロパティにたどりつくのがプロトタイプチェーン


このオブジェクトビジュアライザーで作られたグラフのエッジは矢印になっていて、それを順方向にたどって望みのプロパティにいきつく仕組みをプロトタイプチェーンといいます。
var parent = {
    get: function fn() {
         return this.val;
    },
    val: 42
}
var child = Object.create(parent);
child.val = 3.14159

this.parent = parent;
this.parent_get= parent.get;
this.parent_get_val=parent.get();
this.child=child;
this.child_get = child.get;
this.child_get_val=child.get();
parentオブジェクト、childオブジェクトそれぞれについて、オブジェクト、getプロパティに入っているオブジェクト、getプロパティに入っている関数オブジェクトの戻り値、をオブジェクトビジュアライザーの「this」のプロパティに設定しました。


このグラフでparent.get()とchild.get()をプロトタイプチェーンでたどってみます。

まずparent.get()について。

parentのgetプロパティのオブジェクトをプロトタイプチェーンでたどります。

thisオブジェクトのノードのparentプロパティにparentオブジェクトを代入してあるので、そこからプロトタイプチェーンが始まります。

順方向にエッジをたどってthis.parentオブジェクトのノードにたどりつきます。

this.parentオブジェクトのgetプロパティには関数オブジェクトfnが入っているのでparent.getはfnであることがわかります。(グラフでthisオブジェクトのノードにあるparent_getプロパティの値に該当。)

fnのコードを見るとthis.valが戻り値であることがわかります。(グラフではわからないのでコードを見る必要があります。)

thisオブジェクトが何であるかはWSH JScriptでJavaScriptのお勉強(関数定義、クロージャ、this)で書いた通り関数の呼び出し方に依存するのですが、それがつまりこのプロトタイプチェーンをたどって最初にたどりつくプロパティに一致するという意味です。(オブジェクトビジュアライザーのグラフのノードのthisはコードにでてくるthisではないことに注意。)

とういうことでparent.get()のthis.valを探すためにプロトタイプチェーンをたどってvalプロパティをさがします。

this.parentオブジェクトのノードにvalがあるのでthis.valは42とわかります。


以上の結果、parent.get()は42とわかります。(グラフでthisオブジェクトのノードにあるparent_get_valプロパティの値に該当。)

オブジェクトビジュアライザーのグラフのノードのthisオブジェクトはオブジェクトビジュアライザーのオブジェクトであって、元のコードのオブジェクトではないので、child.get()と違ってparent.get()についてはもとのコードでは一つのオブジェクトだけで完結するので正確にはプロトタイプチェーンをたどるとは言い難いです。

今度はchild.get()についてです。

まずグラフのthisオブジェクトのノードのchildプロパティからエッジを順方向にたどってgetプロパティを探しに行きます。

this.childオブジェクトのノードにたどり着きますがそこにはgetプロパティがありません。

なので、this.child.<prototype>からまたエッジを順方向にたどってthis.parentオブジェクトのノードにたどりついてそこにgetプロパティがあるので、その値の関数オブジェクトfnを使います。

fnはthis.valを使うのでvalプロパティを探しにまた最初のthisオブジェクトのノードからプロトタイプチェーンをたどります。

今度は最初にたどり着いたthis.childオブジェクトのノードにvalプロパティがあるのでその値3.14159を使います。

以上の結果child.get()は3.14159になります。

プロトタイプチェーンの最初にたどりついたプロパティを使用して、そのあとにある同じキーのプロパティを使わない仕組みをプロパティシャドウィング(property shadowing)といいます(継承とプロトタイプチェーン - JavaScript | MDN)。

プロトタイプとなったオブジェクトのプロパティを書き換えているわけではないことに注意が必要です。

参考にしたサイト


Object Playground: The Definitive Guide to Object-Oriented JavaScript
JavaScriptプロトタイプチェーン図示ツール。

JavaScript | MDN
Mozilla Developer NetworkによるJavaScriptのマニュアル。

次の関連記事:JavaScriptプロトタイプチェーン(3)すべてのプロトタイプチェーンはObject.prototypeオブジェクトで終わる

PR

0 件のコメント:

コメントを投稿