Javascriptでオブジェクト指向(クラス,オーバーライド,カプセル化)

Javascriptでオブジェクト指向

Javascriptはプロトタイプベース(インスタンスベース)なOOP。
JavaはクラスベースなOOP。

関数はクラス

function Foobar() {};

// 関数に new でインスタンスを生成できる。
// この関数はコンストラクタとして働く
var foobar = new Foobar();

alert( foobar.constructor ); // function Foobar() {}
alert( foobar instanceof Foobar ); // true

JavascriptではJavaのようなクラスは存在しないけど、関数がクラス的な存在として使うことができる。

クラスの定義

var Foobar = function(arg) {
	// このような定義にすると new した全てのインスタンス
	// 対してここで代入している内容が別々に作られてしまう。
	this.var = arg;
	this.method = function(str) {
		alert(str);
	};
};

// プロトタイプに定義することでコンストラクタ内で関数定義する
// ときのような問題点は解消される。
Foobar.prototype.pVar = 123;
Foobar.prototype.pMethod = function (str) {
	alert(str);
}

// fooインスタンス自身が持つメンバがコールされる
var foo = new Foobar(999);
foo.method(foo.var); // 999

// プロトタイプに定義したメンバにアクセスする。
// これは foo.pMethod == undefined の場合は
// foo.prototype.pMethod にアクセスする仕様になっている為。
// foo.pVar == undefined なら foo.prototype.pVar にアクセス。
foo.pMethod(foo.pVar); // 123

プロトタイプを利用する場合は、生成したインスタンスの分だけメソッドが生成されるというような無駄が生じない。

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

// 親プロトタイプとするオブジェクト
MetaVar = function() {};
MetaVar.prototype.method = function() {
	alert("MetaVar");
};

Hoge = function() {
	// 親のコンストラクタ実行
	MetaVar.apply(this, arguments);
};
// Hoge に MetaVar を継承させる
Hoge.prototype = new MetaVar;

var instance = new Hoge();
instance.method(); // alert「MetaVar」
/* instance.method == undefined のため
 * Hoge.prototype.method(instance.constructor.prorotype.method) にアクセスする
 */

alert( instance instanceof MetaVar ); // alert「true」

アクセスしたプロパティが未定義の場合、プロトタイプのプロパティにアクセスする。
プロトタイプのプロパティがなければ更にその上のプロトタイプのものを見に行く(プロトタイプチェーン)。

instanceof演算子もプロトタイプチェーンを辿る。

アクセサ

var xxx = {
	yyy : undefined,
	get zzz () { return this.yyy; },
	set zzz (arg) { this.yyy = arg; }
};

alert(xxx.yyy); // undefined
alert(xxx.zzz); // undefined

xxx.zzz = "piyopiyo";
alert(xxx.yyy); // piyopiyo
alert(xxx.zzz); // piyopiyo
alert(xxx.zzz = "hogehoge"); // hogehoge
// このスクリプトは動かない
var ppp = {
	qqq : undefined,
	get qqq () {} // フィールドと同じ名前はエラー
};

get, set でゲッター・セッターの定義はできるものの、隠蔽できるわけではない。

オーバーライド?

プロトタイプチェーンを利用することでオーバーライドっぽい動作を実現できる

プロトタイプチェーンを使用しない場合

ただ単に関数オブジェクトを別のものにするだけでも似たようなことはできる。

// ただ単に関数自体を別物にするパターン
var f;

f = function() {
	alert("HOGE");
};
f(); // HOGE

f = function() {
	alert("PIYO");
};
f(); // PIYO

ただし、この場合は最初の関数は消えてしまう。
※これをオーバーライドと呼べるのかは知らない

プロトタイプチェーンの利用

var Super = function(isAlert) {
	// コンストラクタ
	if ( isAlert === false ) {
		alert("SuperConstructor");
	}
};
Super.prototype.func = function() {
	alert("Super.prototype.func");
};

var Sub = function() {
	// this.constructor で Sub を取得
	// this.constructor.prototype.constructor で
	// スーパークラスを取得して
	var SuperClass = this.constructor.prototype.constructor;
	// スーパークラスのコンストラクタを呼び出す
	SuperClass.apply(this, arguments);
	
	// 派生クラスのコンストラクタ
	alert("SubConstructor");
};
Sub.prototype = new Super(false);
Sub.prototype.func = function() {
	alert("Sub.prototype.func");
};

var obj = new Sub();
obj.func(); // Sub.prototype.function
obj.constructor.prototype.func.apply(obj); // Super.prototype.func

// 削除すると…
delete Sub.prototype.func;
obj.func(); // Super.prototype.func
var Super = function() {
	this.number = 123;
};
Super.prototype = {
	number : 123,
	getNumber : function() {
		return this.number;
	}
};



var A = function(number) {
	if ( number != null ) {
		alert( "number changed" );
		this.number = number;
	}
};
A.prototype = new Super();
A.prototype.getNumber = function() {
	return this.number * 10;
};

alert( new A().getNumber() ); // 1230
alert( new A(987).getNumber() ); // number changed -> 9870



var B = function() {
	this.number = 567;
};
B.prototype = new Super();
alert( new B().getNumber() ); // 567

よく分からない prototype の挙動

var A = function() { alert("A"); };
A.prototype.hoge = "tesu";

var B = function() { alert("B"); };
B.prototype = { hoge : "tesu" };

alert( new A().constructor );
alert( new B().constructor );

new A().constructor の結果が function() { alert(“A”); } なのに
new B().constructor の結果が function Object() { [native code] } になってしまう…なんで???

おかげで以下のようなエラーが出ることがある。

var A = function() {};
A.prototype.exec = function() { alert("A"); };

var B = function() {};
B.prototype =  {
	exec : function() { alert("B"); }
};

var C = function() {};
C.prototype.exec = function() { alert("C"); };

C.prototype = new A();
new C().constructor.prototype.exec(C); // 「A」とアラートが出る

C.prototype = new B();
new C().constructor.prototype.exec(C); // execメソッド無いとブラウザ激怒

スーパークラスのメソッドを呼べないじゃん…
何が起きてるんだろう。いろいろと気持ち悪い。
誰か教えてください orz

インターフェース

Javascriptの機能としてインターフェースは存在しないので、インターフェースっぽいものを勝手に実装して下さいってことらしい。

参考:JavaScriptでJavaのInterfaceを実現する方法 – yinkywebの日記

カプセル化

var Class = (function() {
	// privateっぽいもの
	// この環境でのみアクセス可能
	var privateField = "private!!!";
	var privateMethod = function() {
		alert(privateField);
	};

	var clazz = function() {
		alert("コンストラクタ");
	};
	clazz.prototype.publicMethod = function() {
		privateMethod();
	};

	return clazz;
})();

var obj = new Class();
obj.publicMethod();
obj.privateMethod(); // エラー

可視性を指定するものなんて無いのでクロージャで隠蔽する。protected なものって作れるのかな。

関連記事

2012年9月9日 | コメント/トラックバック(2) |

カテゴリー:JavaScript タグ:

トラックバック&コメント

この投稿のトラックバックURL:

トラックバック

コメント

  1. ひよこ より:

    var A = function() {};
    // この時 A.prototype.constructorに”自動で”Aが代入される。

    A.prototype = {methodA: function(){}};
    // prototype にオブジェクトを代入することで、
    // 自動で代入されたA.prototype.constructorはA.prototypeごと上書きされる。

    A.prototype = {constructor: A, methodA: function(){}};
    // もしconstructorプロパティを利用するなら、このようにしておく

    var B = function() {};
    B.prototype = new A(); // EcmaScript5な環境ならば Object.create(A.prototype); とするべき
    var b = new B();
    // このとき b.constructor が A になるのは new A() のconstructorプロパティを読んでいるから

    // b.constructor は A ではなく B を返すのが正しい(と思う)ので、
    B.prototype = new A();
    // の後で
    B.prototype.constructor = B;
    // としておいたほうがいいかもしれない。

    // C は A を継承して、 methodAを書き換える
    var C = function(){};
    C.prototype = {constructor: C, methodA: function(){ /* override */ }};
    var c = new C();
    // もし C の methodA ではなくて A のmethodAを呼び出したいなら
    // constructorプロパティは利用せず
    A.prototype.methodA.call(c);
    // のようにする。

    // constructorプロパティは文字通りコンストラクタを保持するプロパティ。
    // 本来は、継承元との関係はない。

    • Fernweh より:

      __proto__が使えない場合にどうやって親にアクセスするのかと思いましたが、親.prototype.メソッド.call(インスタンス); のように 親オブジェクトを直接指定するのですね。これをやりたくなくて constructor から呼び出していたのですが、ひよこさんの説明を見ていろいろ勘違いしすぎていることが分かりました orz

      とはいえコメントのおかげでどんな挙動をしているのかなんとなく分かりました。
      わかり易い説明をしていただいてとても嬉しいです。


コメントをどうぞ

このページの先頭へ

このエントリーをはてなブックマークに追加 にほんブログ村 IT技術ブログへ
イメージ画像