script要素のdefer属性でパフォーマンスを向上

Updated / Published

DOM構築が完了した時点でスクリプトを実行したいというタイミングは活用どころがとても多いのですが、2013年9月時点の最新ブラウザシェアを見てもInternet Explorer8以下が未だに25%以上もいることから、IE9からサポートしているDOMContentLoadedは使い難く、未だにイベントハンドラのonload属性やwindow.onloadといった画像の読み込みまで含めてしまうパフォーマンスの悪いタイミング操作でスクリプトの実行を行っている制作者は多くいるようです。

そこで、レンダリングができた時点で、スクリプトが実行されるようにUAに指示するための属性であるscript要素のdefer属性を紹介します。defer属性の付いたスクリプトファイルの実行は、DOMContentLoadedの前に行われるため、DOMContentLoadedwindow.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では、レンダリングを完了した段階で実行してくれることになります。このようにDOMContentLoadedwindow.onloadの代わりとしてよりパフォーマンスを向上させるのにdefer属性を用いることができるので、今後の活用が期待できそうです。

ひとつだけ注意点として、defer属性の付いたスクリプトファイル内で即座に内容生成を行うdocument.write()を使用することはJavaScriptの仕様上できません。そのためHTML4仕様におけるdefer属性の解説は「スクリプトに内容生成を含まないことを宣言する」となっていました。わかりやすく言うと、文書のレンダリングを終えてから実行させる関数のみを含んでいるスクリプトファイルであることを宣言するというのが活用目的にあったわかりやすいdefer属性の解釈になるかと思います。