JavaScript: JavaScript 错误与调试
写代码不出 bug 是不可能的——连 JavaScript 的创造者都在 10 天里写出了 typeof null === "object" 这种 bug。所以,学会找 bug 和处理错误比学会写代码更重要。
1. 错误处理与调试技巧
▶ 示例
JAVASCRIPT
// 本节代码示例
const message = "你好,世界!";
console.log(message);
这是最简单的示例,后续章节会详细解释。### (1) 常见错误类型
| 错误类型 | 原因 | 示例 |
|---|---|---|
SyntaxError |
语法写错了 | if (true { 括号不匹配 |
ReferenceError |
访问不存在的变量 | console.log(notExist) |
TypeError |
对错误类型执行操作 | undefined.toString() |
RangeError |
值超出有效范围 | new Array(-1) |
SyntaxError 在代码执行前就会被发现(编译阶段),其余三种是运行时错误。
(2) 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 对象有两个常用属性:
error.message:错误描述error.name:错误类型名
(3) 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() 或其子类,这样有堆栈信息。
(4) 自定义错误消息
用 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>
(5) 浏览器开发者工具(F12)
| 面板 | 用途 |
|---|---|
| Console | 查看日志、执行代码、查看错误 |
| Sources | 设置断点、逐步执行、查看变量值 |
| Network | 查看网络请求、响应状态、加载时间 |
| Elements | 查看/修改 DOM 和样式 |
断点调试是最强大的调试手段:在 Sources 面板点击行号设置断点,刷新页面后代码暂停在断点处,逐行执行、查看变量、调用栈一目了然。
(6) console 家族
| 方法 | 用途 |
|---|---|
console.log() |
普通输出 |
console.warn() |
警告(黄色) |
console.error() |
错误(红色) |
console.table() |
以表格展示数组/对象 |
console.time() / console.timeEnd() |
计时 |
console.group() / console.groupEnd() |
分组输出 |
console.clear() |
清空控制台 |
console.table 特别适合展示对象数组,比 log 直观太多。
(7) 调试技巧
- 逐条注释:注释掉一半代码,看哪半有问题,逐步缩小范围
- console.log 大法:在关键位置打印变量值,原始但有效
- 断点调试:比
console.log更强大,能暂停执行、实时修改变量 - 橡皮鸭调试法:对着橡皮鸭(或同事)逐行解释代码,经常解释到一半自己就发现 bug 了
▶ 示例:try/catch 处理 JSON 解析
HTML
📖 仅展示
<!DOCTYPE html>
<html lang="zh">
<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": "张三",
"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: "张三", age: 20}';
});
</script>
</body>
</html>
▶ 示例:自定义错误与表单验证
HTML
📖 仅展示
<!DOCTYPE html>
<html lang="zh">
<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="zh">
<head>
<meta charset="UTF-8">
<title>错误类型与console</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);
// Visual output is shown via addLine above
}
function simTable() {
var data = [
{ name: "张三", age: 20, score: 90 },
{ name: "李四", age: 22, score: 85 },
{ name: "王五", age: 21, score: 95 }
];
addLine("console.table:", "log-info");
data.forEach(function(d) {
addLine(" " + d.name + " | " + d.age + " | " + d.score, "log-info");
});
// Visual output shown above
}
function simTime() {
var sum = 0;
for (var i = 0; i < 1000000; i++) { sum += i; }
addLine("⏱ console.time/timeEnd: 100万次累加完成", "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")),就可以被 catch。Q finally 什么时候用?
A 当你需要无论是否出错都执行清理操作时:关闭文件、释放资源、隐藏 loading 状态。大多数简单场景不需要 finally,catch 够用。
Q throw 和 return 有什么区别?
A
return 是正常流程的返回,调用者通过返回值处理。throw 是异常流程的中断,会跳过后续代码,沿着调用栈向上寻找 catch。错误情况用 throw,正常结果用 return。📖 小节
try/catch捕获运行时错误,finally无论是否出错都会执行,适合清理资源throw主动抛出错误,可以抛字符串、数字、Error 对象,推荐抛new Error("消息")Error对象有name和message属性,浏览器控制台会显示完整错误堆栈- 浏览器开发者工具(F12):Sources 面板设断点、Watch 变量、单步执行
console家族:log/warn/error/table/group/time/timeEnd,调试效率倍增器
📝 作业
- 写一个函数
safeDivide(a, b),用 try...catch 处理除零错误(提示:JS 中除以零不报错,需要自己判断并 throw),返回结果或错误信息。 - 写一个
safeJSONParse(str)函数,解析 JSON 字符串,如果失败返回{ success: false, error: 错误消息 },成功返回{ success: true, data: 解析结果 }。 - 写一个
retry(fn, times)函数:执行fn,如果抛错就重试,最多重试times次。如果全部失败,抛出最后一次的错误。用 try...catch 实现。