1.

JavaScript の書き方|script タグの置き場所・defer・モジュール

編集
この記事の要点
  • 読み込みは <script src="..." defer> が基本。defer を付ければ <head> に置ける
  • type="module" にすると import が使え、自動的に strict モード+遅延実行になる
  • セミコロンは自動挿入(ASI)されるが、行頭が ([ のときに壊れる
  • 変数は const が既定。再代入するときだけ letvar は使わない
  • 整形は Prettier、検査は ESLint。スタイルは人間が議論せずツールに任せる

HTML への書き方

<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="utf-8">
  <script src="/js/app.js" defer></script>
</head>
<body>
  <button id="btn">押す</button>

  <script>
    document.getElementById("btn").addEventListener("click", () => {
      console.log("押された");
    });
  </script>
</body>
</html>
書き方読み込み実行タイミング順序
<script src>その場で止まるすぐ書いた順
defer並行HTML の解析後書いた順
async並行読み込めた瞬間不定
type="module"並行HTML の解析後(defer と同じ)書いた順

迷ったら defer です。async は他のスクリプトに依存しない計測タグなどに限ります。</body> の直前に置く方法も動きますが、defer なら <head> に置けて読み込みが早く始まります。

要素が見つからないときの原因

<head>
  <script>
    // defer が無いのでこの時点ではまだ button が存在しない
    document.getElementById("btn").addEventListener("click", f);
    // Uncaught TypeError: Cannot read properties of null (reading 'addEventListener')
  </script>
</head>
// 対処 1: defer を付ける(推奨)
// 対処 2: DOM の構築完了を待つ
document.addEventListener("DOMContentLoaded", () => {
  document.getElementById("btn").addEventListener("click", f);
});

詳細は Cannot read properties of null (reading 'addEventListener') を参照してください。

モジュールとして書く

// utils.js
export function taxIncluded(price, rate = 0.1) {
  return Math.floor(price * (1 + rate));
}

export const TAX_RATE = 0.1;

export default function main() { /* ... */ }
// app.js
import main, { taxIncluded, TAX_RATE } from "./utils.js";

console.log(taxIncluded(1000));   // 1100
<script type="module" src="/js/app.js"></script>

モジュールには次の性質があります。

  • 自動的に strict モードになる("use strict" を書かなくてよい)
  • トップレベルの変数が window に漏れない
  • file:// で開くと CORS に阻まれる。ローカルでも HTTP サーバーが要る
  • 相対パスの拡張子(.js)を省略できない

セミコロンと自動挿入

// 行頭が ( や [ だと前の行と繋がる
const a = 1
const b = a
[1, 2].forEach(n => console.log(n))
// → const b = a[1, 2].forEach(...) と解釈されてエラー

// return の後ろで改行すると undefined が返る
function bad() {
  return
    { ok: true };      // ← ここには到達しない
}
console.log(bad());    // undefined

function good() {
  return {             // 波かっこは同じ行に置く
    ok: true,
  };
}

セミコロンを付けない流儀もありますが、その場合は行頭の ( [ ` に注意が要ります。Prettier に任せれば自動でそろうので、書き方を統一して機械に任せるのが確実です。

変数と関数の書き方

const MAX_RETRY = 3;              // 定数は UPPER_SNAKE
let count = 0;                    // 再代入するものだけ let
const userName = "田中";           // 変数・関数は camelCase

class UserProfile {}              // クラスは PascalCase
const _internal = 1;              // 内部用は先頭アンダースコア(慣習)

function calcTotal(items) {}      // 関数宣言
const calcTotal = (items) => {};  // アロー関数

// 文字列はテンプレートリテラルで組み立てる
console.log(`合計 ${count} 件`);

strict モード

"use strict";     // ファイルの先頭(モジュールでは不要)

x = 1;            // ReferenceError: x is not defined
                  // strict でなければ暗黙のグローバル変数が作られてしまう

function f() {
  console.log(this);   // strict では undefined、非 strict では window
}

宣言し忘れた変数がグローバルに漏れる事故を防げます。モジュールとクラスの中は自動的に strict なので、明示的に書く場面は減っています。

整形と検査

npm install -D prettier eslint

npx prettier --write .        # 整形(インデント・クォート・セミコロン)
npx eslint .                  # 未使用変数・未定義変数などを検出
npx eslint . --fix            # 直せるものは自動修正

Prettier は見た目、ESLint は間違いやすい書き方を担当します。役割が違うので両方入れます。

console でのデバッグ

const user = { name: "田中", age: 30 };

console.log(user);
console.log({ user });          // 変数名も一緒に表示される
console.table([user, user]);    // 配列やオブジェクトの一覧を表で
console.dir(document.body);     // DOM をオブジェクトとして展開
console.time("処理"); /* ... */ console.timeEnd("処理");
console.trace();                // ここまでの呼び出し経路

console.log({ user }) と波かっこで囲むと、変数名がラベルとして付いて出力されます。複数の値を出すときに取り違えません。

文字列はテンプレートリテラルで組み立てる

const name = "田中";
const count = 3;

// バッククォートで囲むと ${} で式を埋め込める
console.log(`${name} さんの件数は ${count} 件`);
console.log(`税込 ${Math.floor(1000 * 1.1)} 円`);

// 改行をそのまま書ける
const html = `
  <ul>
    <li>${name}</li>
  </ul>
`;

// クォートの中の同じ記号だけエスケープが要る
const a = "彼は \"はい\" と答えた";
const b = '彼は "はい" と答えた';      // 囲みを変えればエスケープ不要
const c = `彼は "はい" と答えた`;

テンプレートリテラルに利用者の入力をそのまま入れて innerHTML に代入しないでください。HTML として解釈され XSS になります。文字を表示するだけなら textContent を使います。

文と式

// 式(値になるもの)
1 + 2
name === "田中"
cond ? "A" : "B"
items.map((n) => n * 2)

// 文(値にならないもの)
if (cond) { }
for (const x of xs) { }
return value;

// 式が必要な場所に文は書けない
// const x = if (cond) { 1 } else { 2 };   // SyntaxError
const x = cond ? 1 : 2;                    // 三項演算子を使う

// 波かっこはブロックにもオブジェクトにもなる
{ a: 1 }                    // 文の位置ではブロック(ラベル a)として解釈される
const o = { a: 1 };         // 代入の右辺ならオブジェクト
(() => ({ a: 1 }))();       // アローで返すときは丸かっこで囲む

エラーを握りつぶさない

// 悪い例: 何が起きたか分からなくなる
try {
  doSomething();
} catch (e) {
  // 何も書かない
}

// 良い例: 記録して、対処できないなら投げ直す
try {
  doSomething();
} catch (e) {
  console.error("処理に失敗しました", e);
  throw e;
}

// catch の引数は省略できる(値を使わないとき)
try { risky(); } catch { fallback(); }

// 非同期は await を try で囲む
try {
  const res = await fetch("/api");
  if (!res.ok) throw new Error(`HTTP ${res.status}`);
} catch (e) {
  console.error(e);
}

fetch() は 404 や 500 でも例外になりません。通信自体が失敗したときだけ reject するので、ステータスは res.ok で確認してください。

グローバルを汚さない

// 通常のスクリプト: トップレベルの const/let も他ファイルから見える
// (var と function は window にも生える)
var config = {};        // window.config になる

// モジュール: ファイルの中に閉じる
export const config = {};   // 明示的に export したものだけ他から使える

複数の <script> を読み込むページでは、同じ名前の const を 2 つ宣言すると SyntaxError: Identifier 'x' has already been declared になります。type="module" にすればファイルごとに閉じるので、この衝突が起きません。

よくあるつまずき

症状原因
スクリプトが動かない読み込み失敗。開発者ツールの Network タブで 404 を確認する
Cannot read properties of nullDOM 構築前に実行している。defer を付ける
Unexpected token '<'JS ファイルのつもりで HTML(404 ページ)を読んでいる
import が使えないtype="module" が無い/ファイルを直接開いている
変更が反映されないブラウザキャッシュ。強制リロードするか URL にバージョンを付ける
日本語が化ける<meta charset="utf-8"> と実ファイルの文字コードを UTF-8 にそろえる

関連

編集
Post Share
子ページ

子ページはありません

同階層のページ
  1. 記述方法
  2. コメント
  3. 変数の宣言
  4. 関数
  5. 演算子
  6. 条件文
  7. 配列
  8. 連想配列
  9. ループ処理
  10. 非同期処理
  11. 同期処理
  12. 確認ウィンドウを表示する方法
  13. 文字の置換
  14. base urlを取得する方法
  15. formのsubmit前にjavascriptを呼び出す方法
  16. undefinedのイコール判定
  17. Javascript のみで form を post で submit する方法

最近更新/作成されたページ