Javascriptのオブジェクト指向?

前回ざっくりJavascriptのオブジェクト、関数定義についてまとまりなくメモした。でも「で、どうしたら良いのか?」がはっきりしない。柔軟性があるといえば聞こえが良いが、どうとでも記述できてしまうのでよくわからない。

関数式による一般的なクラス定義

オブジェクト指向でアプリケーションを構築する場合、データや機能によってなんらかの形で「まとめた」ほうが整理し易い。JavaやC++ではクラスとしてまとめる。

Javascriptにはクラスの概念がないが、それっぽいことが、functionnewを使って真似るのが一般的である。この方法は賛否あるが、JavaやC++経験者は飲み込みやすい。

// コンストラクタFunction
var Human = function( name, age, gender ){
    // thisはnewを使ってインスタンス化されたオブジェクトを指すようになる
    this.name = name;
    this.age = age;
    this.gender = gender;
}
 
// 関数はプロトタイプに定義すればインスタンスごとにコピーされずに無駄がなくなる
// プロトタイプでなくても構わないが、メリットがない。
Human.prototype.say = function(){
    return "hello";
}
Human.prototype.status = function(){
    return "name:"+this.name+" age:"+this.age+" gender:"+this.gender;
}
 
// インスタンス化
var hu = new Human( "nameless", 0, "unknown");
// 実行
console.log(hu.say()); // hello
console.log(hu.status()); // name:nameless age:0 gender:unknown

関数式クラスの継承

JavaやC++を真似るとなると、継承もできないでは具合が悪いと考える人も多い。一般的な方法は、Object.createでプロトタイプをコピーしたオブジェクトを作成し、基底クラスのプロパティを引き継ぐため、基底クラス.callをコンストラクタ内でコールする。

// 派生クラスを定義
var Japanese = function(name, age, gender, job, state){
    // すべてのfunctionが持つcall関数を実行する。
    // 第1引数で指定したオブジェクトをthisとしてHuman関数が実行される。
    // これによりHumanで定義したプロパティがJapaneseをthisとして代入される。
    Human.call(this, name, age, gender);
     
    // 派生クラスのメンバを追加
    this.state = state;
    this.job = job;
}
 
// Object.createでプロトタイプとするオブジェクトを指定して新しいオブジェクトを作成する
Japanese.prototype = Object.create(Human.prototype);
//constructorのプロパティが自身のコンストラクタFunctionを指すようにする
Japanese.prototype.constructor = Japanese;
 
// メソッドのオーバーライド
Japanese.prototype.say = function(){
    return "konnichiwa";
}
// メソッドの追加
Japanese.prototype.status2 = function(){
    return "I live in "+this.state+". my job is "+this.job+".";
}
 
// インスタンス化と実行
var jp = new Japanese("taro",24,"m","salesman","tokyo");
jp.say(); //konnichiwa
jp.status(); // name:taro age:24 gender:m
jp.status2(); // I live in tokyo. my job is salesman

これはこれで良いと思う。しかし一方では、javascriptにおいて「クラスを模倣する必要があるのか?」「継承がそんなに必要なのか?」という疑問がどうしてもある。javascriptなんだから、JavaやC++よりも劣っている箇所があってもいいので、javascriptの流儀みたいなものに従う、いっそのことnewを使わない場合どうなるのか考えてみる。

オブジェクトの定義と生成

そもそもjavascriptにはクラスというものがなく、あるのはオブジェクトである。オブジェクトの生成は、newObject.create()を使うか、リテラル定義する方法がある。メンバとなるプロパティや関数は、以下のようにドット表記で追加するか、リテラルスコープ内でコロン:で定義する。

ECMAScript 6から、classキーワードで定義可能になる予定

var Human = {
    name:"",
    age:0,
    gender:"",
    say:function(){
        return "hello";
    },
    status:function(){
        return "name:"+this.name+" age:"+this.age+" gender:"+this.gender;
    }
}
 
// Humanをプロトタイプとしたオブジェクト生成
var jp = Object.create(Human);
 
// プロパティの初期化
 
// Humanプロパティで初期化すると、Humanをプロトタイプとして
// 生成されたオブジェクト全てに内容が反映される
// Human.name = "taro";
 
// Object.create()の第2引数で初期化する方法と、
// オブジェクトjpプロパティで初期化するのと同じ
// var jp = Object.create(Human,{name:{value:"taro"}});
 
// また連想配列のように記述することも可能。文字列でプロパティが指定できる
// jp["name"] = "taro";
 
jp.name = "taro";
jp.age = 24;
jp.gender = "m";
jp.job = "salesman";
jp.state = "tokyo";
 
jp.say = function(){
    return "konnichiwa";
}
jp.status2 = function(){
    return "I live in "+this.state+". my job is "+this.job+".";
}
 
jp.say(); //konnichiwa
jp.status(); // name:taro age:24 gender:m
jp.status2(); // I live in tokyo. my job is salesman

結果は先の関数式タイプと結果は同じで、定義したプロパティとfunctionの構成も同じ。 オブジェクト式の場合、コンストラクタがないので、オブジェクト生成後にプロパティを設定している。継承というよりも、生成したオブジェクトにプロパティを生やすイメージに近い。継承というより、拡張といった感じ。

結果を関数式に合わせたため、上記のような記述になったが、jpに代入することが分かっているのであれば、Humanでプロパティを定義すること自体意味がない。またオブジェクトを生成するたびに上記のコードを記述するのは冗長なので、恐らくmakePerson()のような関数でまとめるだろうし、モジュール化もするだろう。どちらが優れているかとかどうでもいい気がしてきた。。

少なくともJavaやC++を模倣するのであれば、関数式が良いと思うし、模倣する必要がなく直感的に「生やしていく」ならオブジェクト式が良いのかな?と感じる。