連載「Webサイト・アプリ高速化テクニック徹底解説」第2回は、JavaScriptの高速化について、まずは前編、後編に渡ってユーザーの体感速度を向上させるための方法を紹介します。JavaScriptの同期・非同期の仕組みやscript要素のasync属性、defer属性について詳しく解説します。
ユーザーの体感速度を高めるためのJavaScriptチューニング(前編)
今回から複数回に分けて、JavaScriptの高速化をテーマに解説していきます。まずは、ユーザーの体感速度を高めるためのJavaScriptチューニングということで、単純なJavaScriptの構文によるスピードを比較するようなものではなく、主にユーザー視点からの高速化を主眼に解説します。その中で、同期・非同期といったJavaScriptの仕組みやscript要素のasync属性、defer属性などについても触れていきます。
ユーザーの体感速度を向上させる
一概にJavaScriptの高速化といっても、大きく分けて2つの手法があります。ひとつ目は、実行されるJavaScriptコードを最適化して、個々のコードを高速化することです。大きなボトルネックとなるコードがあれば、効果が高いこともありますが、基本的には数ミリ秒の最適化を積み重ねるものです。
そして、もうひとつは、ユーザーの体感速度を向上させる方法です。JavaScriptの実行速度自体は大きく変わりませんが、ユーザーから見るとあたかもページが早く表示されているように感じます。この方法では、ちょっとしたコードの修正や、基本的な設計変更がWebサイト・アプリの速度を劇的に改善する可能性があります。そのため、まずはユーザーの体感速度を向上させる方法を検討してみると良いでしょう。ユーザーの体感速度を向上させるには、例えば次の3つのものが挙げられます。
- ページを素早く表示する
- ユーザーに素早くインタラクションを返す
- ユーザーの操作を阻害しない(次回で解説します)
以降は、それぞれについて詳しく解説していきます。
ページを素早く表示する
あたり前のことですが、ページを素早く表示することはとても大事です。最初にページにアクセスした際に、ずっと真っ白い画面のままの場合と、少しずつでもコンテンツが表示される場合とでは、ユーザーの体感速度は大きく違います。実は、これにはJavaScriptのコードが大きく影響していることがあります。次のコードを見てみましょう。
:html:
<!DOCTYPE html>
<html lang="ja">
<head>
<!-- … -->
<script src="1.js"></script>
<script src="2.js"></script>
<script src="3.js"></script>
</head>
<body>
<!-- コンテンツ -->
</body>
</html>
このコードでは、head要素にscript要素で3つのJavaScriptファイルを読み込んでいます。この場合、すべてのJavaScriptの実行が終わるまで画面の描画が行われず、これらのJavaScriptの実行に時間が掛ると、その分だけ何もコンテンツが表示されない時間が長くなります。このコードを実行した結果をChromeのデベロッパーツールで解析してみると次のようになります。
図を見るとわかる通り、1.js~3.jsの読込みと実行が順番に行われ、それが終わるまでの間は、body要素以下の表示が行われません。これは、JavaScript内でdocument.write()メソッドによってHTMLの内容が書き換わる可能性があるため、JavaScriptの実行が終わるまでページの表示がブロックされるためです。では、どのようにして素早くページを表示させるのかというと、次のような位置にscript要素を記述します。
:html:
<!DOCTYPE html>
<html lang="ja">
<head>
<!-- … -->
</head>
<body>
<!-- コンテンツ -->
<script src="1.js"></script>
<script src="2.js"></script>
<script src="3.js"></script>
</body>
</html>
よく利用されるテクニックですが、このようにbody要素の最後にscript要素を記述すると、先に表示できるコンテンツを表示し、コンテンツの表示が終わったあとにJavaScriptを実行するようになります。このようにして、ページがすぐに閲覧できるようになり、ユーザーの体感速度を向上することができます。ただし、これらのJavaScriptでユーザーのクリック動作などを設定している場合は、コンテンツが表示されているのにクリックしても反応がない時間などができてしまうので、状況に合わせて使い分けていきましょう。
その他の方法として、script要素にdefer属性とHTML5で追加されたasync属性というものを利用する方法もあります(defer属性は、HTML4で標準化されています)。まずは、先ほどのコードとまったく同じ効果を持つdefer属性について解説します。defer属性は次のように記述します。
:html:
<!DOCTYPE html>
<html lang="ja">
<head>
<!-- … -->
<script src="1.js" defer></script>
<script src="2.js" defer></script>
<script src="3.js" defer></script>
</head>
<body>
<!-- コンテンツ -->
</body>
</html>
script要素にdefer属性をつけると、コンテンツの表示が終わったあとに、そのJavaScriptを実行するようになります(正確にはDOMContentLoadedイベントの前になります)。そのため、body要素の最後にscript要素を記述したコードとほぼ同じ結果になりますが、ファイルの読込みは非同期で行われるため、こちらのほうが若干早くなる可能性があります。このdefer属性は、すべてのモダンブラウザ(Chrome、Safari、Opera、Firefox、IEなど)で利用することができます。元々は、IEの独自実装であったため、IEはバージョン4の時代から利用できます。
また、もうひとつのasync属性では、JavaScriptの実行でページの表示をブロックせずに、JavaScriptを非同期で実行することができるようになります。script要素にasync属性をつけると、そのままscript要素以降のコンテンツが表示されていきますが、JavaScriptファイルのダウンロードが終わり次第、そのJavaScriptが実行されます。注意として、async属性をつけたscript要素は、実行順序が保障されていないため、他のJavaScriptと依存関係がないものに限って利用する必要があります。また、実行タイミングも保障されないため、まだ読み込まれていない要素に対するDOM操作をしてしまう可能性もありますので注意しましょう(その場合、onloadイベントなどを利用しましょう)。もし、サンプルで利用している1.js~3.jsがお互いに依存していない場合は、次のように記述することができます。
:html:
<!DOCTYPE html>
<html lang="ja">
<head>
<!-- … -->
<script src="1.js" async></script>
<script src="2.js" async></script>
<script src="3.js" async></script>
</head>
<body>
<!-- コンテンツ -->
</body>
</html>
1.js~3.jsはasync属性によって、同時にダウンロードが始まり(ブラウザの同時接続数などにもよります)、完了次第実行されます。そのため、高速に動作するのですが、実行される順番がバラバラになる可能性があります。依存関係があるJavaScript要素には、script要素をそのままで利用するか、defer属性を使いましょう。例えば、2.jsと3.jsに依存関係がある場合には、1.jsにのみasync属性を追加するようにします。async属性は、すべてのモダンブラウザ利用することができますが、IEは9以降の対応となります。といっても、async属性が無視されたとしてもJavaScriptはそのまま実行されるので、非対応のブラウザについてそれほど気にする必要はないでしょう。
async属性とdefer属性を付けたscript要素では、document.write()メソッドが利用できないことと、インライン(script要素に中に直接記述する)でJavaScriptを記述することができませんので注意しましょう。これらの、script要素の位置やasync属性、defer属性などを利用して、なるべくすばやくページが表示されるように気をつけてみましょう。
ユーザーに素早くインタラクションを返す
ユーザーの体感速度を向上させるには、ユーザーの行動に対して、素早く何らかのインタラクションを返すことが重要です。例えば、Gmailのメールスレッドにスターを付けるインターフェースを見てみましょう。
Gmailでは、メールスレッドにあるブランクのスターをクリックした瞬間にスターが黄色くなるようになっています。実際にスターを付ける処理は、バックグラウンドで非同期に実行されます。このように、他の操作に影響が少ない部分であれば、素早くレスポンスを表示することでアプリがサクサク動いているように見せることができます(もちろん、エラーがあった場合には、適切な通知を行うことが必要です)。
この例では、ユーザーの行動に対して先に結果を表示するというものでしたが、処理に時間がかかるものであれば、プログレスバーやローディングアイコンを表示することも、当然ユーザーに素早くインタラクションを返す一例になります。これは、ユーザーに何もレスポンスがないという状態をなるべく減らすということです。基本的な設計に関わる部分なのでユースケースに合わせて考えてみましょう。
次回の内容について
今回は、ユーザーの体感速度を向上させるための手法として「ページを素早く表示する」と「ユーザーに素早くインタラクションを返す」の2つを解説しました。次回は、残りのひとつ「ユーザーの操作を阻害しない」について、JavaScriptのシングルスレッドやイベントループを絡めつつ、HTML5のWeb Workersなどについても解説していきます。
[注] defer属性は、HTML5ではなくHTML4で標準化されているというご指摘を受け、2013/7/22に修正しました。