2011年1月13日

JavaScriptMVC と一覧表示の性能

JavaScriptMVC and List Performance (jupiterjs.com) の日本語訳です。 よくある話ですが「一覧表示のパフォーマンスに気をつける」ことについて適度な粒度でまとまっていましたので、メモがてら日本語にしておきます。

日本語訳

たいていのアプリケーションではデータの一覧と個別のデータを表示して対話的に操作することが中心にあります。 GMail, Grooveshark, Twitter, Facebook, あるいは Hulu のようなアプリケーションを考えてみてください。 これらのアプリケーションのそれぞれがアイテムの一覧を表示し、そこからユーザーは個別のアイテムに進んでいけます。 多くの場合、一覧表示中でもユーザーは個別のアイテムを編集できます。

アイテムの一覧を表示することは、アプリケーションでもっとも性能が悪い (表示するまでの時間という点で) 部分になりがちです。 これは次のようないくつかの理由によります。

  • 一覧の方が相対的に多くのデータを表示することになります。
  • それぞれのアイテムに関する HTML が生成されて DOM に追加される必要があります。
  • それぞれのアイテムに対してイベントハンドラが登録される必要があります。

JavaScriptMVC は最初期の頃から一覧表示を効率化することに注力してきました。 実際、これより優れた方法はなさそうです。 この記事ではこうした機能を実現する方法を議論します。

Insertion

サーバからデータを取得したら、人間が読んだり操作できる形式に変換する必要があります。 Web 開発ではこの形式は HTML を意味します。 こうした HTML が見えるようになるためには DOM に挿入されなくてはなりません。

複数のブラウザに渡って性能を最適化するためには、HTML は文字列であるべきで、 一度の innerHTML 呼び出し (もしくは innerHTML を使う何かしらのテクニック) で DOM に挿入されるべきです。

さらに、文字列を生成するときは文字列を連結するのではなく、配列を結合するようにすることが大事です (IE での性能を考えるなら、ね)。

JavaScriptMVCView 層 ($.View) は、非常に洗練された書き方でこうした最適化手法を提供してくれます。

$("#recipes").html( "//path/to/recipes.ejs",
  [ {name: ‘ice cream’,id: 1},
    {name: ‘hot dog’, id: 2} ] )

//path/to/recipes.ejs の中身。

<%= for(var i =0; i < this.length;i++){ %>
  <div class='recipe <%= recipe[i].identity()%>'>
  <%= recipe[i].name %>
  </div>
<% } %>

テンプレートを作成するときに $.View は JavaScript で文字列の配列をつなぎ合わせる関数を生成します。 その関数は次のようになります。

var _v1ew = [];
for(var i =0; i < this.length;i++){
  _v1ew.push("<div class="’recipe ",
    recipe[i].identity(),">")
  _v1ew.push(recipe[i].name)
  _v1ew.push("</div>")
}
return _v1ew.join("");

開発モードでは $.View はテンプレートに対してリクエストを発行します。 しかし、次のようにすると製品版のビルドには処理済の形式でパッケージングできます。

steal.views(‘//path/to/recipes.ejs’)

ビューをパッケージングすると、リクエストが発行されないようになるだけでなく、その都度処理が走ることもなくなります。

まとめると、 $.View は最小限の記法でクライアントサイドのテンプレートを最大限に効率化してくれます。

Responding to Events

コンテンツが DOM に挿入されたら、ユーザーがそれに対して対話操作できなくてはなりません。 伝統的には、開発者はそのページにイベントハンドラを追加しなくてはなりませんでした。 これはイベントリスナーを追加するため、性能に影響の出る DOM の操作と走査を必要とします。

しかし JavaScriptMVC は、ある要素に対するイベントのために異なる要素にリスナーを設定できるようなイベントデリゲーション (委譲) の道を切り開いてきました。 JavaScriptMVC 1.5 のイベントデリゲーションを jQuery 本体に取り込むように働きかけましたし、 $.fn.delegate のために懸命にやってきました。

$.Controller はイベントデリゲーションを使って設計されています。 ルート要素にイベントリスナーを設定し、性能に影響がある DOM 操作を必要としません。

$.Controller("Mylist",{
  ".recipe .delete click" : function(){
     // delete the recipe
  }
})

まとめると、 $.Controller はイベントハンドラの委譲を構成することによって、計算コストのかかる DOM 操作の必要性を取り除いてくれます。

Conclusion

対話的な一覧を表示する部分は、ユーザー体験にとって致命的でもっとも性能が悪い機能になってしまう可能性を秘めています。

JavaScriptMVC の $.View$.Controller は、直感的で比較的簡単な文法でありながら、 対話的な一覧を表示するために完全に最適化された解決方法を提供してくれます。

終わりに

JavaScriptMVC は jQuery をベースにしたクライアントサイドのフレームワークで、 プラグインシステムによってモジュールを柔軟に切り替えることができます。 テンプレートシステムには EmbeddedJS (ejs), JAML, Micro, jQuery.Tmpl の 4 種類がサポートされています。 Model 層からの scaffold のデフォルトは ejs が使われますが、テンプレートを書き換えれば他のモジュールも使えるはずです。 その他に Mustache for JavascriptMVC 3 もあります。

文字列の連結云々に関しては Yahoo! PRESS から出版されている "High Perfomance JavaScript" が詳しいと思います。 DOM を解析する場合は "Awesome open source DOM Monster Performance bookmarklet!" などが便利でしょう。

改めて日本語にしてみるとイベントデリゲーションに関してさっぱり分かっていないことを再認識しましたので、時間をみつけてソースコードを読めると良いなぁと思いました。

コメントを投稿