【ITニュース解説】JavaScript revision blew my mind today.
2025年10月01日に「Dev.to」が公開したITニュース「JavaScript revision blew my mind today.」について初心者にもわかりやすく解説しています。
ITニュース概要
JavaScriptオブジェクトは隠れた親オブジェクト(プロトタイプ)を持つ。プロパティは親をたどって検索される。`__proto__`はオブジェクトの親を指し、`prototype`はコンストラクタが作るインスタンスの親となるオブジェクトだ。`new`は新しいオブジェクトを作り、その親をコンストラクタの`prototype`に設定する。共有メソッドは`prototype`に置くと効率が良い。
ITニュース解説
JavaScriptのオブジェクトは、プロパティやメソッドをどのように扱うかという点で独特な仕組みを持っている。この仕組みは「プロトタイプチェーン」と呼ばれ、オブジェクト指向の根幹をなす重要な概念だ。システムエンジニアを目指す上で、このプロトタイプチェーンを深く理解することは、JavaScriptのコードを正確に読み解き、効率的に記述するために不可欠である。
すべてのJavaScriptオブジェクトには、自身とは別のオブジェクトへの隠れた参照、つまり「プロトタイプ」が存在する。あるオブジェクトのプロパティやメソッドにアクセスしようとすると、JavaScriptはまずそのオブジェクト自身がそのプロパティを持っているかを調べる。もし持っていなければ、その隠れた参照をたどって次のプロトタイプオブジェクトを探しに行く。このプロセスは、プロパティが見つかるか、それ以上たどるべきプロトタイプがなくなる(nullに到達する)まで繰り返される。この連鎖的な探索の仕組みがプロトタイプチェーンである。
このプロトタイプチェーンを具体的に理解するために、Object.create()メソッドの動作を見てみよう。Object.create(parent)という形で新しいオブジェクトを作成すると、新しく作られたオブジェクトは最初は空だが、その隠れたプロトタイプ参照がparentオブジェクトに設定される。例えば、parentオブジェクトにgreet()というメソッドがある場合、新しく作成されたchildオブジェクトはgreet()メソッドを持たないにもかかわらず、child.greet()を呼び出すことができる。これは、JavaScriptがchild自身のプロパティを調べてもgreet()が見つからなかったため、childのプロトタイプであるparentオブジェクトを調べに行き、そこでgreet()メソッドを見つけて実行するからだ。開発者ツールなどでオブジェクトを検査すると、この隠れたプロトタイプ参照は[[Prototype]]と表示されることが多い。また、プログラムからこのプロトタイプを取得するためにはobj.__proto__というプロパティ(推奨されないが広く使われる)や、より安全なObject.getPrototypeOf(obj)メソッドを使用できる。
ここで、しばしば混同されがちな__proto__とprototypeという二つの用語について正確な違いを理解する必要がある。__proto__は、個々のオブジェクトがその親となるプロトタイプオブジェクトへの実際の参照を指す。これは、前述の[[Prototype]]という内部的なスロットにアクセスするためのものだ。一方で、prototypeはコンストラクタ関数にのみ存在する特別なプロパティである。例えば、function Person() {}というコンストラクタ関数を定義した場合、Person.prototypeというオブジェクトが存在する。このPerson.prototypeに定義されたメソッドやプロパティは、new Person()のようにnewキーワードを使ってPersonコンストラクタから作成されたすべてのインスタンスの__proto__となる。つまり、newで作成されたインスタンスの__proto__は、そのコンストラクタ関数のprototypeに設定されるのである。これにより、すべてのインスタンスが共通のメソッドやプロパティを共有できるようになる。したがって、Object.prototypeというグローバルなプロトタイプオブジェクトは存在するが、object.prototype(小文字のobject)は存在せず、undefinedとなるのは、prototypeがコンストラクタ関数に付随するプロパティだからである。
newキーワードを使ったオブジェクト作成のプロセスは、以下のステップで実行される。まず、新しい空のオブジェクトが作成される。次に、この新しいオブジェクトの内部的な[[Prototype]]が、呼び出されたコンストラクタ関数のprototypeオブジェクトに設定される。その後、コンストラクタ関数が、この新しく作成されたオブジェクトをthisとして呼び出され、オブジェクトに固有のプロパティが追加される。最後に、この新しいオブジェクトが返される。この一連の動作により、インスタンスはコンストラクタのプロトタイプに定義された共有のメソッドにアクセスできるようになる。
プロパティ探索アルゴリズムは、先ほど説明したように、まずオブジェクト自身、次にその__proto__、さらにその__proto__とチェーンを遡ってプロパティを探す。この仕組みを理解することは、どこにデータを置き、どこにメソッドを置くべきかの実践的な指針となる。インスタンスごとに異なるデータ(例えば、人の名前など)は、コンストラクタ関数内でthisを使って定義すべきだ。そうすることで、各インスタンスが独自のデータを保持できる。一方、すべてのインスタンスで共通して使われるメソッド(例えば、挨拶をするメソッドなど)は、メモリを節約し、一貫した動作を保証するために、コンストラクタ関数のprototypeに定義することが推奨される。
JavaScriptのほとんどのオブジェクトは、最終的にObject.prototypeをプロトタイプチェーンのどこかに持つ。そして、Object.prototype自身のプロトタイプはnullである。これはプロトタイプチェーンの終端を示し、これ以上遡るオブジェクトがないことを意味する。もしnullまで探索しても目的のプロパティが見つからなければ、そのプロパティへのアクセス結果はundefinedとなる。
JavaScriptでは、Object自体もまた関数である(typeof Object === 'function')。関数もオブジェクトの一種であり、独自のプロトタイプ(通常はFunction.prototype)を持っている。このように、コンストラクタ関数や組み込みオブジェクトも同じプロトタイプルールに従って動作している。
プロトタイプチェーンを扱う上でのベストプラクティスとして、オブジェクトのプロトタイプを取得する際にはobj.__proto__ではなく、Object.getPrototypeOf(obj)を使うことが推奨される。また、オブジェクトが自身のプロパティだけを持っているかを確認したい場合は、obj.hasOwnProperty('propertyName')を使用すると良い。共有されているプロトタイプの状態を不用意に変更すると、そのプロトタイプを継承するすべてのインスタンスに影響を与える可能性があるため、特に配列などの参照型のプロパティをプロトタイプに持たせる場合は注意が必要だ。プロダクション環境で__proto__を使ってプロトタイプを設定することはパフォーマンス上の理由から避けるべきであり、Object.create()やObject.setPrototypeOf()を使用することが推奨される。これらの知識を身につけることで、より堅牢で効率的なJavaScriptコードを書けるようになるだろう。