タイトル
ステートフルJavaScript ―MVCアーキテクチャに基づくWebアプリケーションの状態管理
著者
Alex MacCaw (著), 牧野 聡 (翻訳)
出版社
オライリージャパン
Amazonで購入する

本書は、クライアントサイド(ブラウザ)で JavaScript を使ってリッチなアプリを作る際のプラクティスを説明しています。

前半では、JavaScript によるオブジェクト指向開発の方法や MVC 開発の方法を、後半では、JavaScript の MVC デザインパターンに則った、各種ライブラリの説明が行われています。

最近では、ユーザビリティの良いアプリを開発するのに必須なクライアントサイドでの JavaScript の使い方が複雑になってきているように思います。ちょっと jQuery を使って DOM 操作していればよかった頃と比べて、最近では色々なライブラリを組み合わせる必要があったり、クライアントサイドでも MVC パターンが必要になったりと、大変です。

本書は、そんな JavaScript でリッチアプリケーションを作成する際の取っ掛かりを学ぶのに良いと思います。オライリー本なので、読むのが簡単ということはなく、むしろ格式高い感じすらしますが、多少 JavaScript でオブジェクト指向の経験があれば、読み進めるのは難しく無いと思います。

おぼえがき

インスタンスメソッド定義のイディオム

JavaScript ではクラスのプロトタイプに対して関数を追加することでインスタンスメソッドを定義できます。

1
2
3
Person.prototype.breath = function() { /* ... */ };
var person = new Person();
person.breath();

クラスのプロトタイプに fn というエイリアスを設定することが広く行われています。

1
2
Person.fn = Person.prototype;
Person.fn.run = function() { /* ... */ };

関数呼び出しのコンテキスト

apply() と call() を使うと、関数の呼び出しコンテキストを変更できます。jQuery の API ではイベントハンドラや each() による繰り返しの中で、コンテキスト(this の値)が変更されています。

1
2
3
$('.btn').click(function() {
  $(this).hide();
});

元のコンテキストにアクセスするには、良く self などの名前で this を保存しておき、後で参照する方法がとられます。

1
2
3
4
5
var self = this;

$('.btn').click(function() {
  self.btnClicked();
});

apply() を使うと、self を使わなくても同じことができます。

1
2
3
4
5
6
7
8
9
var proxy = function(func, context) {
  return (function() {
    return func.apply(context, arguments);
  });
};

$('.btn').click(proxy(function() {
  this.btnClicked();
}, this);

jQuery の proxy() メソッドがまさしく、これと同じことをしています。

イベントの発生順序

ある要素とその祖先の要素が同じ種類のイベントに対してイベントハンドラを登録している場合、先に呼び出されるのはブラウザによって違います。

イベントが呼び出される順番は2種類あり、イベントキャプチャリングイベントバブリングになります。

イベントキャプチャリング

最も上位(外側)の要素からイベントが発生した要素へとイベントリスナが呼び出されます。つまり、祖先の要素、内側の要素の順番でイベントリスナが呼び出されます。

イベントバブリング

イベントが発生した要素(内側)から祖先要素へとイベントリスナが呼び出されます。

W3C の仕様では、addEventListener() の3つめの引数に true を指定するとイベントキャプチャリングで呼び出され、false か 指定しないとイベントバブリングで呼び出されます。

バブリングが行われている際には、イベントオブジェクトの stopPropagation() を呼び出すことによって、バブリングを中止することができます。

1
2
3
4
link.addEventListener('click', function(e) {
  e.stopPropagation();
  /* ... */
});

ブラウザによってそれぞれのイベントに対するデフォルトのアクションが定義されています。たとえば、リンクのクリックでは、src で指定された URL に遷移する動作や、チェックボックスのクリックで項目が選択状態になる動作などです。

これらのデフォルトのアクションはイベントの伝播が完了した後で実行されます。このデフォルトのアクションを中止するには、 preventDefault() 関数を呼び出します。また、イベントハンドラの戻り値で false を返すことで、preventDefault() を呼び出したのと同じ動作になります。

1
2
3
4
form.addEventListener('submit', function(e) {
  /* e.preventDefault() */
  return confirm('本当に送信しますか?');
});

イベントの委譲

イベントバブリングの性質を考えると、複数ある子孫の要素それぞれにイベントリスナを設定するより、祖先の要素一つだけにイベントハンドラを設定する方が効率がよくなります。

jQuery では、これを delgate() メソッドを使って実装できます。

1
2
3
4
5
/* 望ましくないコード */
$('ul li').click(function() { /* ... */ });

// こちらのほうが効率が良い
$('ul').delegate('li', 'click', function() { /* ... */ });

このように、イベント処理を委譲することによるメリットは、動的に追加された要素についても、自動でイベントリスナが適用されることです。

画面表示時の初期データの読み込み

ページ上のデータは、最初に読み込まれるページに含まれているか、Ajax や JSONP を使った HTTP リクエストによって事後的に読み込まれます。多くのデータを初期表示時に読み込むのには時間がかかりますが、Ajax や JSONP によるリクエストは、並列処理が可能であるので高速に処理できます。

また、HTTP リクエストで取得したデータは、ローカルのストレージにキャッシュすることができます。ストレージには2種類あり、 localStoragesessionStorage というオブジェクトでアクセスできます。

ローカルストレージ

ブラウザが終了した後も保持される。

セッションストレージ

ブラウザが開いている間のみ保持される。

コントローラと内部状態

まず言えるのは、DOM 内にモデルの内部状態やその他の情報を保持するべきではありません。頑強な MVC アーキテクチャの実現のためには、内部状態やその他の情報は、コントローラ内に保持させる方がよい。

ビューごとにコントローラを1つずつ用意するというのがよく使われているパターンです。

リソースとURL

通常、Ajax などでリソースとなるデータをロードした場合、ブラウザの URL は変化しません。しかし、Web 上の個々のリソースには URL が割り当てられていると考えるのが一般的で、URL が変わらない状況というのは好ましくありません。

そこで、この問題に対しては、内部状態が変化したら URL を変化させるようにします。このとき、よく使われるのが、URL のハッシュ(#以降の文字列)を変更するというものです。ハッシュの値はサーバに送信されないため、ページの再読込を発生させることなく変更できます。

location.hash を使うと、ページのハッシュを取得できます。

主要なブラウザでは、URL のハッシュが変更されたかどうかを調べるのに、hashchange イベントが使えます。

JavaScript で作られたページは、クローラにとってはすべて同じ URL のように見えてしまいます。そこで、Google は、Ajax Crawling という仕様を提案しています。

Backbone.js ライブラリ

は JavaScript アプリケーションの構築のための優れたライブラリです。Backbone.js も MVC 構造でアプリケーションを作成していきます。

参考