JavaScript 入門

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のパースを再開します。これは、特に大きなスクリプトや遅いネットワーク環境において、ユーザーに明らかな遅延を感じさせる原因となります。

deferasync 属性は、この挙動を最適化するために役立ちます。

2.1 defer 属性

外部スクリプトの <script> タグに defer 属性を追加すると、ブラウザに対して「このスクリプトをHTMLパースと並行してダウンロードし、HTMLドキュメントのパースが完了してから実行してね」と伝えます。

defer の主な特徴:

  • 非ブロッキング・ダウンロード: ブラウザはスクリプトをダウンロードしながらHTMLのパースを継続します。
  • 遅延実行: スクリプトはHTMLドキュメントのパースが完全に終了し、DOMの準備が整った後に実行されます。
  • 順序の保持: 複数の defer スクリプトがある場合、それらはHTML内に記述された順序通りに実行されます。
  • 最適な用途: 完全なHTMLドキュメント(インタラクティブな要素やフォームバリデーションなど)に依存し、かつ実行順序が重要なスクリプトに適しています。

構文:

<script src="path/to/your-script.js" defer></script>

例: script1.jsscript2.js があり、script2.jsscript1.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.jsscript2.js はバックグラウンドで並行してダウンロードされます。HTMLパース完了後、必ず script1.js が先に実行され、次に script2.js が実行されます。これにより、script2.jsscript1.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.js

js/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)もスクリプトによる変更が反映されているはずです。