Yokohama.js行ってきた

最近特にJavaScript使ってるのが楽しいもんで、JS系勉強会ってないかなーって探してはみるんだけど、HTML5関連のはいくつかあるものの、純粋にJSで遊ぶ系勉強会というものがほとんど見つからない。そんな時代に辟易していると、ふと目に飛び込んできたのが第3回くらいYokohama.js (#yjs20120805) : ATNDだった。横浜となるとあまり行きやすくはないが、まぁひどく遠いというわけでもないので、ちょっと行ってみることにした。

タネマキでなんの種を蒔くか

開催場所は、横浜にあるコワーキングスペースタネマキ。2時間以上利用の場合、使用料1,000円(ワンドリンク付)で利用できる。別にコンビニで飲み物買ってきてもいいので、その辺は自由に。けっこうざっくりした感じの内装だけれども、それが逆に圧迫感をなくしててよかった。
そんなうっかりタマネギと空目してしまうタマネ…タネマキで行われたYokohama.jsの内容は、「スライダーを作ろう」というハンズオン。予めHTMLと画像が用意されており、これをJS(jQuery)でスライダーとして実装する。個人的には仕事上、こういうもの、というかこれより複雑なスライダーを組んだことがあるので、さほど難易度は高くないが、ちゃんとJSを勉強してから組むのは初めてなので、その辺の知識も利用しつつ、力試しをしてみた。

目標

個人的なコンセプトとしてはこうだ。まず、予め用意されているHTMLとCSSとには手を加えない。あくまで、今実装されているものをベースにjQueryだけでスライダーを実装する。右ボタンを押したら右の画像が、左ボタンを押したら左の画像が表示されるようスライドし、端まできてボタンを押すと、反対側の端へ移動する。また、できるだけ共通動作等は関数化し使いまわす。また、もし時間があるようなら、それをjQueryプラグインとして実装し直す。

bind便利。

まず、ひとまず作った版では、jQuery.ready()後に必要なタグや数値を変数に入れている。この時、できる限り定数は使わないように心がけた(が、実は1箇所、面倒くさくて定数のまま使ってたりする)。インデックスベースにして(何故か変数名は counter )、インデックス値が指している箇所にスライドするようにした。つまり、左右ボタンをクリックした際の動作が、「インデックス値までスライド」という共通の動作にまとめられるので、それ用の関数を作成している。となると、左右ボタンそれぞれの固有の動作というのは、インデックス値の増減なのだが、このためだけに1つイベントリスナを作るのはアレゲなので、出来れば先の関数にまとめたい。普通なら引数に正/負の値を与えることでそれを制御するのだが、イベントリスナの第2引数に渡すのは「関数呼び出し」ではなく「関数オブジェクト」なので、引数を与えて呼ぶことができない。しかし、そのためにJavaScriptには便利なメソッドが存在してる。bindである。

jQueryのbindは、addEventListenerのラッパであったが、JavaScriptのbindはぜんぜん違う。ざっくりいうなら「該当関数内のthisの参照先を第1引数のオブジェクトとする、関数オブジェクトを返す」関数である。つまり、obj.func.bind(obj); と呼んでも、実行はされない。Functionクラスのオブジェクトが返ってくるだけである。そして、これの特に便利なところは、「第2引数以降を、この新しい関数の引数として扱う」ところである。

言葉ではわかりづらいので、サンプルを。

var obj = {};
obj.x = 1;
obj.func = function(){
  console.log("this.x: " + this.x);
  console.log("arguments[0]: " + arguments[0]);
};

var obj2 = { x: 2 };

obj.func("test");
>>> this.x: 1
>>>arguments: test

var funcobj = obj.func.bind(obj2, "one", "two", 3);

funcobj;
>>> function(){ [ native code] }

funcobj();
>>> this.x: 2
>>>arguments: one

んで、問題のイベントリスナの第2引数には関数オブジェクトしか受け取らない件だが、実際に下記のようにしてみるとわかると思う。

//use jQuery1.7.x
var btn = $("#btn");
var func = function(num){
  console.log(num);
};

btn.on("click", func); //通常の記法
>>> o.Event //btnをクリックすると、通常では第1引数としてEventオブジェクトを受け取ってしまう。
btn.off("click");

btn.on("click", func(12));
>>> TypeError: Cannot read property 'guid' of undefined // 引数を与えようと、通常の関数呼び出しっぽくfunc(12)書いてみると、
>>> //まぁ普通の関数呼び出しになるので、書いた時点(クリックした時ではない)でその式を処理してしまう。
>>> //return が書かれていないので、 返り値はundefined?

btn.on("click", func.bind(func, 12));
>>> 12 //func.bind()の返り値は関数オブジェクトなので、エラーは出ないし、即時実行もされない。
>>> //btnをクリックすると、bindの第2引数で与えた値が実際に実行される関数funcの第1引数として実行されるので、"12"と表示される
btn.off("click");

これを使えば、任意の引数をイベントリスナに与えて呼び出すことができるので、インデックス値の増減を件の関数内に含めることができるようになる。bind超便利。

jQueryプラグイン

jQueryプラグイン版への変更は、大したことをしているわけではなく、規定フォーマットに先ほど作ったものをコピペし、変数定義をすこし変更したくらいである。

handleEvent

主催が作った、jQueryなし版スライダーを見て、一つおもしろいものを学習した。それは"handleEvent"プロパティである。実はこのコード、明示的にはどこからもhandleEvent()を呼んでいない。しかし、そこに書かれた処理は、ボタンクリック時にちゃんと動作している。

var obj = {};
obj.handleEvent = function(e){
  console.log("called");
};

document.getElementById("btn").addEventListener("click", obj);
>>> called //クリック時

パーフェクトJavaScriptにもhandleEventについては書かれているのだが、いまいち使いドコロがわからず、身についていなかった。しかし、実際にそれを利用したプログラムを見て、実行してみたことで、なんとなくだが、これの利用価値についてわかったような気がしている。

感想

実装中は特に周りとの交流もなく、なんとなく時間が過ぎていっている感じがあったが、最終的に周りの人が組んだプログラムを見ることで、いろいろな勉強になったのは良かったと思う。まだまだYokohama.jsに溶け込めたとは言えない感じなので、ぜひ次回も開催されたら参加したいな、と思っている。