JavaScript <script>タグ
開発環境を整え、最初のJavaScriptプログラム「Hello, World!」を書き終えた後、ふと疑問に思うかもしれません。「このJavaScriptコードは、一体どうやってWebページにリンクされ、実行されているのだろう?」
ここで登場するのが、<script> タグです。
<script> タグは基本的なHTML要素であり、HTMLの構造とダイナミックなJavaScript機能の間の架け橋となります。これはWebブラウザに対して「ねえ、ここに実行可能なコードがあるから、処理してね!」と伝える役割を果たします。
Web開発者にとって、このタグを「いつ」「どこで」使うかを理解することは極めて重要です。タグの位置や属性は、サイトのパフォーマンス、ユーザー体験(UX)、さらにはJavaScriptコードが正常に動作するかどうかにまで大きな影響を与えるからです。
1. JavaScriptの組み込み:インラインスクリプトと外部スクリプト
<script> タグを使用してJavaScriptをHTMLドキュメントに含めるには、主にインラインスクリプト (Inline Script) と 外部スクリプト (External Script) の2つの方法があります。それぞれの方法には特定のユースケースと意味があります。
1.1 インライン JavaScript (Inline JavaScript)
インラインJavaScriptとは、HTMLファイル内部の <script> タグの間に直接JavaScriptコードを記述することを指します。この方法は、小さなスクリプトやクイックテストには直感的ですが、保守性やパフォーマンスの観点から、通常は大規模なプロジェクトには推奨されません。
構文:
<script>
// あなたの JavaScript コードをここに書きます
</script>1.1.1 示例:<head> 内のインラインスクリプト
<script> タグがHTMLドキュメントの <head> セクションに配置されると、ブラウザはページの <body> をパースする前に、そのJavaScriptコードに遭遇し、実行します。つまり、スクリプトがまだロードされていないHTML要素を操作しようとすると、失敗する可能性が高くなります。
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Head 内のインラインスクリプト</title>
<script>
// このスクリプトはブラウザが遭遇した瞬間に実行されます。
// まだロードされていない要素を修正しようとすると、動作しません。
// document.getElementById('myParagraph').textContent = 'Head スクリプトが内容を修正しました!'; // この行は失敗します!
console.log("<head> スクリプトからの挨拶!"); // コンソールへの出力は可能です。
</script>
</head>
<body>
<p id="myParagraph">オリジナルの段落内容。</p>
</body>
</html>この例では、console.log メッセージは段落要素がページ上で完全にレンダリングされる前に、ブラウザのデベロッパーコンソールに表示されます。もし myParagraph にアクセスしようとする行のコメントアウトを解除すると、スクリプト実行時点では myParagraph がドキュメントオブジェクトモデル (DOM) 内に存在しないため、エラーがスローされます。
1.1.2 示例:<body> 内のインラインスクリプト
<script> タグを <body> セクションの最後(閉じタグ </body> の直前)に配置するのは一般的な手法です。これにより、スクリプトより上のHTMLコンテンツが既にパースされ、レンダリングされていることが保証されるため、JavaScriptで安全に要素を操作できるようになります。
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Body 内のインラインスクリプト</title>
</head>
<body>
<p id="myParagraph">オリジナルの段落内容。</p>
<script>
// このスクリプトは 'myParagraph' 要素がパースされた後に実行されます。
const paragraphElement = document.getElementById('myParagraph');
if (paragraphElement) {
paragraphElement.textContent = 'Body スクリプトが内容を修正しました!';
}
console.log("<body> スクリプトからの挨拶!");
</script>
</body>
</html>ここでは、スクリプトが myParagraph 要素の存在した後に実行されるため、コンテンツの検索と修正に成功します。これは、HTMLとJavaScriptを併用する際に「実行順序」がいかに重要かを示す核心的なコンセプトです。
1.2 外部 JavaScript ファイル (External JavaScript Files)
ある程度の規模のJavaScriptコードを書く場合、コードを独立した .js ファイルに記述し、<script> タグの src 属性を使用してHTMLドキュメントにリンクさせることがベストプラクティスです。
構文:
<script src="path/to/your-script.js"></script>外部JavaScriptのメリット:
- 関心の分離 (Separation of Concerns): HTMLは構造に専念し、JavaScriptは振る舞いに専念させることで、コードをクリーンに保てます。これは優れたWeb開発の基本原則です。
- キャッシュ (Cacheability): ブラウザは外部
.jsファイルをキャッシュできます。ユーザーが再度サイトを訪れた際、ブラウザはJavaScriptを再ダウンロードする必要がないため、ページロードが高速化されます。 - 再利用性 (Reusability): 同じJavaScriptファイルを複数のHTMLページにリンクできるため、コードの重複を避けられます。
- メンテナンスの容易さ (Easier Maintenance): JavaScriptのロジックを修正する際、複数のHTMLファイルを探す必要がなく、1つの
.jsファイルを修正するだけで済みます。
1.2.1 示例:外部スクリプトのリンク
まず、index.html と同じディレクトリ(または js/script.js のようなサブフォルダ)に、script.js(または意味のある名前)というファイルを作成します。
script.js:
// このスクリプトは HTML ドキュメントによってロードされた時に実行されます。
console.log("外部スクリプトからの挨拶!");
// 要素の修正を試みます
const headerElement = document.getElementById('mainHeader');
if (headerElement) {
headerElement.textContent = 'JavaScript ページへようこそ!';
}index.html:
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>外部スクリプトの例</title>
</head>
<body>
<h1 id="mainHeader">ロード中...</h1>
<p>これは何らかのコンテンツです。</p>
<script src="script.js"></script>
</body>
</html>index.html がロードされる際、ブラウザはまずHTMLをパースし、その後 script.js を取得して実行します。script.js は <body> の最後に配置されているため、スクリプトが実行される時には id="mainHeader" を持つ h1 要素が既にDOM内に存在しており、JavaScriptによる内容の更新が成功します。
2. defer と async 属性によるスクリプト読み込みの制御
現代のWeb開発では多くのJavaScriptファイルが関わることが多く、それがページレンダリングの速度を低下させることがあります。デフォルトでは、ブラウザは <script> タグに遭遇すると、HTMLのパースを一時停止し、スクリプトを取得(外部ファイルの場合)して実行してから、HTMLのパースを再開します。これは、特に大きなスクリプトや遅いネットワーク環境において、ユーザーに明らかな遅延を感じさせる原因となります。
defer と async 属性は、この挙動を最適化するために役立ちます。
2.1 defer 属性
外部スクリプトの <script> タグに defer 属性を追加すると、ブラウザに対して「このスクリプトをHTMLパースと並行してダウンロードし、HTMLドキュメントのパースが完了してから実行してね」と伝えます。
defer の主な特徴:
- 非ブロッキング・ダウンロード: ブラウザはスクリプトをダウンロードしながらHTMLのパースを継続します。
- 遅延実行: スクリプトはHTMLドキュメントのパースが完全に終了し、DOMの準備が整った後に実行されます。
- 順序の保持: 複数の
deferスクリプトがある場合、それらはHTML内に記述された順序通りに実行されます。 - 最適な用途: 完全なHTMLドキュメント(インタラクティブな要素やフォームバリデーションなど)に依存し、かつ実行順序が重要なスクリプトに適しています。
構文:
<script src="path/to/your-script.js" defer></script>例: script1.js と script2.js があり、script2.js が script1.js で定義された内容に依存している場合を想定します。
script1.js:
console.log("スクリプト 1 がロードされました。");
window.myVariable = "スクリプト 1 からの挨拶!";script2.js:
console.log("スクリプト 2 がロードされました。");
if (window.myVariable) {
console.log("スクリプト 2 がアクセス成功: " + window.myVariable);
} else {
console.log("スクリプト 2: myVariable がまだ見つかりません!");
}index.html:
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>Defer の例</title>
</head>
<body>
<h1>Defer 属性のデモンストレーション</h1>
<script src="script1.js" defer></script>
<script src="script2.js" defer></script>
</body>
</html>index.html がロードされる際、script1.js と script2.js はバックグラウンドで並行してダウンロードされます。HTMLパース完了後、必ず script1.js が先に実行され、次に script2.js が実行されます。これにより、script2.js は script1.js が定義した myVariable を確実に参照できます。
2.2 async 属性
async 属性も外部スクリプトに使用されます。これはブラウザに「このスクリプトをHTMLパースと並行してダウンロードし、ダウンロードが完了したら即座に実行してね。HTMLパースの完了や他のスクリプトを待つ必要はないよ」と伝えます。
async の主な特徴:
- 非ブロッキング・ダウンロード: ブラウザはスクリプトをダウンロードしながらHTMLのパースを継続します。
- 独立した実行: スクリプトは準備ができ次第すぐに実行されるため、HTMLパースの途中で実行されることもあります。
- 順序の保証なし: 複数の
asyncスクリプトがある場合、ダウンロードが早く終わったものから順に実行されます。script1.jsより先にscript2.jsが実行されることもあり得ます。 - 最適な用途: 他のスクリプトの順序やDOMの完全性に依存しない独立したスクリプト(アクセス解析、広告など)に適しています。
構文:
<script src="path/to/your-script.js" async></script>2.3 type="module" (概要)
JavaScriptモジュールの深い解説はこの入門コースの範囲外ですが、type="module" 属性についても簡単に触れておきます。この属性はモジュールのインポート/エクスポートに使用されます。モジュールは特定の機能をカプセル化し、制御された方法でデータを共有できる独立したファイルです。type="module" を使用すると、スクリプトはデフォルトで遅延実行(defer と同様の挙動)され、常に厳格モード (Strict Mode) で実行されます。
構文:
<script src="path/to/module.js" type="module"></script>3. 実践的な例とデモンストレーション
これまでの内容を総合的な例でまとめてみましょう。
ファイル構成:
my-project/
├── index.html
└── js/
├── head-script.js
├── body-script.js
├── deferred-script.js
└── async-script.jsjs/head-script.js:
// <head> 内でリンク、defer/async なし
console.log("1. head-script.js: HTML パース中に即座に実行されます。");js/body-script.js:
// <body> の最後にリンク、defer/async なし
console.log("3. body-script.js: HTML がここまでパースされた後に実行されます。");
document.getElementById('greeting').textContent = 'Body スクリプトによって修正されました!';js/deferred-script.js:
// 'defer' を使用してリンク
console.log("4. deferred-script.js: 並行ダウンロードされ、HTML パース完了後に実行されます。");
document.getElementById('info').innerHTML += '<p>Deferred スクリプトがこの段落を追加しました。</p>';js/async-script.js:
// 'async' を使用してリンク
console.log("2. async-script.js: 並行ダウンロードされ、準備ができ次第実行されます(順序保証なし)。");
document.getElementById('async-message').textContent = 'Async スクリプトがここを更新しました!';index.html:
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>Script タグの深掘り</title>
<script src="js/head-script.js"></script>
<script src="js/async-script.js" async></script>
</head>
<body>
<h1 id="greeting">こんにちは, JavaScript!</h1>
<p id="info">初期コンテンツです。</p>
<p id="async-message">オリジナルの async メッセージ。</p>
<script>
console.log("Body: インラインスクリプトが実行されました。");
document.getElementById('info').innerHTML += '<p>インラインスクリプトがこの段落を追加しました。</p>';
</script>
<script src="js/deferred-script.js" defer></script>
<script src="js/body-script.js"></script>
</body>
</html>ブラウザで index.html を開き、デベロッパーコンソールを確認すると、ログの順序が観察できます。ネットワーク速度によりますが、一般的には「1. head-script.js」が最初に現れ、次にインラインスクリプト、その後に「4. deferred-script.js」や「3. body-script.js」が続きます。「2. async-script.js」のタイミングはダウンロード完了次第で変動します。ページの各要素(h1, p)もスクリプトによる変更が反映されているはずです。