読み書きプログラミング

日常のプログラミングで気づいたことを綴っています

JavaScriptパフォーマンスベストプラクティスその3

Nokia Developerより

よりよいロードパフォーマンスの秘訣

より速いスタートアップとスプラッシュスクリーンの表示のためにはブロッキングなしにスクリプトをロードすること
  • <script>タグがHTMLドキュメントの中に見つかると、参照されたスクリプトリソースをダウンロードし実行する。この間レンダリングエンジンは他のリソースをダウンロードすることができない。これはタグの下のページのレンダリングを事実上ブロックする。このブロックの振る舞いを避けるには、スクリプトタグを動的スクリプトタグ挿入として知られる機構を介して生成すればよい。
  • この機構のおかげでレンダリングエンジンは、JavaScriptリソースがまだロードか実行中の間に、HTMLで定義された初期ビュー(スプラッシュスクリーン)をすぐに描画し表示することができる。これはよりよいユーザー体験に繋がる。
  • When <script> tags are found in the HTML document the referenced script resources are downloaded and executed before the rendering engine can continue to download other resources which effectively blocks the rendering of the page below the tag. To avoid this blocking behaviour a script tag can be created via a mechanism known as a dynamic script tag injection.
  • This mechanism allows the rendering engine to immediately render and display the initial view (aka the splash screen) defined in HTML while the JavaScript resources are still being loaded and executed which leads to better user experience.

遅い:

<div id="splash"/>
<script src="my-script-file.js" type="text/javascript"></script>

速い:

<div id="splash"/>
// JavaScript
function loadScript(src, callback) {
    var head = document.getElementsByTagName('head')[0],
        script = document.createElement('script');
    done = false;
    script.setAttribute('src', src);
    script.setAttribute('type', 'text/javascript');
    script.setAttribute('charset', 'utf-8');
    script.onload = script.onreadstatechange = function() {
        if (!done && (!this.readyState || this.readyState == 'loaded' || this.readyState == 'complete')) {
            done = true;
            script.onload = script.onreadystatechange = null;
                if (callback) {
                    callback();
                }
            }
    }
    head.insertBefore(script, head.firstChild);
}
 
// load the my-script-file.js and display an alert dialog once the script has been loaded
loadScript('my-script-file.js', function() { alert('my-script-file.js loaded.'); });

ソース:

ExpiresかCache-Control HTTPヘッダを加えること

Apacheの場合、サーバーレスポンスでのExpires HTTPヘッダとCache-Control HTTPヘッダのmax-ageディレクティブは.htaccessで設定できる。構文法は以下の通り:

Using Apache the Expires HTTP header and the max-age directive of the Cache-Control HTTP header in server responses can be configures in .htaccess. The syntax is as follows:

ExpiresDefault "<base> [plus] {<num> <type>}*" ExpiresByType type/encoding "<base> [plus] {<num> <type>}*"

例: ExpiresActive On ExpiresByType image/png "access plus 1 year"

ソース: Apache mod_expires

JavaScriptCSSリソースをGzipすること

以下は、JavaScriptCSSだけでなく、HTML, XML, JSONgzipするようにする簡単な設定である。これを達成するには、以下をApache .htaccessに設定しなければいけない:

Below is a simple configuration to gzip not only JavaScript and CSS but also HTML, XML and JSON. To accomplish this, the following must be set in the Apache .htaccess:

AddOutputFilterByType DEFLATE text/html text/css text/plain text/xml application/x-javascript application/json

ソース:

YUI CompressorかJSMinを使ってコードを圧縮すること
  • 最高: YUI Compressorは総時間=ダウロード時間+評価時間として考えた時、全体にベストのパフォーマンスを提供する。Rhinoに依存していて、リアルタイム圧縮には使えない。
  • 簡単: JSMinはほとんどすべての言語で実装があり、リアルタイム圧縮適用可能である。gzip後のサイズはYUI Compressorのそれにかなり近い。
  • JSMinかYUI COmpressor + gzippingはDean EdwardsのPacker + gzippingよりも性能がよい。Packerは(バイトサイズで)最小のコードを提供するけれど、スクリプトがクライアントサイドでRegExp?のワンパスを使ってアンパックされるので、評価がとても遅い。これは遅いモバイルデバイス上では特に大きなオーバーヘッドを持ち込むことになる。
  • The Best: YUI Compressor provides the best overall performance when you consider it as: Total_Speed = Time_to_Download + Time_to_Evaluate. Depends on Rhino and is not applicable for real-time compression.
  • Simple: JSMin has implementations in nearly all the languages and is applicable for real-time compression. After gzipping the size comes very close to that of YUI Compressor.
  • JSMin or YUI Compressor + gzipping is better performer than Dean Edwards' Packer + gzipping. Although Packer provides the smallest (byte-size) code it will evaluate much slower as the scripts are unpacked on the client-side using one pass of a RegExp? this introduces an overhead especially significant on slow mobile devices.

ソース

リソースの数とサイズを最小化すること
  • 複数スクリプトを1つに連結すること。しかし、いくつかのモバイルブラウザではキャッシュに保持するリソースの大きさに制限があることを考慮すること。
  • 常にミニフィケーションの前に最小のコードサイズを目標とし、再利用可能なコードを増やすようリファクタすること。すべてのリソースを考慮すること: HTML, JavaScript, CSS, images, XHRを使ってロードされるJSON
  • ホストがリソースをより効率的に並列ロードできるようにリソースの数を最小化すること
  • 1ホスト当たり2リソースの並列ロードというHTTP/1.1制限に「打ち勝つ」ためにコンテンツを異なるホストから配ること
  • Concatenate multiple scripts into one. However, consider that some mobile browsers have limits in how big resources they keep in cache.
  • Always aim for the smallest code size prior to minification and re-factor to increase re-usable code, consider all resources: HTML, JavaScript, CSS, images, JSON loaded using XHR.
  • Minimize the number of resources per host to enable more efficient parallel loading of resources
  • Serve your content from different hosts to overcode HTTP/1.1 limitation of parallel loading of two resources per host.
並列ダウンロードをブロックせずにスクリプトをロードすること
  • Multiple scripts can be loaded in parallel without blocking using techniques such as via normal script src, XHR eval, XHR injection, script in iframe, script DOM element, script defer and document.write script tag.
  • Depending on your specific constraints (resources in the same/different domain, need to preserve script loading order, need to show browser loading indicator) you can do an informed decision by checking the decision tree in the following presentation (slide 26).

Even Faster Websites - Steve Souders at SXSW ‘09

非同期スクリプトを組み合わせること
  • スクリプトに依存したインラインスクリプトがあるなら、複数のオプションがある: hardcoded callback, window.onload, timer, degrading script tags, script onload.
  • 詳しくはスライド35以降を参照すること:
  • If you have inline script which depend on the script you have multiple options: hardcoded callback, window.onload, timer, degrading script tags and script onload.
  • See slide 35 onwards for details:

Even Faster Websites - Steve Souders at SXSW ‘09

スタイルシートの上にインラインスクリプトを移動させること
  • Browsers download stylesheets in parallel with other resources that follow unless the stylesheet is followed by an inline script.
  • Workaround is to either avoid inline scripts or move them above stylesheets or below other resources (e.g. images, scripts) and use , not @import.
iframeの使用を控えること
  • 親のonloadはiframeとその要素すべてをダウンロードするまで起こらない。
  • SafariChromeの対処策はJavaScriptでiframe srcを設定することだ。
  • iframeは親と接続プールを共有する。それは通常ホスト当たり2接続。
  • Parent's onload doesn't fire until iframe and all its components are downloaded.
  • Workaround for Safari and Chrome is to set iframe src in JavaScript.
  • Scripts and stylesheets also block iframe from loading.
    • In IE and FF stylesheets in the parent block the iframe or its resources
  • iframe shares connection pool with parent, typically 2 connections per host.