読者です 読者をやめる 読者になる 読者になる

JavaScriptをプロトタイプベースのオブジェクト指向言語と言うべきではない

勘違いしている方も結構多いと思ったので、これの解説。

ウェブ上の記事を眺めていると、JavaScriptをプロトタイプベースのオブジェクト指向言語(以下OOPと書く)と説明している例がよく散見される。この書き方は間違ってはいないかもしれないが、もはや誤解を生むだけである。

そう言う理由には、ES6からはclass構文があるため、通常のクラスベースのOOPと一切何も変わらなくなっているからというのがある。だが最も大きな理由は、JavaScriptはプロトタイプベースのOOPの中でも異端であり、本来のプロトタイプベースのOOPとはあまり似ていないからである。

JavaScriptでは、new演算子を使ってオブジェクトを生成したり、prototypeオブジェクトを使ってオブジェクトのメソッドやプロパティを定義したりするが、他のプロトタイプベースのOOPではnew演算子は無いし、prototypeオブジェクトも無い。

この記事では、本来のプロトタイプベースのOOPがどういったものであり、JavaScriptとはどう違うのかを説明する。

JavaScriptの確認

まずは、JavaScriptでオブジェクトを定義・生成する方法を確認していく。

コンストラクタとして、まずは関数を定義する。

var MyObject = function(name) {
  this.name = name;
};

次に、定義した関数オブジェクトのprototypeプロパティにプロパティを定義することで、オブジェクトがインスタンス化された時のプロパティやメソッドを定義できる。

MyObject.prototype = {
  createGreeting: function() {
    return 'Hello, ' + this.name;
  }
};

オブジェクトをインスタンス化する際には、new演算子を使って生成する。

var obj = new MyObject('JavaScript');
obj.createGreeting(); // => 'Hello, JavaScript'が返る

さて、プロトタイプベースのOOPではこういったオブジェクトの定義や生成はどうやって行うのか。

プロトタイプベースのOOPでは?

純粋なプロトタイプベースのOOPでは、クラスやJavaScriptのprototypeオブジェクトのようなものは無い。新しいオブジェクトを作るには、new演算子によるインスタンス化ではなくオブジェクトの複製を作ることで自分が欲しいオブジェクトを作成していく。

ここでは、プロトタイプベースのOOPの例として、ioという言語で説明する。ioは純粋なプロトタイプベースのOOPだ。また、ioには制御構文用の文法が無く全てが(if文ですらも)メソッドである。

まずは、先ほどJavaScriptで書いたようなMyObjectをioで作ったのが以下だ。

// オブジェクトの複製を作る
MyObject := Object clone
// メソッドの定義
MyObject createGreeting := method("Hello, " .. self name)
// プロパティの定義
MyObject name := "Your Name"

ioの文法のほんの少し説明すると、:=演算子は変数を宣言する演算子である。また、JavaScriptではオブジェクトのプロパティのアクセスに"."演算子を使うが、ioでは単にスペースを使う*1。また、引数がない場合にはメソッド呼び出しに()は不要だ。

hoge := "hogehoge" // JSだとvar hoge = "hogehoge";になる
hoge size // JSだとhoge.sizeになる
hoge print // printメソッドの呼び出し

先ほどの例では、MyObjectというオブジェクトを作っているが、重要なことはこのMyObjectはすでに利用できる一個のオブジェクトだということだ。例えば、ここでcreateGreetingメソッドを呼び出せばそれはそのまま動作する。JavaScriptではこうはいかない。

MyObject createGreeting // "Hello, Your Name" が返る

ioでは新しくオブジェクトを作るときには、クラスをインスタンス化してオブジェクトを生成する代わりに、すでにあるオブジェクトを複製することでオブジェクトを生成する。

myobj := MyObject clone
myobj name = "io"
myobj createGreeting // => "Hello, io" が返る

さて、ioのやり方を見るとJavaScriptとはだいぶ違うことがわかる。ioなどのプロトタイプベースのOOPではオブジェクトの定義や継承や生成などの処理を全てcloneを通じて行えるので非常にシンプルだ。それに比べるとJavaScriptのprototypeオブジェクトを使うやり方は何かちぐはぐな印象を与えてくる。

GoFのデザインパターンの一つにオブジェクトの生成に関わるプロトタイプパターンというのがあるが、プロトタイプベースのOOPではこのデザインパターンを言語の設計レベルで全面的に採用することでnew演算子やクラスの存在を消してしまっている。

JavaScriptでは、プロトタイプ(ひな形)となるオブジェクトは関数オブジェクトのprototypeプロパティに代入したオブジェクトになるが、これは通常のオブジェクトとしてそのまま利用できるわけではないし、これはある意味クラスを表現するクラスオブジェクトのような形になっている。

ioではそういったオブジェクトは無くプロトタイプ(ひな形)となるのはそのオブジェクト自身である。ioではオブジェクトの複製を通じてのみ新しいオブジェクトを生成できる。

こうやって書いてみると、JavaScriptが通常のプロトタイプベースのOOPとどれほどかけ離れているかがわかる。JavaScriptはクラスを持たないためプロトタイプベースのOOPとして分類できるのかもしれないが、その仕組みを観察してみると、クラスベースとプロトタイプベースの合いの子のような妙な形になっている。

終わりに

個人的な意見になるが、JavaScriptをプロトタイプベースのOOPと言い切っている人は、プロトタイプベースのOOPがどういったものかを理解していない、もしくはioやSelfのような言語を触ったことがない人だろう。なぜならJavaScriptは何の留保も無しにプロトタイプベースと言い切るにはあまりに奇妙だからだ。何にせよ、ES6からはclass構文が追加されたので、JavaScriptをプロトタイプベースのOOPと言うのはもはや適切ではないだろう。

追記: ブックマークコメントへの返信

id:oscdis765 Even though ECMAScript includes syntax for class definitions, ECMAScript objects are not fundamentally class-based such as those in C++, Smalltalk, or Java.
http://b.hatena.ne.jp/entry/249961310/comment/oscdis765

id:otherworld インスタンス化に使うキーワードが違うだけで、プロトタイプチェーンを辿ってプロパティ/スロットを解決するというコンセプトは同じじゃ?? jsをクラスベースと呼ぶ方が無理がある
http://b.hatena.ne.jp/entry/249961310/comment/otherworld

この記事では、JavaScriptをクラスベースのOOPと呼ぶべきという主張をしているわけではないです。プロトタイプベースの、という枕詞を付けても説明として何の意味もないという話です。

*1:余談だがioではプロパティと言わずにスロットと呼ぶ