doScrollによるDOMContentLoadedエミュレーションの落とし穴

今更こんなことが問題になるケースは稀だと思うが、『パーフェクトJavaScript』の中でも紹介されていたので注意として書いておく。

IE8以前でDOMContentLoadedイベントをエミュレートする方法として、doScrollを使ったハックは広く知られている。例として『パーフェクトJavaScript』230ページのリスト8.9より引用。

function IEContentLoaded (callback) {
    (function () {
        try {
            document.documentElement.doScroll('left');
        } catch (error) {
            setTimeout(arguments.callee, 0);
            return;
        }
        callback();
    })();
}

しかし実際に試してみればわかるが、これだとwindow.onloadより実行が遅くなる場合がある。

テスト1(画像なし) テスト2(画像あり)

具体的には

  • 画像など外部から読み込まれるリソースが少ない場合
  • リロードした時にキャッシュがきいている場合

こういったケースではwindow.onloadとdoScrollハックの実行順序は逆転する場合がある。もちろん逆転しない場合もある。doScrollハックはあくまでハックであって正式なイベントシステムの一部ではないのだから、一貫した動作をしなくても当然だと言える。

この実行順序の逆転を防ぐために、世の中のライブラリではdocument.onreadystatechangeイベントを併用するのが習わしとなっている。例えばdoScrollハックを世に広めたDiego Perini氏の実装を見れば、trying to always fire before onload というコメントとともにreadyStateをチェックするコードが入っているのが分かる。

document.onreadystatechange = function() {
    if (document.readyState == 'complete') {
        //コールバック関数を実行し、以後はdoScrollハックが動かないようにしておく
    }
};

テスト1(画像なし) テスト2(画像あり)

現実問題としては、わざわざ自前でdoScrollハックを書くような状況では、この順序の逆転が問題になることはまずないと思われる。しかし以下のような教訓を読み取ることはできるだろう:

  • 広く知られていようがハックはハック。使うときは慎重になろう
  • 可能な限り信頼できる既存の実装を使おう
  • 自分の手で実験しよう

Leave a Reply