JavaScriptでもクラスな考え方をしたい
「JavaScriptにおいて、どうクラスを書くか?」の問いに
「コレだ!」ってのがなかなか見つからなかったが、やっと自分の中で
「こう書くとシックリくる」ってのが見つかったのでメモ、って話。
JavaScript言語では「クラス」という概念は無い(※1)。
無いが、「Google流 JavaScript におけるクラス定義の実現方法(※2)」
とかでも「クラス」って表現は普通に使われている。
さて、疑似的に「クラス」を実装するにはどう書けばよいか?
クラスの記述方針
以下のような方針でクラスを書くと、しっくり来た。
- グローバル空間で「var クラス名 = function(){}」にて『クラスを定義』する。
- 静的メンバ変数のprivateスコープ化は、クロージャーを使って実現する。
- 動的メンバのprivateスコープ化は、『諦める』。
この方針であれば、ある程度のprivate化を行いながらも、
メソッドはprototypeで定義できるので、「メモリへの負荷が無い」
っていうプロトタイプベースの恩恵も享受できる(※3)。
サンプルコード
具体例としては、以下のようなコードになる。
なお、jqueryとjquery.cookie.js(※4)を利用している。
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<script type='text/javascript' src='./jquery-1.12.3.js'></script>
<script type='text/javascript' src='./jquery.cookie.js'></script>
<script>
var CookieBind = function( cookieName ){
// コンスタラクタ
// ⇒グローバル変数で定義する。
//
this._cookieName = cookieName;
// このタイミングでは未定義のメソッド、も指定可能。
// ⇒「this」は実行時解決されるから。
this.itsLoadedStr = this.load();
};
(function(){
//
// 続いて、prototypeを用いてメソッドを定義
// ⇒即時関数の中で行うことで、静的praivate変数/メソッドを実装可能。
// クロージャーの仕組みを利用している。
//
// ⇒動的なpraivate変数は諦める。
// (メソッドは、適宜thisを渡すことで動的な実装は可能)。
//
// ⇒JavaScriptの場合は、メソッドは「静的な枠」+「動的なメンバ変数」で実装する。
//
// ※prototype内で function(){} 定義するときは、thisに注意。
// ⇒ function(){ の内部 } では、thisは呼び出し元になり、このクラスインスタンスを指さない。
// ⇒ function() の親側で var self = this; して、function(){ の内部 } では、
// self.~の形式で呼ぶこと(selfはクロージャーになる)。
//
var COOKIE_OPTION = { expires: 7 }; // 静的プライベート変数
CookieBind.prototype.save = function( str ){
$.cookie( this._cookieName, str, COOKIE_OPTION );
};
CookieBind.prototype.load = function(){
return $.cookie( this._cookieName );
};
CookieBind.prototype.getValue = function(){
return this.itsLoadedStr;
}
CookieBind.prototype.getCookieName = function(){
return this._cookieName;
}
// prototype = {}; は禁止。いろいろ破壊するので、扱いが面倒。
}());
// クラス継承はこちら。
var CookieBind2Input = function( cookieName, id_input, id_button ){
var self = this;
CookieBind.call( this, cookieName ); // 継承元のコンスタラクタを明示的に呼び出す。
this._idInput = id_input;
this._idButton = id_button;
this.load2Input();
$("#" + this._idButton).click( function(){
self.saveByButton(); // この位置は、thisが異なるので注意。
});
};
// 親のメソッドを「継承」する。
CookieBind2Input.prototype = Object.create( CookieBind.prototype );
// サブクラスでのメソッド追加も、同様に即時関数内で行う。
(function(){
// 明示的にインスタンスを渡すことで、privateな動的メソッドを実装可能。
var privateFunc = function( instance, value ){
var str = value + "\r\nをcookie名 ";
str += instance._cookieName; // 親クラスの動的メンバにアクセス。
str += " に保存しました!";
alert( str );
};
CookieBind2Input.prototype.load2Input = function(){
var value = this.getValue();
if( value ){
$("#" + this._idInput).val( value );
}
};
CookieBind2Input.prototype.saveByButton = function(){
var value = $("#" + this._idInput).val();
if( value.length > 0 ){
this.save( value );
privateFunc( this, value );
}
};
CookieBind2Input.prototype.del = function(){
this.save( null );
}
}());
// 実行例
$(document).ready(function(){
var text_cookie1 = new CookieBind( "key-sample1" );
var text_cookie2 = new CookieBind2Input( "key-sample2", "id_text2", "id_button2" );
var text_cookie3 = new CookieBind2Input( "key-sample3", "id_text3", "id_button3" );
if( text_cookie1.getValue() ){
$("#id_text1").val( text_cookie1.getValue() );
}
$("#id_button1").click( function(){
var text = $("#id_text1").val();
text_cookie1.save( text );
alert( text + "\r\nをcookieに保存しました" );
})
$("#id_clear").click( function(){
text_cookie1.save( null );
text_cookie2.save( null ); // スーパークラスのメソッド呼んでみる。
text_cookie3.del();
alert( "cookieを削除しました" );
})
});
</script>
</head>
<body>
<form>
<input id="id_text1" type="text" style="width: 128;"></input>
<input id="id_button1" type="button" value="保存1"></input>
<br>
<br>
<input id="id_text2" type="text" style="width: 128;"></input>
<input id="id_button2" type="button" value="保存2"></input>
<br>
<br>
<input id="id_text3" type="text" style="width: 128;"></input>
<input id="id_button3" type="button" value="保存3"></input>
<br>
<br>
<input id="id_clear" type="button" value="クッキー削除"></input>
</form>
</body>
</html>
補足
※1:ECMAScript5前提。IE11でも動くように。
※2:Google流 JavaScript におけるクラス定義の実現方法
http://www.yunabe.jp/docs/javascript_class_in_google.html
⇒ ほぉ!と思ったが、private化を「名前の末尾に _ を付ける」
名前規約で対応している所が、モヤモヤする。
※3:コンスタラクタでreturn { mehotd1 : funcion() }
形式で
メソッド定義すれば、praivateメンバ/メソッド利用し放題なのだが、
それだとインスタンス生成(new)の度に、全てのメソッドの生成コスト
が掛かる。せっかくのプロトタイプベースのJavaScriptでソレは、
モヤモヤとする。
※4:jquery.cookie.js は「cookieを手軽に扱えるようになるjQueryのプラグイン」。
「jquery.cookie.jsの使用方法メモ」などを参照のこと。
http://cly7796.net/wp/javascript/plugin-jquery-cookie/
※5:ちな、JavaScript のオブジェクト指向プログラミングの考え方は、
公式のMozilla Developer Network のページが普通に分かり易くて
驚いた。このページは、適宜読み返したいと思う。
https://developer.mozilla.org/ja/docs/Web/JavaScript/Introduction_to_Object-Oriented_JavaScript