【JavaScript】scriptタグの書き方:headとbodyどちらに書く?defer/asyncの違い

javascript-icon JavaScript

こんにちは、かつコーチです。

前回は、開発者ツールのコンソールを使ってJavaScriptを試す方法を紹介しました。

今回は、実際のWebページにJavaScriptを読み込ませるための<script>タグについて解説します。

「headに書くの?bodyに書くの?」「defer・asyncって何が違うの?」というのは、初心者が必ず一度は疑問に思うポイントなので、ここでしっかり整理しておきましょう。

scriptタグとは?

JavaScriptをHTMLに読み込む方法

<script>タグは、HTMLファイルの中にJavaScriptのコードを埋め込んだり、別ファイルのJavaScriptを読み込んだりするためのタグです。

書き方は大きく分けて2種類あります。

<!-- 1. HTMLファイルの中に直接コードを書く -->
<script>
  console.log("直接書いたコードです");
</script>

<!-- 2. 別ファイルのJavaScriptを読み込む -->
<script src="main.js"></script>

実際の開発では、HTMLとJavaScriptを別ファイルに分けて、src属性で読み込む書き方が基本になります。

コードが増えてくると、HTMLファイルの中に直接書くよりも、別ファイルに分けたほうが管理しやすいためです。

なぜ「書く場所」が問題になるのか

HTMLは、基本的に上から下に向かって順番に読み込まれていきます。

<script>タグに差し掛かると、ブラウザは原則としてその場でJavaScriptの読み込み・実行を待ってから、続きのHTMLを読み進めます。

つまり、書く場所やタイミングを間違えると、次の2つの問題が起こりえます。

  • ページの表示が遅くなる(JavaScriptの読み込みを待つ間、画面が真っ白になる)
  • JavaScriptがエラーになる(まだ存在しないHTML要素を操作しようとしてしまう)

この問題を理解するために、まずは「headに書く場合」と「bodyの最後に書く場合」を比べてみましょう。

headに書く場合とbodyの最後に書く場合

❌ Before:headに何も指定せず書いてしまう

<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <title>サンプルページ</title>
  <script src="main.js"></script>
</head>
<body>
  <button id="greetButton">挨拶する</button>
</body>
</html>
// main.js
document.getElementById("greetButton").addEventListener("click", function () {
  alert("こんにちは!");
});

このコードをブラウザで開くと、コンソールに次のようなエラーが表示されます。

Uncaught TypeError: Cannot read properties of null (reading 'addEventListener')

<head>の中でmain.jsが読み込まれた時点では、まだ<body>内の<button>が存在していません。

そのため、document.getElementById("greetButton")null(何も見つからなかった)を返し、そのnullに対してaddEventListenerを呼び出そうとしてエラーになるのです。

私も初心者の頃、このエラーメッセージの意味がわからず「nullって何?」と検索しまくった記憶があります。

原因は「JavaScriptがHTML要素より先に読み込まれてしまっていた」という、書く順番の問題でした。

✅ After:bodyの最後に書く

<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <title>サンプルページ</title>
</head>
<body>
  <button id="greetButton">挨拶する</button>

  <script src="main.js"></script>
</body>
</html>

<script>タグを</body>の直前、つまりHTMLの一番最後に移動させます。

こうすることで、<button>を含むすべてのHTML要素が読み込まれたにJavaScriptが実行されるため、nullエラーが起きなくなります。

昔から使われている、最もシンプルで確実な解決方法です。

defer属性・async属性という選択肢

なぜ属性が用意されているのか

「bodyの最後に書く」方法は確実ですが、HTMLとJavaScriptの読み込み場所が離れてしまい、管理がしづらくなる面もあります。

そこで用意されているのが、<head>に書いたまま読み込みタイミングを制御できるdefer属性とasync属性です。

<head>
  <script src="main.js" defer></script>
</head>
<head>
  <script src="main.js" async></script>
</head>

どちらも「HTMLの読み込みをブロックせずにJavaScriptファイルをダウンロードする」という点は共通していますが、実行されるタイミングに違いがあります。

defer・asyncの比較表

項目属性なしdeferasync
HTML解析への影響読み込み中は解析が止まる解析を止めずに並行ダウンロード解析を止めずに並行ダウンロード
実行タイミングダウンロード完了後すぐHTML解析が終わった後ダウンロードが終わり次第すぐ
実行順序記述順を保証記述順を保証保証されない(早く着いた順)
HTML要素へのアクセス場所によっては失敗する常に安全にアクセス可能実行タイミング次第で失敗する場合がある

結局どちらを使えばいいのか:判断軸

初心者のうちは、次の判断軸で選べば迷いません。

  • ページの動作に必要なJavaScript(ボタンの処理やメニューの開閉など)deferを使う
    • HTML解析後に、書いた順番通り実行されるため安全
    • 通常のWebページ制作では、これが基本の選択肢になる
  • 他のスクリプトと依存関係がない、単独で動く処理(アクセス解析タグなど)asyncを使う
    • 実行順序を気にしなくてよい処理であれば、少しでも早く実行できるメリットがある
  • 迷ったらdeferを使い、bodyの最後に書く方法からは卒業する

まとめると、「HTML要素を操作するようなJavaScriptならdefer」「HTMLと無関係に単独で動く処理ならasync」と覚えておくと実務でも困りません。

実際の書き方のおすすめパターン

現在のおすすめは「head + defer」

昔ながらの「bodyの最後に書く」方法も間違いではありませんが、現在は<head>defer属性をつけて書く方法がより推奨されています。

<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <title>サンプルページ</title>
  <script src="main.js" defer></script>
</head>
<body>
  <button id="greetButton">挨拶する</button>
</body>
</html>

<head>にまとめて書けるので、HTMLファイルを見返したときに「どんなJavaScriptを読み込んでいるか」がひと目でわかりやすくなるというメリットもあります。

複数のscriptタグがある場合の注意点

deferは記述順が保証されるため、複数のファイルを読み込むときも安心して並べられます。

<head>
  <script src="utility.js" defer></script>
  <script src="main.js" defer></script>
</head>

main.jsの中でutility.jsの機能を使っている場合でも、deferを使っていれば書いた順番通りに実行されるので、意図しない動作エラーを防げます。

asyncの場合はこの順序が保証されないため、ファイル間で依存関係があるときは避けたほうが無難です。

まとめ

この記事のポイント

  • <script>タグは、HTMLの上から下への読み込み順の影響を受ける
  • headに何も指定せず書くと、HTML要素がまだ存在せずエラーになることがある
  • deferはHTML解析後に記述順通り実行され、asyncはダウンロード完了次第すぐ実行される
  • 迷ったら<head>defer属性をつけて書くのが、現在のおすすめパターン
  • 依存関係のあるスクリプトが複数ある場合は、順序が保証されるdeferを使う

次に読むべき記事

scriptタグの基本がわかったところで、次はJavaScriptを学ぶうえで欠かせないconsole.logを使ったデバッグの方法を解説します。

エラーを恐れずに手を動かせるようになるための第一歩です。

→ 次の記事:console.logでのデバッグ入門

タイトルとURLをコピーしました