🎄teratailクリスマスプレゼントキャンペーン2024🎄』開催中!

\teratail特別グッズやAmazonギフトカード最大2,000円分が当たる!/

詳細はこちら
JavaScript

JavaScriptは、プログラミング言語のひとつです。ネットスケープコミュニケーションズで開発されました。 開発当初はLiveScriptと呼ばれていましたが、業務提携していたサン・マイクロシステムズが開発したJavaが脚光を浴びていたことから、JavaScriptと改名されました。 動きのあるWebページを作ることを目的に開発されたもので、主要なWebブラウザのほとんどに搭載されています。

Q&A

解決済

3回答

511閲覧

MDNのプロトタイプチェーンについての説明の不明点

slimat

総合スコア57

JavaScript

JavaScriptは、プログラミング言語のひとつです。ネットスケープコミュニケーションズで開発されました。 開発当初はLiveScriptと呼ばれていましたが、業務提携していたサン・マイクロシステムズが開発したJavaが脚光を浴びていたことから、JavaScriptと改名されました。 動きのあるWebページを作ることを目的に開発されたもので、主要なWebブラウザのほとんどに搭載されています。

0グッド

1クリップ

投稿2019/12/05 02:35

編集2019/12/24 16:16

こんにちは.

継承とプロトタイプチェーンにある以下の文について質問があります.

プロトタイプチェーンの上層にあるプロパティの検索時間は、性能に悪影響を及ぼす可能性があり、性能が重要であるコードにおいて、意義深いものになるかもしれません。加えて、存在しないプロパティへのアクセスは、常にプロトタイプチェーン全体を通過します。

また、オブジェクトのプロパティを順に処理する際、プロトタイプチェーンにあるすべての列挙可能なプロパティが列挙されます。

上の最終行は, とあるオブジェクトAからプロパティaが呼び出された時に, aをAが持っていなかったら, AのプロトタイプBがaを持っていないか探し, それでも見つからなければAのプロトタイプのプロトタイプC内でaを探すというようなプロトタイプチェーンがある場合に, A内を探すときはAオブジェクトの持っている全てのプロパティを列挙し, B内を探すときはBの持っているプロパティを全てのプロパティを列挙するということでしょうか? まさか, Bがaを持っている場合に, Cのプロパティも列挙されるわけではないですよね?

宜しくお願い致します.


追記1

やはり, ルートオブジェクトまでにプロパティが見つかったならば, その地点でプロトタイプを遡ることはないのですね.
また, 列挙可能 というのは専門用語だったのですね.
よって, プロパティの検索は, オブジェクト毎に列挙可能なプロパティを列挙し, その中に求めるプロパティがあれば, その地点で検索を終了するのですね.

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答3

0

プロトタイプチェーンを辿る機能

また、オブジェクトのプロパティを順に処理する際、プロトタイプチェーンにあるすべての列挙可能なプロパティが列挙されます。

代表的なものは既出の通り、for-in ですが、MDNでは特定の機能に限定していません。
重要なのは「プロトタイプチェーンを辿る機能」と「プロトタイプチェーンを辿らない機能」を区別して覚えることですので、この一文にこだわる必要はないと思います。
例えば、プロパティ名が分かっているオブジェクトに対して、プロパティの初期化を行うとして、次のコードを書いた場合、

JavaScript

1for (var keys = ['a','b','c','d','f']; i < 5; i++) { 2 object[keys[i]] = i; 3}

object.a がプロトタイプ上のプロパティであった場合、object[keys[i]] はプロトタイプ上のプロパティを参照します。
プロパティアクセサはプロトタイプチェーンを辿るからです。

プロトタイプチェーンを辿らない機能

hasOwnProperty は JavaScript において唯一、プロトタイプチェーンを通らずにプロパティを扱うものです。

MDNでは上のように説明していますが、これは「間違い」で、Object.keys()Object.getOwnPropertyNames() など、プロトタイプチェーンを辿らない機能は他にもあります。

Object.keys() が実装されていないブラウザのシェアが高かった時代、Object.keys() の代用として次のコードが書かれていました。

JavaScript

1for (var key in object) { 2 if (object.hasOwnProperty(key)) { 3 // 処理 4 } 5}

for-in は「プロトタイプチェーンを辿る機能」ですが、hasOwnProperty を併用する事で、プロトタイプチェーンを辿らない直属のプロパティを対象にします。
これはほぼ Object.keys() と同じ振る舞いをしますが、「直属の enumerable 値を見ない場合がある」という違いがあるので、完全に挙動をあわせるには下記コードに変えます。

JavaScript

1for (var key in object) { 2 if (object.hasOwnProperty(key) && Object.getOwnPropertyDescriptor(object, key).enumerable) { 3 // 処理 4 } 5}

ある関数、演算子が「プロトタイプチェーンを辿るのか」という観点で再調査してみると、見えてくるものがあると思います。

Re: slimat さん

投稿2019/12/06 11:30

編集2019/12/06 12:06
think49

総合スコア18189

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

slimat

2019/12/25 09:09

ご回答ありがとうございます. JavaScriptには列挙可能という用語があるとは知りませんでした. ただただ国語辞書的な意味で理解したまま読み飛ばしていました.
guest

0

上の最終行は, (中略)全てのプロパティを列挙するということでしょうか?

違います。for-inのような、キー自体を列挙する時の場面の話です。

投稿2019/12/05 02:40

maisumakun

総合スコア145975

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

slimat

2019/12/24 16:24

ご回答ありがとうございます. 列挙可能というのが用語だとは思いませんでした.
maisumakun

2019/12/24 23:13

ここでの「列挙可能」は、enumerableの訳語です。 Object.definePropertyで指定できる属性のうちの1つです。
guest

0

ベストアンサー

最終行

前置きが長くなります。

ご質問はリンク先の「性能」のセクションです。

性能として、考えるべき要点が次の2点で、トレードオフにあることを考える必要があります。

  1. プロパティの検索時間
  2. メモリ効率

「2値を加減算する」機能を持ったオブジェクトを異なる方法で定義してみます。

  1. メンバ関数をコンストラクタ関数内で定義する方法
  2. メンバ関数をコンストラクタ関数の prototype で定義する方法

javascript

1// 1. 2function MyClass(...args){ 3 if( args[0] instanceof MyClass ) return args[0] 4 5 this.a = args[0]; 6 this.b = args[1]; 7 8 this.sum = function(){ 9 return this.a + this.b; 10 } 11 this.sub = function(){ 12 return this.a - this.b; 13 } 14} 15// 2. 16function MyClass(a, b) { 17 this.a = a; 18 this.b = b; 19} 20MyClass.prototype = { 21 sum : function(){ 22 return this.a + this.b; 23 }, 24 sub : function(){ 25 return this.a - this.b; 26 } 27}

この2つの定義方法、インスタンスは共に同じ機能を提供します。

javascript

1var c = new MyClass( 3, 2 ); 2console.log( c.sum() ); // 5 3console.log( c.sub() ); // 1

しかし、内部では決定的に異なる実装です。

javascript

1// 1. 2console.log( c ); // MyClass {a: 3, b: 2, sum: ƒ, sub: ƒ} 3console.log( c.__proto__ ); // {constructor: ƒ} 4 5// 2. 6console.log( c ); // MyClass {a: 3, b: 2} 7console.log( c.__proto__ ); // {sum: ƒ, sub: ƒ, constructor: ƒ}

トレードオフにある2つを考えると、次のようなことが言えます

  1. はインスタンスごとに sum(), sub() を直接持つ分、メソッド検索は高速だがメモリを食う。
  2. はインスタンスごとに sum(), sub() を[[Prototype]]参照する分、列挙に時間を要するが、メモリを節約できる。

現在の実行環境では、Canvasへの膨大な書き換えなどで起こる程度と、その速度差は小さくなっています。
なので、可読性も高い 2. の方法が使われます。

最終行

回答です。
for..inのような列挙は、[[Prototype]] に定義されたメンバを順次参照することを述べていて、プロパティサーチが幾重にも重なると、所要時間が性能面に関わることを説明しています。

ですので、長い前置きの通り、トレードオフとなる特徴から、しっかり選ぶ必要があるということです。

※MDN上級者向けのページは、コードを書いた経験、読んだ経験を積んで読み直すと、理解が変わります。

追記)

まさか, Bがaを持っている場合に, Cのプロパティも列挙されるわけではないですよね?

MDNに

加えて、存在しないプロパティへのアクセスは、常にプロトタイプチェーン全体を通過します。

と条件を示している通り、プロパティが見つかった時点でサーチは終了します。
(これが、ご質問の本質であり、求める回答でしょうか?)


継承を行った時は、この特徴が関連してきます。

  1. オブジェクトAの定義
  • class A {}
  • A[[Prototype]].method
  1. Aから派生したオブジェクトBの定義
  • class B extends A {}
  • B[[Prototype]].method

AにもBにも同じ名称のmethodがある場合、B のインスタンス bでは、親Aの持つ同名のmethodを参照しません。

Bのmethodで参照する必要がある場合、

  1. 古典的実装

javascript

1B.prototype.method = function() { 2 // A.prototype に定義されたメンバと明示して、call()/apply()で利用 3 return A.prototype.method.call(this) * 10 4}

2.Classブロック(シンタックスシュガー)

javascript

1class B extends A { 2 /* omitted */ 3 method () { 4 // 親を参照するための インタックスシュガー super が使える。 5 return super.method() * 10 6 } 7}

質問では「とあるオブジェクト」のような身勝手な想像上の表現では分かりづらいです。
リファレンスにも記述される様式 で具体的な定義を示したり、箇条書きにすると、回答もしやすい。
また、インスタンスもオブジェクトに違いないのですが、区別したほうが伝わりやすいです。

  1. オブジェクト=抽象化の定義
  2. インスタンス=具象化したオブジェクト

とにかく、少しでも理解が深まれば幸いです。

投稿2019/12/06 10:20

編集2019/12/06 21:31
AkitoshiManabe

総合スコア5434

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

slimat

2019/12/24 16:24

ご回答ありがとうございます. > これが、ご質問の本質であり、求める回答でしょうか? はい, そうです. これからは, オブジェクトとインスタンスを使い分けていきたいと思います. ご丁寧にありがとうございました.
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.36%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問