LoginSignup
6
5

More than 3 years have passed since last update.

ロード開始からDOMContentLoadedまでとパーサー

Posted at

HTMLに書いたJavaScript実行のタイミングを改めて見返していたら、いろいろと発見がありました。

TL; DR

  • DOMContentLoadedはパーサーのイベント
  • <script>要素には、表に現れない内部属性がある
  • タイマーでDOMContentLoadedより前に割り込める

DOMContentLoadedは何のイベント?

よくJavaScriptの実行タイミングとして使われるDOMContentLoadedですが、厳密に言えば何が起きているかをご存知でしょうか。実は…

Fired at the Document once the parser has finished (HTML LSより)

と、HTMLのパーサーが完了したタイミングで起きるイベントなのです。

パーサーとJavaScript

「パースだけならすぐ終わってしまうんじゃない?」というのが正直な感想かと思いますが、HTMLのパーサーは単純にHTMLだけの処理をしているわけではありません。

さらに厳密な話は後からしていきますが、文書の途中に書いた<script>がそれ以前のDOMだけ読める、というように、HTML内にあるJavaScriptは、HTMLのパースを中断することがあります。

もっと強烈なものとして、document.write()があります。これはHTMLとして出来上がっていない断片(開きタグだけとか、コメントに入る<!--だけとか)すら出力できるというように、document.write()の結果は文書全体のパーサに影響することとなります(このため、<script>以降のパースは、JavaScriptを実行し終えてからでないと行えません)。

<script>要素の内部属性

HTMLやJavaScript処理の過程で、<script>にはいくつかのフラグが付けられます。

  • already started…実行済みかどうか
  • parser-insertedDocumentのパーサーが生成した(HTMLに書いてあった、もしくはdocument.writeで出力した)か
  • non-blocking…スクリプトのロードがドキュメントをブロックしないか(基本的に、parser-insertedの場合にfalseとなります)。
    • なお、asyncとは別管理となっていて、(あとからasyncを付け外ししたときに実行漏れになるのを防ぐためなのか、理由ははっきりわかりませんが)asyncを付けたスクリプトに対してはnon-blockingはfalseとなります。

なお、以下の解説ではできるだけふつうのHTMLに即した形にしてありますが、動的に<script>要素を作り変えた場合など、特殊な場合にはこれらのフラグで考える必要があります。

状況に応じた挙動

<script>要素の状況に応じて、それがどのように実行されるか区分していきます。なお、煩雑になるのを避けるため、type="module"の場合の挙動については省略します。

HTMLに書いてあった、あるいはdocument.write()で生成した場合

  • srcあり、deferあり、asyncなしの場合…パーサーの完了時に、HTMLにある順序に従って実行される(実行が終わるまでパーサーは完了しません)
  • srcあり、deferなし、asyncなしの場合…JavaScriptのロード完了までパーサーを止めて、その場で実行します。 ※
  • srcあり、asyncありの場合…読み込みが終わり次第、文書での順番に関係なく実行される。
  • srcなし(直書き)の場合…パーサーを止めて、その場で実行します。 ※

※: 「その場で実行する」ものの場合、これ以前に読み込まれたスタイルシートがあると、その読み込みを待つこととなります。本来、スタイルシートの読み込みはパーサーをブロックしない(DOMContentLoaded影響しないMDN))のですが、同期的に実行されるスクリプトが1つでもあると、「スクリプトがパーサーをブロックする」→「スタイルシートがスクリプトをブロックする(HTML LS)」の2段構成で、スタイルシートもDOMContentLoaded前のロードが強制される形となります。

DOMで作った場合

もちろん、DOMと言ってもdocument.write()は除きます。

  • srcありの場合…読み込みが終わり次第、文書での順番に関係なく実行される(asyncがある場合と同じ)。
  • srcなしの場合…ドキュメントツリーへの挿入と同時に、同期的に実行される。

DOMContentLoaded前に処理を割り込ませる

deferとなったスクリプトがある場合、それが読み込まれるまでDOMContentLoadedが発生しませんので、ファーストビューがDOMContentLoaded前に発生することもあります。そのような状況で「仮想DOMを出来るだけ早くマウントしたい」という場合に、挿入する場所の直後、あるいは</body>直前に<script>を書くことで、そのタイミングで実行させるという方法があります。

ページの上に入れた場合にも、タイマー系はDOMContentLoadedと関係なく動作しますので、requestAnimationFrameでループさせて、目的のエレメントを検出する、なんていうことをやってみてもいいかもしれません。

参照

6
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
5