- はじめに
- JavaScriptのオブジェクト指向
- JavaScriptのオブジェクト生成
- プロトタイプチェーンで継承を実現?
- プロトタイプチェーンのメリット
- メモリモデル
- なぜプロトタイプベースなのか
- まとめ
- 謝辞
はじめに
JavaScriptはオブジェクト指向開発を実現する一つの言語として位置づけられていますが、代表的なオブジェクト指向言語であるC++やJava、C#などとは異なる特徴があります。
JavaScriptを勉強しはじめて間もない頃は、C++やJavaなどと同じだとうろ高を括っていたのですが、進めていくうちにJavaScriptの特徴に戸惑うことばかりで、どうしても頭の中でJavaScriptの全体像が整理できずにいました。
クロージャーやプロトタイプ、プロトタイプチェーンなどJavaScript固有の特徴は個々に理解したとしても、『木を見て森を見ず』の様な感覚が払拭できずにいたのです。
それは「なぜプロトタイプベースなの?」という疑問です。
この記事ではJavaScriptの個々の特徴に関する技術的解説はさておき「JavaScriptの存在意義」や「JavaScriptのオブジェクト指向が意味するところ」に焦点を当て、「なぜプロトタイプベースなの?」の答えに迫ってみたいと思います。
JavaScriptのオブジェクト指向
代表的なオブジェクト指向言語であるC++やJavaはクラスベースのオブジェクト指向に分類される一方で、JavaScriptはプロトタイプベースのオブジェクト指向に分類されています。
プロトタイプベースのオブジェクト指向言語はJavaScriptが唯一といって過言ではないほど、特異な存在なのです。 では、プロトタイプベースとは何なのでしょうか?クラスベースとは何が違うのでしょうか?
それぞれ、次のように定義されています。
クラスベース(WikiPediaより)
クラスベースとはオブジェクト指向のスタイルのひとつで、「鋳型」となるクラスがあり、その鋳型によってオブジェクトの実体であるインスタンスを生成する、というスタイルである。クラスベースのシステムでは、システム内に、継承のシステムを持っていることが多い。プロトタイプベース(WikiPediaより)
プロトタイプベースのオブジェクト指向言語は、クラスベースのオブジェクト指向言語が委譲をクラスの継承関係にもとづいておこなうのに対し、委譲を「プロトタイプ」と呼ぶ既存のオブジェクトに投げる、といったようにしておこなう点が特徴である。そのために例えば、新しいオブジェクトを作る際には、「クラスのインスタンスを作る」のではなく、「既存のオブジェクト(プロトタイプ)のクローンを作る」というようなスタイルになる。
とあります。ふーっむ...
なんとなく分かった気にはなるのですが、それで? っという感じ。具体的なイメージが今一つわいてきません。具体的な振る舞いを見てみます。
JavaScriptのオブジェクト生成
クラスベースのオブジェクト指向言語では、クラスをnew
することで新しいオブジェクトを作りますが、プロトタイプベースのJavaScriptではオブジェクトの作り方がいくつか存在します。
- オブジェクトリテラルで作る
var o = { a: 'a', b: 'b', name: 'Object o', show: () => {console.log(this.name);} };
- 空のオブジェクトを作り、そこにプロパティを登録していく
var o = {} o.a = 'a'; o.b = 'b(; o.name = 'Object o'; o.show = function () {console.log(this.name);};
function createObject () { this.a = 'a'; this.b = 'b'; this.name = 'Object o2'; this.show = function () {console.log(this.name);} } var o2 = new createObject();
- Object.createで作る
オブジェクトOから新しいオブジェクトを作るという振る舞いです。
var o1 = Object.create(o);
記述の仕方は異なりますが、最後のObject.createを除き、作られるオブジェクトはどれも同じく次のような形になります。
{a: "a", b: "b", name: "Object o", show: ƒ} a: "a" b: "b" name: "Object o" show: ƒ () __proto__: Object
厳密には違う部分もありますが、概略はリテラルでも、コンストラクタでも、最初に新しいオブジェクトを作り、それにプロパティを追加していくという2番目の記述と同じ流れになります。
ここで出来上がったオブジェクトには__proto__
という要素がありますが、これは後で深堀します。
最後のObject.create
では すでに存在するオブジェクトo
を元に新しい新しいオブジェクトを作り、その参照をo1
という変数に代入しています。
ですので、出来上がったオブジェクトは先の3っつのやり方とは若干違う形になっています。
{} __proto__: Object
作成されたオブジェクトの形に若干の違いはあるものの、どのやり方も出来上がりのオブジェクトには必ず__proto__
というプロパティが登録されています。
このプロパティには元にしたオブジェクトへの参照が格納されます。
最初の三つのやり方ではObject
というJavaScriptのビルトインオブジェクト(処理系に最初から準備されているオブジェクト)を元にしているので、protoにはObjectへの参照が入ります。
最後のObject.create
ではオブジェクトo
を元にo1
を作っているので__proto__
にはo1
への参照が格納されています。
共通して言えることは
何がしかの既に存在するオブジェクトをクローンして新たなオブジェクトを作り、それを拡張する。そして__proto__
というプロパティに元にしたオブジェクトへの参照を保持している
という点です。
まさに、「既存のオブジェクト(プロトタイプ)のクローンを作る」が意味するところですね。
プロトタイプベースはインスタンスベースとも呼ばれています。上で見たJavaScriptのオブジェクトの作り方は、まさにのインスタンスベースという呼び名にピッタリ。これでようやく ”そういうことかっ!”と腑に落ちました。
プロトタイプチェーンで継承を実現?
プロトタイプベース(すなわちインスタンスベース)と呼ばれる理由は自分なりに腑に落ちましたが、オブジェクト指向言語が持つ特徴をどう実現しているかは、まだ明らかにできていません。次はその点を考察してみます。
オブジェクト指向ではよく4っつ特徴が上げられます。
一方でプロトタイプベースでは以下の特徴があげられます。
- クロージャー
- プロトタイプ
- プロトタイプチェーン
よくクロージャーでカプセル化を、プロトタイプチェーンで継承を実現しているといわれているのでオブジェクト指向の4っつの特徴と対応付けてみると以下のような関係といえそうです。
クラスベース | プロトタイプベース |
---|---|
カプセル化(情報隠蔽) | クロージャー |
継承 | プロトタイプチェーン |
多態性 | ? |
動的束縛 | ? |
でも個人的な感覚では、実はプロトタイプベースであること自体が継承の実現なのではないかと、そしてプロトタイプチェーンは継承よりも多態性と動的束縛の一部としてとらえるべきではないかと思いました。
なぜなら、JavaScriptのオブジェクトは『既存のオブジェクトをクローンして拡張、改造することで作られている』からです。 クローンすること自体が元のオブジェクトを継承することであり、クローンして拡張、改造することが派生することと解釈できるからです。
そうとらえるとスッキリと理解が進みました。
改めて4っつの特徴と対応は次の様にとらえるとわかりやすいです。
クラスベース | プロトタイプベース |
---|---|
カプセル化(情報隠蔽) | クロージャー |
継承 | プロトタイプベース |
多態性 | プロトタイプチェーン |
動的束縛 | プロトタイプチェーン |
クローンして拡張・改造することを繰り返し、その過程を記録していくことでクラスベースの継承とよく似た関係づけを表現できる。それがプロトタイプチェーンであると位置付けると更に理解が進みます。
「積極的に継承を実現するためにプロトタイプチェーンを導入した」
というより、
「プロトタイプベースでオブジェクトを作っていったら継承ができちゃった」
というちょっと消極的な、もしくは副産物的な位置づけでプロトタイプチェーンを考えると、解釈がスッキリしていきました。
プロトタイプチェーンのメリット
プロトタイプチェーンによる継承は副産物であるという解釈を示しましたが、継承のメリットは二つあると思います。
同じ振る舞いを共通化できること。
この点ではプロトタイプチェーンによる継承は効果的でコードのクローンを削減できリソースの浪費を防げます。継承関係を元にした多態性や動的束縛による抽象化
これはクラスベースのオブジェクト指向にとって、その根幹とも言える大きな要素ですが、JavaScriptでこれらをどう実現するのかについては、全く考察が足りていません。
C++やJavaなどのクラスベースのオブジェクト指向では「型」の概念がしっかりしているので、多態性、動的束縛を使う場面をイメージしやすい。(抽象化された型で認識し、具象化した振る舞いを実現するという感じ)
一方、型の制約がない(緩い)プロトタイプベース(この場合、JavaScriptに限定するのが適切かもしれない)で多態性と動的束縛をどう使うのかイメージできないでいます。
仮に、JavaScriptにおけるオブジェクト指向の範囲が
継承とカプセル化に限定されていて、多態性と動的束縛への関心が薄いとしたら、
- JavaScriptをオブジェクト指向言語と表現するのは適切なのだろうか?
- プロトタイプチェーンを継承と位置付けることの意義はなんなのか?
- むしろ、単純に振る舞いの共通化とリソース浪費防止のためにプロトタイプチェーンが存在していると解釈したほうが自然ではないか?
- JavaScriptのオブジェクト指向とはインスタンスからインスタンスを作るという意味でとらえるのが適切では?
などなどの思いがわいてきます(このあたりは別の議論ですね)。
ここまで、
プロトタイプベースということとJavaScriptのオブジェクト指向について考察してきました。
次は少し視点を変えて
メモリモデルの観点から、プロトタイプベースであることを捕えてみたいと思います。
メモリモデル
プロトタイプベースを採用しているJavaScriptはブラウザ上で動作するために生まれた言語といっても過言ではないでしょう。対照的に、その大半がネイティブや仮想環境上で動作することを前提としたクラスベースとではメモリ管理の点でも大きな違いがあります。
クラスベースのメモリモデル
おなじみのスタティック、コード、スタック、ヒープ領域のメモリモデルが代表的です。 スタティック領域には静的な変数やコードを、コード領域にはクラスのメソッドを静的に配置します。型が分かればメソッドのコード部分は不変なので固定的に配置できます。
ヒープ領域にはオブジェクトプロパティ(インスタンス毎の変数やオブジェクトへの参照など)を、スタックにはAuto変数を動的に配置するメモリ管理のモデルです。 クラスの型情報があらかじめわかるので、実行時ではなくアプリ起動時にメモリをレイアウトできます。
実装上の細かな違いはあるにせよ、クラスベースのオブジェクト指向言語の大半がこのモデルを採用していると言えるでしょう。プロトタイプベースのメモリモデル
JavaScriptインタプリタがスクリプトを処理するごとにオブジェクトプロパティをメモリ上に配置し、ハッシュ関数などを使ってアクセス用のインデックスを作る方式がとられています。JavaScriptでは関数、メソッドも変数と同じオブジェクトとして扱われるので、メモリ上は変数も関数も同じようにプロパティとして配置されることになります。 (実際の実装では動的にクラス情報を構築していくHidden Classという手法がとられている(参考URL))
この様なメモリレイアウトの違いが生じた理由はやはり、ブラウザ上で動作させるためだと思います。 JavaScriptはブラウザがWebサーバからjsファイルをダウンロードして実行します。HTMLに埋め込まれたコードもあれば独立したjsファイルとしてHTMLとは別にダウンロードしたり、アプリやライブラリを個別にダウンロードするような場合もあります。UXの観点からはレスポンスを確保する必要もあるでしょう。こうした状況ではクラスベースの様にあらかじめメモリレイアウトを準備するというのは困難な様に思います。 (※ブラウザがJVM(Javaの実行環境)の様な環境を提供する方向になっていたら状況は違ったかもしれませんが…)
この様にJavaScriptは、
実行環境上の制約からクラスという概念を持ち込むのが困難であった。そのため関数やメソッドも変数などと同様にプロパティとして動的にメモリに配置せざるを得なくなり、必然的にプロトタイプベース(インスタンスベース)という方式を採用するにいたったと言えるのではないでしょうか。
なぜプロトタイプベースなのか
冒頭で述べた疑問への答えは、
JavaScriptがブラウザ上で動作する唯一の言語であるということでしょう。
「なぜプロトタイプベースなの?」
ブラウザで動作させるという命題から クラスベースの様な型情報に基づくメモリレイアウトの事前準備ができないという制約があった。 そこで考え出されたのが、スクリプトの実行とともに動的にオブジェクトを作っていく、既存のオブジェクトをクローンし 拡張していくというやり方なのでしょう。これがプロトタイプベースの根源となり、JavaScriptがプロトタイプベースを採用した(もっと言うと採用せざるを 得なかった)理由なのではないか理解するに至りました(間違ってたら教えてください)。「JavaScriptのオブジェクト指向が意味するところ」
JavaScriptの使命はオブジェクト指向の実現ではなく、ブラウザ上での動作であった。だがWebアプリの大規模化に伴い、エンジニアの多くがクラスベースのオブジェクト指向経験者で、プロトタイプベース経験者が少ないという業界の事情もあり、エンジニア確保の意味からJavaScriptにもオブジェクト指向的な設計・開発のアプローチも取り入れなければならないというニーズが強かった。そこでプロトタイプベースにクラスベースの特徴を当てはめてきたのがJavaScriptのオブジェクト指向である。
こういった文脈で解釈すると、霧が晴れて整理できたように思います。
まとめ
ここまでを総括すると、
プロトタイプベースのオブジェクト指向とクラスベースのオブジェクト指向は別者。
プロトタイプベースのオブジェクト指向はその名のとおり、インスタンスからインスタンスを作るという意味でのオブジェクト指向であり、クラスベースのオブジェクト指向アプローチも一部実現できる
程度で理解しておくのが最適の様に思いました。そうすることで開発する際、オブジェクト指向的な設計アプローチにどこまでこだわるべきか、境界線が見えてくると同時に、プロトタイプチェーンやクロージャーを素直に理解していけるのではないでしょうか
謝辞
長々と書きましたが、最後まで読んでくださりありがとうございました。何かのお役に立てられれば幸いです。