HTMLからJavaScriptを読み込むには、script要素のsrc属性へJavaScriptファイルのパスを指定します。ただし、書く場所やdefer・asyncの選び方を間違えると、「ファイルはあるのに動かない」「HTML要素を取得できない」といった問題が起こります。
この記事では、初心者がそのまま試せる外部ファイルの読み込み方から、HTMLへ直接書く方法、読み込み順序、JavaScriptモジュール、エラーの確認方法まで解説します。
HTMLからJavaScriptを読み込む基本
通常のWebページでは、HTMLとJavaScriptを別ファイルに分け、HTMLのhead内からdefer付きで読み込む方法が使いやすいです。まず、同じフォルダへindex.htmlとscript.jsを用意します。
sample-site/ ├── index.html └── script.js
index.htmlへ、ボタンと結果を表示する要素を作ります。script要素はhead内へ書き、src="script.js"で外部ファイルを指定してください。
<!doctype html>
<html lang="ja">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>JavaScriptの読み込み練習</title>
<script src="script.js" defer></script>
</head>
<body>
<button class="message-button" type="button">メッセージを表示</button>
<p class="result"></p>
</body>
</html>次に、script.jsへ処理を書きます。ボタンをクリックすると、空だった段落へメッセージが入ります。
const button = document.querySelector('.message-button');
const result = document.querySelector('.result');
button.addEventListener('click', () => {
result.textContent = 'JavaScriptを読み込めました!';
});ブラウザでindex.htmlを開き、ボタンを押して「JavaScriptを読み込めました!」と表示されれば成功です。deferがあるため、ブラウザはHTMLの解析を進めながらファイルを取得し、HTMLの解析が終わった後にJavaScriptを実行します。
script要素とsrc属性の役割
script要素は、HTMLへ実行可能なスクリプトを組み込むための要素です。src属性はsourceの略で、読み込む外部ファイルの場所をURLとして指定します。
<script src="script.js" defer></script>外部ファイルを読み込むscript要素にも終了タグ</script>が必要です。<script src="script.js" />のような自己終了タグにはしないでください。
JavaScriptファイルのパスを正しく指定する
JavaScriptが読み込めない原因として多いのが、srcのパス間違いです。パスは、基本的にscript要素を書いたHTMLファイルの場所を基準に考えます。
HTMLとJavaScriptが同じフォルダにある場合
<script src="script.js" defer></script>./script.jsと書いても同じ場所を表せます。最初の./は「現在のフォルダ」という意味です。
jsフォルダの中にある場合
sample-site/
├── index.html
└── js/
└── script.js
<script src="js/script.js" defer></script>ファイル名の大文字と小文字も区別して確認しましょう。開発中のパソコンでは動いても、公開先のサーバーでScript.jsとscript.jsが別ファイルとして扱われることがあります。
相対パスと絶対パスの考え方はCSSファイルの読み込みでも共通です。パス指定から練習したい方は、次の記事も参考にしてください。
script・defer・async・moduleの違い
同じ外部JavaScriptでも、script要素の属性によってダウンロードと実行のタイミングが変わります。HTML要素を操作する一般的なスクリプトなら、まずdeferを選ぶと理解しやすいです。

| 書き方 | HTML解析中の取得 | 実行タイミング | 実行順序 | 主な用途 |
|---|---|---|---|---|
| 属性なし | 解析を止めて取得 | 取得後すぐ | 記述順 | 即時実行が必要な古典的スクリプト |
defer | 解析と並行 | HTML解析後 | 記述順 | DOMを操作する一般的な外部スクリプト |
async | 解析と並行 | 取得でき次第 | 保証されない | 他の処理へ依存しないスクリプト |
type="module" | 解析と並行 | HTML解析後が基本 | 依存関係を解決 | import・exportを使うモジュール |
属性なしのscriptはHTML解析を止める
async、defer、type="module"のない外部スクリプトに出会うと、ブラウザはHTMLの解析を一時停止し、ファイルを取得して実行してから続きを解析します。
<script src="script.js"></script>この書き方をhead内で使い、JavaScriptが後にあるHTML要素を探すと、まだ解析されていないためnullになることがあります。ファイルの取得中もHTML解析が止まるため、通常は理由なく選びません。
deferはHTML解析後に記述順で実行する
deferを付けた外部スクリプトはHTML解析と並行して取得され、解析が終わってから実行されます。複数ある場合は、HTMLに書いた順序で実行されます。
<script src="vendor.js" defer></script>
<script src="script.js" defer></script>script.jsがvendor.jsの機能を使う場合でも、この順序を保てます。また、HTML解析後に実行されるため、外部ファイル内で通常はDOMContentLoadedを重ねて待つ必要がありません。
asyncは取得できたファイルから実行する
asyncもHTML解析と並行してファイルを取得しますが、取得が終わるとすぐに実行します。複数ファイルの実行順序は保証されません。
<script src="independent-tool.js" async></script>ほかのJavaScriptやDOMに依存せず、取得でき次第動いてよい処理に向いています。順番が重要なライブラリと自作コードへ、まとめてasyncを付けるのは避けましょう。
type=”module”はimport・exportを使える
JavaScriptを機能ごとのファイルへ分けるなら、type="module"を使えます。モジュールスクリプトは既定で遅延実行されるため、deferを追加する必要はありません。
<script type="module" src="js/main.js"></script>export function createMessage(name) {
return `こんにちは、${name}さん!`;
}import { createMessage } from './message.js';
const result = document.querySelector('.result');
result.textContent = createMessage('田中');モジュール内の相対パスでは、./message.jsのように./と拡張子まで書きます。モジュールをローカルで試すときは、file://で直接開かず、開発用のローカルサーバーを使うとCORSやMIMEタイプの問題を避けやすくなります。
body終了タグ直前へscriptを書く方法
script要素を</body>の直前へ置く方法も動作します。その時点では前にあるHTML要素の解析が終わっているため、DOMを取得できます。
<button class="message-button" type="button">メッセージを表示</button>
<script src="script.js"></script>
</body>ただし、「JavaScriptは必ずbodyの最後」と決まっているわけではありません。head内へdefer付きで書けば、HTML解析を妨げず早い段階からファイル取得を始められ、読み込み場所もまとまります。
bodyの最後へ置く理由とDOMの読み込み順序は、次の記事でも実例を使って解説しています。
HTMLへJavaScriptを直接書く方法
短い動作確認なら、script要素の中へJavaScriptを直接書くインラインスクリプトも使えます。この場合はsrc属性を付けません。
<button class="message-button" type="button">メッセージを表示</button>
<p class="result"></p>
<script>
const button = document.querySelector('.message-button');
const result = document.querySelector('.result');
button.addEventListener('click', () => {
result.textContent = '読み込みに成功しました!';
});
</script>インラインスクリプトへdeferを付けても、通常のスクリプトでは効果がありません。head内に直接書きつつDOMの解析を待つ必要がある場合は、DOMContentLoadedイベントを利用できます。
<script>
document.addEventListener('DOMContentLoaded', () => {
const result = document.querySelector('.result');
result.textContent = 'HTMLの解析が終わりました。';
});
</script>実際の制作では、HTMLと処理を整理し、複数ページから再利用しやすくするため、外部ファイルへ分けるのが基本です。HTML内へ直接書く具体例はこちらでも確認できます。
JavaScriptを読み込めないときの確認方法
Consoleで最初のエラーを確認する
Chromeではページ上で右クリックして「検証」を選び、DevToolsの「Console」を開きます。赤いエラーが複数ある場合は、後続エラーの原因になりやすい最初のエラーから確認してください。
Cannot read properties of nullと表示される場合は、セレクタの書き間違いか、対象HTMLより先にJavaScriptが実行されている可能性があります。クラス名を確認し、外部スクリプトならdeferを付けましょう。
NetworkでJavaScriptのHTTPステータスを確認する
DevToolsの「Network」を開いてページを再読み込みし、対象の.jsファイルを探します。404ならパスやファイル名が間違っています。200でも動かない場合は、Consoleの構文エラーやMIMEタイプを確認します。
JavaScriptの先頭で読み込みを確認する
ファイルそのものが実行されているか分からないときは、script.jsの先頭へ一時的にconsole.log()を書きます。
console.log('script.jsを読み込みました');Consoleへ表示されればファイルの取得と実行までは成功しています。確認後は、不要になったログを削除してください。
defer=”false”と書いていないか
deferとasyncは真偽属性です。属性が存在すれば有効になるため、defer="false"と書いても無効にはなりません。使わない場合は属性自体を削除します。
ライブラリより先に自作コードを実行していないか
jQueryなどのライブラリを使う場合は、ライブラリを先、自作コードを後に読み込みます。順序が必要なら両方へdeferを使い、asyncは使いません。
<script src="js/library.js" defer></script>
<script src="js/app.js" defer></script>CDNからjQueryを読み込む具体的な方法と注意点は、次の記事で解説しています。
HTMLでJavaScriptを読み込むときのよくある質問
script要素はheadとbodyのどちらに書きますか?
一般的な外部JavaScriptなら、head内へdefer付きで書く方法が分かりやすいです。古典的な属性なしスクリプトを使う場合は、DOMより後に実行するため</body>直前へ置く方法もあります。
type=”text/javascript”は必要ですか?
通常のJavaScriptを読み込むだけなら、type="text/javascript"は省略できます。ESモジュールとして扱う場合は、type="module"を指定します。
deferとasyncを両方付けるとどうなりますか?
両方を指定すると、対応ブラウザではasyncとして扱われます。実行順序が必要ならdeferだけを使い、独立して取得後すぐに動いてよい場合だけasyncを選びましょう。
まとめ
HTMLからJavaScriptを読み込む基本は、script要素のsrc属性へファイルのパスを書くことです。DOMを操作する一般的な外部スクリプトなら、head内でdeferを付けると、HTML解析後に記述順で実行できます。
ほかの処理に依存しないスクリプトにはasync、import・exportを使う構成にはtype="module"を選びます。動かないときは、パスを見直し、DevToolsのConsoleとNetworkを順番に確認してみてください。
