script要素のdefer属性でパフォーマンスを向上
Updated / Published
DOM構築が完了した時点でスクリプトを実行したいというタイミングは活用どころがとても多いのですが、2013年9月時点の最新ブラウザシェアを見てもInternet Explorer8以下が未だに25%以上もいることから、IE9からサポートしているDOMContentLoaded
は使い難く、未だにイベントハンドラのonload
属性やwindow.onload
といった画像の読み込みまで含めてしまうパフォーマンスの悪いタイミング操作でスクリプトの実行を行っている制作者は多くいるようです。
そこで、レンダリングができた時点で、スクリプトが実行されるようにUAに指示するための属性であるscript
要素のdefer
属性を紹介します。defer
属性の付いたスクリプトファイルの実行は、DOMContentLoaded
の前に行われるため、DOMContentLoaded
やwindow.onload
よりもパフォーマンス向上が計れます。また、旧Presto版Operaとスマートフォン市場を除けば、すでに2011年頃よりdefer
属性をDOMContentLoaded
の代わりに利用できる(活用できる)環境は整っていました。
defer属性について
もともとは、1997年にリリースされたMicrosoft Internet Explorer 4.0の時に独自に採用された機能です。当時はブラウザ戦争真っ只中、独自のスクリプト言語を開発したりと非常にスクリプトの開発に熱心であったMicrosoftは、スクリプトの読み込みや実行で他の要素の読み込みやレンダリングがブロックされてしまうことを避けるためにdefer
属性を実装しました。
そして、defer
属性はHTML4からは正式に仕様として採用・公認されますが、サポートしていたのは相変わらずInternet Explorerのみで他のブラウザにおけるサポートが進むことはありませんでした。しかし、HTML5仕様の策定を皮切りにFirefoxではFirefox 3.1から、SafariではSafari 5.0から、Google ChromeではChrome 8.0から、同様にスマートフォンにおいてもiOS 5.0から、Android 3.0からと次々にdefer
属性をサポートしていきました。このように2011年の時点でデスクトップでは9割以上のブラウザがdefer
属性をサポートしていたことになります(参考:Can I use defer attribute for external scripts?)。
デスクトップ主要ブラウザの中ではOperaのみがサポートしていないブラウザとして残ったのですが、2013年7月にOpera 15.0としてリリースされた、OperaからはChromiumのレンダリングエンジンとしてBlinkを搭載したものになっており、defer
属性をサポートしています。しかし、すべてのOperaユーザが新しいBlink版Operaに乗り換えたということはなく、またLinux OSユーザにおいては未だに旧Presto版レンダリングエンジンを搭載したOperaしか配布されていない状態です。そして、まだしばらくは強制的にBlink版Operaへのアップデートが行われることもないため、現在のOperaユーザの大半はバージョン12.16の旧Presto版Operaを利用されていることがほとんです。そのためdefer
属性で障壁となるのは、これをサポートしていない旧Presto版Operaということになります。
もっとも大きな問題は、未だにAndroid端末シェアにおいて、そのシェア数が30%以上近くあるAndroid2.3以前のブラウザが未対応であることです。
サポート状況を考慮したdefer属性の使い方
先にも述べたようにdefer
属性のサポート状況は良好ですが、問題となるのは、旧Presto版OperaとAndroid2.3以前のブラウザということになります。
まず、head
要素やbody
要素内でscript
要素で提供するスクリプトファイル(sample.js
)にdefer
属性をつけます。
<script src="sample.js" defer></script>//HTMLの場合
<script src="sample.js" defer="defer"></script>//XHTMLの場合
次に、defer
属性の付いたスクリプトファイル(sample.js
)で一工夫するだけです。
function load(){
/* DOM構築完了時に実行したいスクリプト*/
};
(function(){
/*旧Presto版Operaにのみwindow.operaが存在
Blink版Operaにwindow.operaは存在しない
タッチデバイスでかつwindow.matchMediaをサポートしていない
のは、ちょうどiOS4以前とAndroid2.3以前だけになる
*/
if(window.opera || (document.ontouchstart !== undefined && !window.matchMedia)){
document.addEventListener("DOMContentLoaded", load, false);
}else{
load();
}
})();
旧Presto版Operaを対象としたwindow.opera
とAndroid2.3以前を対象したdocument.ontouchstart !== undefined && !window.matchMedia
内のコードについては、そのままDOMContentLoaded
のタイミングでイベントが実行されることになります(参考:Can I use matchMedia?)。
下段のload();
だと本来は読み込みと同時に即時実行ですが、defer
属性をサポートするUAでは、レンダリングを完了した段階で実行してくれることになります。このようにDOMContentLoaded
やwindow.onload
の代わりとしてよりパフォーマンスを向上させるのにdefer
属性を用いることができるので、今後の活用が期待できそうです。
ひとつだけ注意点として、defer
属性の付いたスクリプトファイル内で即座に内容生成を行うdocument.write()
を使用することはJavaScriptの仕様上できません。そのためHTML4仕様におけるdefer
属性の解説は「スクリプトに内容生成を含まないことを宣言する」となっていました。わかりやすく言うと、文書のレンダリングを終えてから実行させる関数のみを含んでいるスクリプトファイルであることを宣言するというのが活用目的にあったわかりやすいdefer
属性の解釈になるかと思います。