JavaScript エラーとデバッグ
バグのないコードを書くことは不可能です。JavaScript の作者でさえ10日間で typeof null === "object" のバグを作ってしまいました。そのため、バグの発見とエラー処理はコードの書き方を学ぶことよりも重要です。
📖 まとめ
一般的なエラータイプ
| エラータイプ | 原因 | 例 |
|---|---|---|
SyntaxError |
無効な構文 | if (true { — 括弧の不一致 |
ReferenceError |
存在しない変数へのアクセス | console.log(notExist) |
TypeError |
間違った型への操作 | undefined.toString() |
RangeError |
有効範囲外の値 | new Array(-1) |
SyntaxError はコード実行前に(解析段階で)検出されます。他の3つはランタイムエラーです。
try...catch...finally
try...catch を使ってランタイムエラーをキャッチし、プログラムのクラッシュを防ぎましょう。
HTML
<script>
try {
const data = JSON.parse('{bad json}');
} catch (error) {
console.log("解析失敗:", error.message);
} finally {
console.log("クリーンアップ処理");
}
</script>
catch の error オブジェクトには2つのよく使われるプロパティがあります。
error.message— エラーの説明error.name— エラータイプ名
throw — エラーを手動でスローする
try...catch はランタイムエラーしかキャッチできないため、ビジネスロジックのエラーは手動でスローする必要があります。
HTML
<script>
function setAge(age) {
if (typeof age !== "number" || age < 0) {
throw new Error("年齢は正の数でなければなりません");
}
console.log("年齢を設定しました: " + age);
}
try {
setAge(-5);
} catch (e) {
console.log(e.message);
}
</script>
throw は任意の値をスローできますが、new Error() またはそのサブクラスを使用することを推奨します。スタックトレース情報が含まれるためです。
カスタムエラーメッセージ
Error のサブクラスやカスタムプロパティを使って、より豊かなエラー情報を提供しましょう。
HTML
<script>
class ValidationError extends Error {
constructor(field, message) {
super(message);
this.name = "ValidationError";
this.field = field;
}
}
try {
throw new ValidationError("email", "無効なメール形式");
} catch (e) {
console.log(e.name + " [" + e.field + "]: " + e.message);
}
</script>
ブラウザ DevTools(F12)
| パネル | 用途 |
|---|---|
| Console | ログの表示、コードの実行、エラーの確認 |
| Sources | ブレークポイントの設定、コードのステップ実行、変数の確認 |
| Network | ネットワークリクエスト、レスポンスステータス、読み込み時間の確認 |
| Elements | DOM とスタイルの表示・修正 |
ブレークポイントデバッグは最も強力なデバッグ手法です。Sources パネルで行番号をクリックしてブレークポイントを設定し、ページを更新するとコードがそのポイントで一時停止します。行ごとにステップ実行し、変数を確認し、コールスタックを調べましょう。
console ファミリー
| メソッド | 用途 |
|---|---|
console.log() |
一般的な出力 |
console.warn() |
警告(黄色) |
console.error() |
エラー(赤色) |
console.table() |
配列/オブジェクトをテーブルで表示 |
console.time() / console.timeEnd() |
タイミング計測 |
console.group() / console.groupEnd() |
グループ化された出力 |
console.clear() |
コンソールのクリア |
console.table はオブジェクトの配列を表示する際に特に便利です。log より遥かに読みやすいです。
デバッグのヒント
- 半分をコメントアウト:コードの半分をコメントアウトし、どちらの半分に問題があるかを確認して段階的に絞り込む
- console.log でまく:重要なポイントで変数の値を出力する。原始的だが効果的
- ブレークポイントデバッグ:
console.logより強力。実行を一時停止してリアルタイムで変数を変更できる - ゴムアヒルデバッグ:ゴムアヒル(または同僚)にコードを1行ずつ説明する。説明の途中でバグが見つかることも多い
例:JSON 解析の try/catch
HTML
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>JSON 解析</title>
<style>
body { font-family: sans-serif; padding: 20px; }
.demo { max-width: 600px; margin: 16px auto; }
textarea { width: 100%; height: 120px; padding: 12px; font-size: 14px; font-family: monospace; border: 2px solid #ccc; border-radius: 6px; resize: vertical; box-sizing: border-box; }
button { padding: 10px 24px; font-size: 16px; border: none; border-radius: 6px; cursor: pointer; margin: 8px 4px; }
.parse { background: #4a90d9; color: #fff; }
.parse:hover { background: #357abd; }
.bad { background: #d9534f; color: #fff; }
.bad:hover { background: #c9302c; }
.result { margin-top: 16px; padding: 16px; border-radius: 8px; }
.success { background: #d4edda; border: 2px solid #5cb85c; }
.error-box { background: #f8d7da; border: 2px solid #d9534f; }
.error-type { font-weight: bold; color: #721c24; }
.error-msg { margin-top: 4px; color: #721c24; }
</style>
</head>
<body>
<h2 style="text-align:center;">JSON 解析エラー処理</h2>
<div class="demo">
<textarea id="jsonInput">{
"name": "Alice",
"age": 20,
"skills": ["JavaScript", "HTML", "CSS"]
}</textarea>
<div style="text-align:center;">
<button class="parse" id="parseBtn">JSON を解析</button>
<button class="bad" id="badBtn">無効な JSON を挿入</button>
</div>
<div id="result"></div>
</div>
<script>
function parseJSON(jsonStr) {
try {
const data = JSON.parse(jsonStr);
return { success: true, data: data };
} catch (error) {
return { success: false, name: error.name, message: error.message };
}
}
document.getElementById("parseBtn").addEventListener("click", function() {
var input = document.getElementById("jsonInput").value;
var result = parseJSON(input);
var output = document.getElementById("result");
if (result.success) {
output.className = "result success";
output.innerHTML = "<strong>解析成功!</strong><pre>" +
JSON.stringify(result.data, null, 2) + "</pre>";
} else {
output.className = "result error-box";
output.innerHTML = '<div class="error-type">' + result.name +
'</div><div class="error-msg">' + result.message + '</div>';
}
});
document.getElementById("badBtn").addEventListener("click", function() {
document.getElementById("jsonInput").value = '{name: "Alice", age: 20}';
});
</script>
</body>
</html>
例:カスタムエラーとフォームバリデーション
HTML
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>カスタムエラー</title>
<style>
body { font-family: sans-serif; padding: 20px; }
.form { max-width: 440px; margin: 16px auto; padding: 24px; background: #f9f9f9; border-radius: 12px; }
.row { margin: 12px 0; }
label { display: block; margin-bottom: 4px; font-weight: bold; }
input { width: 100%; padding: 10px 12px; font-size: 16px; border: 2px solid #ccc; border-radius: 6px; box-sizing: border-box; }
input.valid { border-color: #5cb85c; }
input.invalid { border-color: #d9534f; }
.field-error { color: #d9534f; font-size: 13px; margin-top: 2px; min-height: 20px; }
button { padding: 10px 24px; font-size: 16px; border: none; border-radius: 6px; cursor: pointer; background: #4a90d9; color: #fff; width: 100%; margin-top: 8px; }
button:hover { background: #357abd; }
.global-msg { margin-top: 12px; padding: 12px; border-radius: 6px; text-align: center; }
.global-ok { background: #d4edda; color: #155724; }
.global-fail { background: #f8d7da; color: #721c24; }
</style>
</head>
<body>
<h2 style="text-align:center;">カスタムエラー検証</h2>
<div class="form">
<div class="row">
<label>ユーザー名(3〜20文字):</label>
<input type="text" id="username" placeholder="ユーザー名を入力" />
<div class="field-error" id="usernameErr"></div>
</div>
<div class="row">
<label>年齢(0〜150):</label>
<input type="text" id="age" placeholder="年齢を入力" />
<div class="field-error" id="ageErr"></div>
</div>
<div class="row">
<label>メールアドレス:</label>
<input type="text" id="email" placeholder="メールアドレスを入力" />
<div class="field-error" id="emailErr"></div>
</div>
<button id="submitBtn">送信</button>
<div class="global-msg" id="globalMsg"></div>
</div>
<script>
class ValidationError extends Error {
constructor(field, message) {
super(message);
this.name = "ValidationError";
this.field = field;
}
}
function validateUsername(value) {
if (!value.trim()) throw new ValidationError("username", "ユーザー名は空欄にできません");
if (value.trim().length < 3) throw new ValidationError("username", "ユーザー名は3文字以上でなければなりません");
if (value.trim().length > 20) throw new ValidationError("username", "ユーザー名は20文字以下でなければなりません");
}
function validateAge(value) {
if (!value.trim()) throw new ValidationError("age", "年齢は空欄にできません");
var num = Number(value);
if (isNaN(num)) throw new ValidationError("age", "有効な数値を入力してください");
if (num < 0 || num > 150) throw new ValidationError("age", "年齢の範囲:0〜150");
if (!Number.isInteger(num)) throw new ValidationError("age", "年齢は整数でなければなりません");
}
function validateEmail(value) {
if (!value.trim()) throw new ValidationError("email", "メールアドレスは空欄にできません");
if (!value.includes("@")) throw new ValidationError("email", "メールアドレスには @ が必要です");
if (!value.includes(".")) throw new ValidationError("email", "無効なメール形式です");
}
var validators = {
username: validateUsername,
age: validateAge,
email: validateEmail
};
function clearErrors() {
["username", "age", "email"].forEach(function(field) {
document.getElementById(field).className = "";
document.getElementById(field + "Err").textContent = "";
});
document.getElementById("globalMsg").innerHTML = "";
document.getElementById("globalMsg").className = "global-msg";
}
document.getElementById("submitBtn").addEventListener("click", function() {
clearErrors();
var hasError = false;
["username", "age", "email"].forEach(function(field) {
var value = document.getElementById(field).value;
try {
validators[field](value);
document.getElementById(field).className = "valid";
} catch (e) {
hasError = true;
document.getElementById(field).className = "invalid";
document.getElementById(field + "Err").textContent =
e.name + ": " + e.message;
}
});
var msg = document.getElementById("globalMsg");
if (hasError) {
msg.className = "global-msg global-fail";
msg.textContent = "フォームにエラーがあります。修正してもう一度お試しください";
} else {
msg.className = "global-msg global-ok";
msg.textContent = "すべてのフィールドが検証に合格しました!";
}
});
</script>
</body>
</html>
例:エラータイプクイックリファレンスと console メソッド
HTML
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>エラータイプとコンソール</title>
<style>
body { font-family: sans-serif; padding: 20px; }
.section { margin: 20px 0; }
table { border-collapse: collapse; width: 100%; max-width: 700px; }
td, th { border: 1px solid #ddd; padding: 10px 14px; text-align: left; }
th { background: #4a90d9; color: #fff; }
.trigger { background: #f8d7da; }
.safe { background: #d4edda; }
button { padding: 6px 16px; border: none; border-radius: 4px; cursor: pointer; margin: 2px; font-size: 14px; }
.btn-err { background: #d9534f; color: #fff; }
.btn-safe { background: #5cb85c; color: #fff; }
.btn-info { background: #5bc0de; color: #fff; }
.output { margin-top: 12px; padding: 12px; background: #1a1a2e; color: #e0e0e0; border-radius: 6px; font-family: monospace; font-size: 13px; min-height: 60px; max-height: 200px; overflow-y: auto; }
.log-line { margin: 2px 0; }
.log-warn { color: #f0ad4e; }
.log-error { color: #d9534f; }
.log-info { color: #5bc0de; }
</style>
</head>
<body>
<h2>エラータイプと console メソッドのデモ</h2>
<div class="section">
<h3>一般的なエラータイプ</h3>
<table>
<tr><th>エラータイプ</th><th>トリガーコード</th><th>try/catch 結果</th></tr>
<tr class="trigger"><td>SyntaxError</td><td><code>JSON.parse("{bad")</code></td><td id="r1"></td></tr>
<tr class="trigger"><td>ReferenceError</td><td><code>notExistVar</code></td><td id="r2"></td></tr>
<tr class="trigger"><td>TypeError</td><td><code>undefined.toString()</code></td><td id="r3"></td></tr>
<tr class="trigger"><td>RangeError</td><td><code>new Array(-1)</code></td><td id="r4"></td></tr>
</table>
</div>
<div class="section">
<h3>console メソッド(シミュレート出力)</h3>
<button class="btn-info" onclick="simLog('log', '通常のログ')">console.log</button>
<button class="btn-info" onclick="simLog('warn', '警告メッセージ')">console.warn</button>
<button class="btn-err" onclick="simLog('error', 'エラーメッセージ')">console.error</button>
<button class="btn-info" onclick="simLog('info', '情報メッセージ')">console.info</button>
<button class="btn-safe" onclick="simTable()">console.table</button>
<button class="btn-safe" onclick="simTime()">console.time</button>
<button class="btn-info" onclick="clearSim()">クリア</button>
<div class="output" id="simConsole"></div>
</div>
<script>
var tests = [
function() { JSON.parse("{bad"); },
function() { notExistVar; },
function() { var x; x.toString(); },
function() { new Array(-1); }
];
var ids = ["r1", "r2", "r3", "r4"];
var names = ["SyntaxError", "ReferenceError", "TypeError", "RangeError"];
tests.forEach(function(fn, i) {
try {
fn();
document.getElementById(ids[i]).textContent = "トリガーされず(予期しない)";
} catch (e) {
document.getElementById(ids[i]).innerHTML =
'<span class="safe">' + e.name + ": " + e.message + '</span>';
}
});
function simLog(type, msg) {
var cls = type === "warn" ? "log-warn" :
type === "error" ? "log-error" : "log-info";
var prefix = type === "warn" ? "⚠" :
type === "error" ? "✖" : "ℹ";
addLine(prefix + " " + msg, cls);
if (type !== "error") {
console[type](msg);
}
}
function simTable() {
var data = [
{ name: "Alice", age: 20, score: 90 },
{ name: "Bob", age: 22, score: 85 },
{ name: "Charlie", age: 21, score: 95 }
];
addLine("console.table:", "log-info");
data.forEach(function(d) {
addLine(" " + d.name + " | " + d.age + " | " + d.score, "log-info");
});
console.table(data);
}
function simTime() {
console.time("demo");
var sum = 0;
for (var i = 0; i < 1000000; i++) { sum += i; }
console.timeEnd("demo");
addLine("⏱ console.time/timeEnd: 1,000,000 回の加算完了", "log-info");
}
function addLine(text, cls) {
var div = document.getElementById("simConsole");
div.innerHTML += '<div class="log-line ' + (cls || "") + '">' + text + '</div>';
div.scrollTop = div.scrollHeight;
}
function clearSim() {
document.getElementById("simConsole").innerHTML = "";
}
</script>
</body>
</html>
❓ よくある質問
Q try...catch は SyntaxError をキャッチできますか?
A いいえ。
SyntaxError は実行が始まる前の解析段階でスローされるため、try ブロックに到達しません。ただし、ランタイムで SyntaxError が発生した場合(例:JSON.parse の失敗、new Function("bad syntax"))はキャッチできます。Q finally をいつ使うべきですか?
A エラーの有無に関わらずクリーンアップ操作を実行する必要がある場合です。ファイルを閉じる、リソースを解放する、ローディング状態を非表示にするなど。多くのシンプルなシナリオでは、
catch だけで十分です。Q throw と return の違いは何ですか?
A
return は通常のフロー用です。呼び出し元が戻り値を処理します。throw は異常なフロー用です。後続のコードをスキップし、コールスタックを遡って catch を探します。エラーには throw を、正常な結果には return を使いましょう。📝 演習
safeDivide(a, b)関数を書きましょう。try...catch を使ってゼロ除算を処理してください(ヒント:JS でゼロ除算してもスローされないため、自分でチェックしてスローする必要があります)。結果またはエラーメッセージを返してください。safeJSONParse(str)関数を書きましょう。JSON 文字列を解析してください。失敗時は{ success: false, error: <エラーメッセージ> }を、成功時は{ success: true, data: <解析結果> }を返してください。retry(fn, times)関数を書きましょう。fnを実行し、スローされた場合は最大times回再試行してください。すべての試行が失敗した場合は最後のエラーをスローしてください。try...catch で実装してください。



