2021/08/01

プロトタイプチェーンをかじる

@ 酒井悠宇

javascriptを色々勉強していると、「プロトタイプベース」とか「プロトタイプチェーン」とかを耳にする機会が多くあるけど、その辺をあまり詳しく理解できていないので、
【JavaScript】プロトタイプチェーンについて徹底解説とか
プロトタイプ(prototype)によるJavaScriptのオブジェクト指向とか
図で理解するJavaScriptのプロトタイプチェーンとかみながら
勉強しつつ記事書いていきたいと思いま〜す!

オブジェクト指向とは

端的に表すと「ある事象をオブジェクトという単位で分解・整理して、認識・再現すること」です。つまりプログラミングならば、「最終的に作りたいシステム」が事象となり、このシステムをオブジェクトという単位に分解して、コンピュータ上で再現することがオブジェクト指向プログラミングになる訳です。

このオブジェクト指向という概念の中にクラスベースという考え方とプロトタイプベースという考え方があるみたい。

クラスベースとは

全てのオブジェクトにはクラスという雛形が必ず存在し、その雛型を実体化することでオブジェクトは生成される、という考え方です。クラスから実体化されたオブジェクトは、インスタンスと呼ばれます。このようなクラス-インスタンスという概念を使用するオブジェクト指向言語のことを、クラスベースオブジェクト指向言語(Class Based Object Oriented Language)と言います。

Rubyとかはまさにクラスベースの言語で、Railsとか見てみると、コントローラーとかモデルとか全部クラスになってる。
全部に雛形があって、それを継承しているクラスを作って、それを実体(インスタンス)化することで機能を作る。

クラスベースには人間にとって直感的でわかりやすいというメリットの反面、インスタンスごとにメソッドを持つ性質からメモリをたくさん使ってしまうというデメリットがあります。

なるほど。確かにクラスの概念はわかりやすいけどメモリ圧迫するのか。

プロトタイプベースとは

JavaScriptではクラス-インスタンスという考え方をしません。オブジェクトは別なオブジェクトを元(プロトタイプ)にして独自の特徴を付加することで存在する、という考え方をします。このようなオブジェクト指向言語のことを、プロトタイプベースオブジェクト指向言語(Prototype Based Object Oriented Language)と言います。

出ましたプロトタイプベース!

プロトタイプベースを用いることで、オブジェクト毎にメソッドを持たせなくても、継承元を探索することで継承元の持つメソッドを実行することができます。メリットは、オブジェクト毎に同じメソッドを持たないのでメモリ領域が少なくて済むという点です。

なるほど!クラスみたいに継承したクラス1つ1つがメソッドを持っているんじゃなくて、あくまでもメソッドを持ってるのは継承元のオブジェクトだけで、そこを参照してメソッドを使うからメモリ領域が少なくて済むと!!すげぇ!!

プロトタイプとは

プロトタイプとは、オブジェクトの継承が行われた際に、オブジェクト同士(継承元のオブジェクトと継承後のオブジェクト)がお互いに参照を持つことでお互いの機能を共有できるというある種Win-Win的なメカニズム。

プロトタイプベースのオブジェクト指向を実現しているメカニズムがプロトタイプってことか。

プロトタイプチェーンとは

プロトタイプの連鎖のことをプロトタイプチェーンと呼ぶ。

文字通り!笑
あるオブジェクトは別のオブジェクトをプロトタイプとしてできていて、そのオブジェクトも別のオブジェクトをプロトタイプとしてできていて....みたいなことか!

JavaScript には1つだけ、継承が発生する要素があります。オブジェクトです。どのオブジェクトもプロトタイプと呼ばれる、他のオブジェクトへの内部的な繋がりを持っています。そのプロトタイプオブジェクトも自身のプロトタイプを持っており、あるオブジェクトのプロトタイプが nullに到達するまでそれが続きます。nullは、定義によれば、プロトタイプを持たず、プロトタイプチェーンの最終リンクとなります。

そのプロトタイプの繋がりをずっと辿っていくと最終的にnullに到達すると言うことか。
そりゃそうやな。一番上のオブジェクトにはプロトタイプが存在しようがないもんな。

【実験タイム!】

const obj = {name: "ゆう"}; 
console.log(obj);

オブジェクトを作ってそれを出力してみると、こんな感じのが出てきた。

nameと言うプロパティと、設定した覚えのない[[Prototype]]と言うプロパティが出てきた。
[[Prototype]]と言うプロパティを展開してみると、さまざまな関数がここに格納されていることがわかった。
この関数はObject.prototypeと言うオブジェクトに含まれるメソッドと一致しているらしい。

Object クラスは JavaScript のデータ型の一つを表します。これは様々なキー付きコレクションとより複雑な実態を格納するために使用されます。Object は Object() コンストラクターまたはオブジェクト初期化子/リテラル構文を使用して生成することができます。

[[Prototype]]を出力したい場合は、obj.[[Prototype]]じゃなくてって

obj.__proto__

みたいに記述するらしい。
試しにやってみると、

さっきと全く同じ関数が出てきた。
Object.prototypeも出力してみる。

これもまた同じのが出てきた。
と言うことは、

obj.__proto__ === Object.prototype
⇨ true

になる!

そもそもObjectって?

Objectと言う変数はwindowに定義されている

windowってなに?

windowはブラウザのグローバルオブジェクトである

グローバルオブジェクトってなに?

JavaScriptのコードはオブジェクトに格納されている必要があり、その格納するオブジェクトの最上位に位置する単一のオブジェクトがグローバルオブジェクトと呼ばれます。

と言うことは

window.Object.prototype

って書いても、

さっきと同じのがでる!

window.console.log('console.logもwindowオブジェクトに含まれています')
⇨console.logもwindowオブジェクトに含まれています。

console.logも問題なく使えた。
つまりこのwindowオブジェクトというのが、ブラウザで実行されるjavascriptにおける最上位のオブジェクトということか。

window

windowを出力してみると数え切れないくらいのプロパティが入ってるオブジェクトになってた。
このwindowオブジェクトにブラウザで使用する全ての関数とかが入ってるってことか。

プロトタイプチェーンに実際に触れてみる

わかりやすそうなQiitaの記事を見つけたので頭にぶち込むために以下はほぼ丸写しです。

前提
・const obj = {name: "ゆう"}; はすでに定義してあるものとする。
・obj._proto_とObject.prototypeは同じ。

Object.prototypeオブジェクトにtoString()というメソッドがあるので、さっき作ったobjのprotoから実験的に呼んでみる。

console.log(obj.__proto__.toString);
⇨f toString() { [native code] } //呼べた!

※ fはfunctionの略
じゃあこれは?

console.log(obj.toString);
⇨f toString() { [native code] } //呼べてしもた!

プロトタイプチェーンの仕組み

  1. 指定したオブジェクトにプロパティが存在を調べる
  2. なかった場合__proto__が参照する先で存在を調べる
  3. それでもなかった場合__proto__が参照する・・・(ループ)
  4. 最終的にnullになるまで行う。nullならundefinedを返す

プロトタイプチェーンの流れを見る

  • obj.name (インスタンスに存在するパターン)
  • obj.toString (プロトタイプチェーン先に存在するパターン)
  • obj.hogehoge (最後まで見つからないパターン)

obj.nameを探す際の動き
objを探す。obj.nameを探す。
obj.toStringを探す際の動き
objを探す。obj.toStringを探す。見つからないのでobj._proto_を探す。obj._proto_.toStringを探す
obj.hogehogeを探す際の動き
objを探す。obj.hogehogeを探す。見つからないのでobj._proto_を探す。見つからないのでobj._proto_.hogehogeを探す。見つからないので、obj._proto_._proto_を探す。プロトタイプの_proto_にはnullが設定されてあるので、nullを返す。

オブジェクトのプロトタイプをみていくとそのオブジェクトの大元の継承もとに辿り着くということです。ですが、継承の大元であるオブジェクトは初めから存在するものなので、そいつの継承元はない。つまり、nullに帰属するということになります。もうちょっと踏み込むと、{ }(空オブジェクト)を経てから、nullに帰着します。

オブジェクトのプロパティを探す、なければオブジェクトのプロトタイプをさがす。オブジェクトのプロトタイプのプロパティを探す。なければオブジェクトのプロトタイプのプロトタイプをさがす...を繰り返していくといずれそのオブジェクトの継承元にたどり着くからnullを返すってことか。なるほど。

今後はFunctionのプロトタイプとかについても考えていきたいと思ってまっす。
プロトタイプチェーンについて大体どんなものなのかは雰囲気わかった気がします。
ありがとうございました~

© 2021 powerd by UnReact