ストレスなく JavaScript をロードするためのパターン

最近,真面目に JavaScript を書くようになったので何か1冊読んでおこうかなぁと本屋で立ち読みしていたところ,JavaScript パターン と言う本が面白そうだったので,買って読んでみることにしました.この本を選んだ理由は,他の章の内容も気になっているのですが,8章の内容が即効性がありそう(すぐに実戦できて効果がありそう)で覚えておきたかったと言うのが一番の理由です.

ここでは,その中で JavaScript に関してほとんど何も知らない状態でも「効果がありそう」と感じられた 8.6節以降の内容の一部を引用しながら感想を書いてみます.

スクリプトをまとめる

HTTP リクエストは時間がかかるので、読み込みが速いページにする第一原則は、ページ以外のファイルの数をできるだけ減らすことです。JavaScript について言うなら、スクリプトファイルをまとめてバンドルにすれば、ページの読み込みを著しく高速化できます。
・・・(中略)・・・
ファイルをまとめる作業は、コードを正式リリースする直前に行うべきです。開発時にまとめてしまうと、デバッグ作業で苦労します。

JavaScript パターン - 8.6.1 スクリプトをまとめる (p.205)

動画ファイルなどになってくるとまた別の話になってきますが,HTTP 通信においては多くの場合,帯域よりも RTT の方が問題になってくることが多々あります.たった 1KB のファイルであったとしても,ファイルが 2分割されているだけで,最悪の場合(TCP コネクションが切れてしまってハンドシェイクからやり直しの場合),数倍の時間がかかると言う事も有り得ます.

日本国内の場合は RTT が大体 10ms〜20ms なので,数10ms で表示できたはずの Web ページがうっかり表示に 100ms 以上かかってしまうと言うケースも考えられます.これが大陸間通信が絡むと(RTT が 200ms とかそんなレベル),表示されるまでの時間はもっと不安定になってきます.トランスポート層における Web 最適化 - Life like a clown でも少し述べましたが,帯域的には誤差でしかない 1往復分のデータ通信を改善しようとするのも,その辺りに起因します.

そんな訳で,ファイル数を減らすと言う事は有効な一手のようです.個人的にも,次に挙げられている「コメントなどを削除して 1ファイルのサイズを削減する」よりも有効である場合が多いだろうと予想しています.ただ,ファイルを結合するためのリリース準備なども,変な不都合を混在させないように,うまく自動化する必要がありそうです.

ミニファイする、あるいは圧縮する

ユーザの立場から考えると、ダウンロードされるコードにコメントがあっても、アプリケーションの動作には何の役にも立ちません。

ミニファイの効果は、コメントや空白をどのくらい使っているか、さらにはミニファイのツールによって異なります。平均的には 50% ほどファイルサイズが削減されます。

スクリプトファイルを圧縮してリリースする選択もあります。

・・・(中略)・・・

平均するとファイルサイズの約70% が圧縮されます。ミニファイと圧縮を組み合わせると、ミニファイも圧縮もしていないソースコードの役15%のファイルサイズになります。

JavaScript パターン - 8.6.2 ミニファイする、あるいは圧縮する (p.206)

先にも書きましたが,現在のネットワーク状況では,こちらはそこまで効いてくる場面はないのではと言う気はしています.スクリプトファイルが馬鹿でかいとか,突然有名になって死にそう(主にサーバ側の回線が)と言う時には検討しても良いかもしれません.

ただ,どっちにしても先の「スクリプトをまとめる」処理で何らかの(リリース用スクリプトファイルを作成するための)スクリプトをかますことになるので,一緒にやってしまうと言うのは良い方法かなとは思います.

script 要素をどこに書くか

script 要素はページのダウンロードの進行をブロッキングします。ブラウザはいくつかのファイルを同時にダウンロードしますが、外部のスクリプトに遭遇すると、そのスクリプトファイルのダウンロード、構文解析、実行が完了するまで、他のファイルのダウンロードを中断します。この結果ページ全体の読み込み時間が遅くなり、場合によっては数倍もかかることもあります。

このブロッキングの不都合を最小にするには、script 要素をページの終わり近く、</body> タグの直前に書きます。こうすればスクリプトによって他のリソースがブロッキングされなくなります。ページの残りのファイルは、スクリプトより前にダウンロードされユーザに届きます。

ドキュメントのヘッドで複数のファイルを取り込むのは最悪のアンチパターンです。

JavaScript パターン - 8.7.1 script 要素をどこに書くか (p.207)

この節が,この本を買うきっかけとなった部分でした. をどこに書くかと言うのはよく分かっていなくて,自分の過去を振り返ってみても,何となく <head> に書いてしまうと言う事はよくやらかしていました.

確かに,途中でいきなりページの表示が停止してしまって不快な思いをすると言う(ユーザの立場での)経験はよくあったので,どこに書くかは一度見直した方が良いなと感じました.

script 要素を動的にしてダウンロードのブロッキングを回避する

JavaScript は後続するファイルのダウンロードをブロッキングしてしまいますが、これを防ぐためのパターンがいくつかあります。

  • XHR リクエストでスクリプト文字列として受け取り、eval() を実行する。このアプローチは同一ドメインの制約に悩まされるだけでなく、アンチパターンそのものの eval() も使っています。
  • defer や async 属性を使うにしても、すべてのブラウザで動作するわけではありません。
  • 動的 <script>

最後のパターンは検討に値します。

・・・(中略)・・・

次の例は残りのダウンロードをブロッキングせずに非同期に JavaScript ファイルを読み込みます。

var script = document.createElement("script");
script.src = "all_20100426.js";
document.documentElement.firstChild.appendChild(script);
JavaScript パターン - 8.7.3 script 要素を動的にしてダウンロードのブロッキングを回避する (p.210)

この辺りから,徐々に自分の理解がまだ追いつけていない状態になっていますw.Ajax と言う言葉が流行って以降,JavaScript の使用方法については「いったん最初のページを表示した後,ユーザの操作などに応じてそのページ内に必要な情報をロードする」みたいな形が主流になっているので,ここと遅延読み込み辺りはきちんと理解しなければと感じました.

尚,本書では,このパターンの欠点とその解決方法についても言及されていました.

遅延読み込み

遅延読み込みとは、ページの load イベントが発生した後に外部ファイルを読み込むことを言います。

・・・(中略)・・・

ページを段階的に読み込むことで、使えるものをできるだけ早く用意するのが目標です。ユーザがページを眺めている間にバックグラウンドで残りの部分が読み込まれます。

JavaScript の後半部分の読み込みは、head あるいは body に script 要素を動的に追加するだけです。

JavaScript パターン - 8.7.4 遅延読み込み (p.212)

これも先に述べた通りまだ理解はできていません.ただ,サンプルコードを見る限り,チャンク形式の HTTP 通信用になっていたので,そう言った用途(サーバ側での処理が重くて,一度に全てを返すと待ち時間が長くなってしまう,等?)で使うのかもしれません.

オンデマンドで読み込む

前のパターンではページを読み込んだ後、追加の JavaScript を無条件に読み込みました。ほんとうに必要なコードだけ読み込むにはどうすれば良いでしょう?

・・・(中略)・・・

ここでロードオンデマンドパターンの登場です。require() という関数またはメソッドを作成します。読み込むスクリプトのファイル名、そしてスクリプトが読み込まれたときに実行されるコールバック関数を引数にします。

require() 関数は次のように使います。

require("extra.js", function() {
    functionDefinedInExtraJS();
});

このような関数をどう実装すれば良いでしょうか。スクリプトをリクエストするのは単純で、動的 <script> 要素のパターンに従うだけです。スクリプトが読み込まれたタイミングの判断は、ブラウザごとに差異があるのでちょっとした仕掛けが必要です。

JavaScript パターン - 8.7.5 オンデマンドで読み込む (p.212)

オンデマンドは,例えば ソーシャルボタンを表示する JavaScript - Life like a clown で作成した JavaScript でも「MD5() 関数は Delicious で詳細表示する時しか必要ないんだけど無駄だよなぁ・・・」みたいに感じる事が結構あったので,そう言った要求を解決する手段になるのかなとちょっと期待しています.

JavaScript を事前に読み込む

遅延読み込みパターンとオンデマンドパターンは、現在のページで要求されるスクリプトを後から読み込みます。また現在のページでは必要ないけれども、後に続くページで必要になるスクリプトも読み込むことができます。このやり方だと、ユーザが次のページに移動したとき、スクリプトは事前に読み込まれているので、全体の体感速度が向上します。

・・・(中略)・・・

IE の場合、おなじみの画像ビーコンパターンでリクエストすることができます。

new Image().src = "preloadme.js";

他のブラウザの場合、script 要素のかわりに <object> を使い、その data 属性にスクリプトの URL を指定します。

var obj = document.createElement('object');
obj.data = "preloadme.js";
document.body.appendChild(obj);
JavaScript パターン - 8.7.6 JavaScript を事前に読み込む (p.214)

これもまだ理解していないのですが,例えば共通部分みたいなところだと,同じスクリプトを何度もロードする必要があるので,そう言った時に最初にロードしたスクリプトを使いまわせるのかな・・・そうだといいな,とか思いました.

後半はまだまだ理解度が足りていないので,しばらくは読んだり写経したりして,何が起こるのかを観察していこうと思います.