こんにちは、かつコーチです。
前回は、開発者ツールのコンソールを使って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の比較表
| 項目 | 属性なし | defer | async |
|---|---|---|---|
| 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でのデバッグ入門