デブサミ版Google Hackathon行ってきました*1。OpenSocialに触れるのがもちろんメインテーマなんですが、今回それとは別に「JavaScriptを使いこなせるようになる」と言うサブテーマを自分に課してました。
今日一日でJavaScriptを好きになれたので、そのノウハウをメモっておきます。
参考: 今日書いたJS
名前空間
JavaScriptで何が嫌かって、裸で存在する変数やハンドラ類です。今まで start() とか init() なんて関数名とか普通に使ってたんですが、「こんなのいつかぶつかるに決まってるじゃん」、と非常に嫌な思いをしてました。
名前空間の作り方がわかれば、このストレスが一気になくなります。これだけでJavaScript嫌いが治るといっても過言じゃないくらい効果的です。
名前空間はオブジェクトとして作ればOKです*2。変数の局所化のために ( function () { ... } )(); で包みつつ、作成した名前空間に関数をぶら下げるだけです。
var socialquest = new Object(); (function () { socialquest.recievedViewerData = function (data){ ... 処理 ... }; })();
これでグローバル空間にありきたりな名前が並ぶ事態は防げるようになりました。
クラス定義
JavaScriptではクラス = コンストラクタ(関数オブジェクト) です。でもって、びっくりするくらい色んなクラス定義方法があります。大きく分けると考え方は二つです*3。
- (コンストラクタで)オブジェクト(this)にメソッドをつける
- クラスのprototypeにメソッドをつける(= オブジェクトの__proto__にメソッドを生やす)
個人的にPythonのようにコンストラクタでthisに色々生やすのは気に入らないので、今回はprototypeを使いました。
socialquest.CharacterMaker = function (){}; socialquest.CharacterMaker.prototype = { _getJob: function (p){ ... メソッドの処理 ... }, make: function (p) { ... メソッドの処理 ... } };
継承
弾さんも昔述べられていたように、JavaScriptのプロトタイプ継承は素敵です。
JavaScriptでの継承は、クラスのprototypeに継承元となる"オブジェクト"をセットすることでできます。オブジェクトを継承してクラスを作るってのが混乱を産む部分です。そして、サブクラスでのメソッド定義は、前述したようにコンストラクタでthisに生やす方法と、prototypeに生やす方法があります。
今回は、 opensocial.Person オブジェクトから継承して md5 と hp(体力) と job(職業) がついたオブジェクトを作りました*4。サブクラス側のメソッド定義には、「生成したオブジェクトにメソッドを追加する方法(≒ コンストラクタでthisに生やす方法)」を使っています。
function clone(obj) { var Class = new Function(); Class.prototype = obj; return new Class(); } ... 略 ... make: function (p) { var person = clone(p); person.md5 = MD5_hash(p.getId()); person.hp = person.md5.charCodeAt(0); person.job = this._getJob(p); return person; } ... 略 ...
この例はオブジェクトから直接その性質を継承したオブジェクトを作成することでJavaScriptのプロトタイプ継承の柔軟性を示してますが、内部の無名クラスを公開すればクラスベースのOOPと同等の継承を表現できます。
脳味噌がOOPになってるオレには、何かあった時に大変心強いです!
ハンドラとの付き合い方
イベントドリブンだとかAjaxとかのせいで、JavaScriptにはハンドラが大量に出てきます。こいつは静的なOOPの「メソッドに引数を渡すと値が返ってきて続きを実行できる」と言う性質をぶちこわします。メソッドを呼んでも値は返って来ずにロジックの流れは途切れてしまいます。その代わりに、普段あまり慣れてない、ハンドラを作成して渡すことを要求されます。
よく見るとこのパターン、アクターモデルとかLISPの継続とかの概念に似てます。ですので、メソッド呼び出しって観点を捨てて、アクター間でのメッセージ送信って言う見方をした方がしっくり来るのではないかと思います。ハンドラに単純に処理の続きを書くのではなく、「依頼した処理が終わったらどのアクターにどんなメッセージを送るべき?」と考えるといいでしょう。
なお、大抵のサンプルだと簡潔だからと言う理由でハンドラには無名関数を渡してますが、非同期呼び出しを連続して行う場合には無名関数のネストが起こって大変な目にあいます。適切と思われるメッセージ名を関数名として定義した方がベターな気がします。