クラスベースのオブジェクト指向な JavaScript

もうすぐ院試なんで院試に向けて勉強している今日この頃。 息抜きにクラスベースっぽいオブジェクト指向JavaScript について書いてみます。

JavaScript でクラス (っぽいもの) のコンストラクタを定義する方法といえば

Object1 = function( aName, aType ) {
	// private なインスタンス変数
	var name = aName;
	// public なインスタンス変数
	this.type = aType;
	// public なインスタンスメソッド
	this.getName = function() {
		return name + " (length: " + getNameLength() + ")";
	}
	// private なインスタンスメソッド
	var getNameLength = function() {
		return name.length;
	}
}

という感じで変数もメソッドも全部関数内に書いてしまう方法や

Object2 = function( aName, aType ) {
	// この方法だと private なインスタンス変数が使用できない
	// (プロトタイプを使用して定義したメソッドからアクセスできない)
	this.name = aName;
	this.type = aType;
}
Object2.prototype.getName = function() {
	return this.name + " (length: " + this.getNameLength() + ")";
}
Object2.prototype.getNameLength = function() {
	return this.name.length;
}

という感じで prototype を使う方法など、いくつも方法はあります。

前者は private な変数やメソッドが使えて便利だなー、と思ってたんですがよく考えるとメモリ空間を無駄に消費しちゃう気が・・・。 でも 『初めての JavaScript』 を読んでると全くメモリ使用量について触れていないし、「関数リテラル (function() {...} の構文) が解析されるのは 1 回だけ」 って書いてるし (つまり一見するとインスタンスごとにメソッドが生成されるように見えるけど実際はそうではないのか?)、良くわかんなかったんで実際に試してみました。

使用したコード

// ===== 名前空間もどきの宣言 =====
namespace = {}

// ===== オブジェクト定義 =====
/**
 * プロトタイプを使用せずにメソッドを定義したオブジェクト
 */
namespace.Object1 = function( aName, aType ) {
	// private なインスタンス変数
	var name = aName;
	// public なインスタンス変数
	this.type = aType;
	// public なインスタンスメソッド
	this.getName = function() {
		return name + " (length: " + getNameLength() + ")";
	}
	// private なインスタンスメソッド
	var getNameLength = function() {
		return name.length;
	}
}
/**
 * プロトタイプを使用してメソッドを定義したオブジェクト
 */
namespace.Object2 = function( aName, aType ) {
	// この方法だと private なインスタンス変数が使用できない
	// (プロトタイプを使用して定義したメソッドからアクセスできない)
	this.name = aName;
	this.type = aType;
}
namespace.Object2.prototype.getName = function() {
	return this.name + " (length: " + this.getNameLength() + ")";
}
namespace.Object2.prototype.getNameLength = function() {
	return this.name.length;
}

// ===== main 関数 =====
namespace.main = function() {
	
	var abc  = new namespace.Object1("aaa", "typeA");
	var abc2 = new namespace.Object1("bbb", "typeB");
	window.alert( abc.getName === abc2.getName ? "一緒" : "違う" );
	// => "違う"
	
	abc  = new namespace.Object2("ccc", "tmpA");
	abc2 = new namespace.Object2("ddd", "tmpB");
	window.alert( abc.getName === abc2.getName ? "一緒" : "違う" );
	// => "一緒"
	
}

// ===== 実行 =====
namespace.main();

こんなコードを書いて、Firefox 3 で実行してみました。

結果は予想通りで、前者の方はインスタンスごとにメソッド用のメモリ空間を確保するようです。 後者は当然ながら同じメモリ空間を参照していました。 むぅ、やっぱり prototype を使ったほうがいいのかなぁ。 悩みます。

コンストラクタの定義方法

IT 戦記の大分古い記事ですが、カスタムオブジェクトの定義方法を色々挙げている記事 があります。 amachang さんは一番下の方法を使ってるということですけど、確かに一番下の方法は書きやすくていいですねー。 真似しようかな。

また、クラスの生成と継承について自分でも書きました ので、参照してください。