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 对象有两个常用属性:

(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) 调试技巧


▶ 示例: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>
逻辑代码 64 行(超过 40 行限制,仅展示)

▶ 示例:自定义错误与表单验证

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>
逻辑代码 107 行(超过 40 行限制,仅展示)

▶ 示例:错误类型速查与 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>
逻辑代码 99 行(超过 40 行限制,仅展示)

❓ 常见问题

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。

📖 小节

📝 作业

  1. 写一个函数 safeDivide(a, b),用 try...catch 处理除零错误(提示:JS 中除以零不报错,需要自己判断并 throw),返回结果或错误信息。
  2. 写一个 safeJSONParse(str) 函数,解析 JSON 字符串,如果失败返回 { success: false, error: 错误消息 },成功返回 { success: true, data: 解析结果 }
  3. 写一个 retry(fn, times) 函数:执行 fn,如果抛错就重试,最多重试 times 次。如果全部失败,抛出最后一次的错误。用 try...catch 实现。
Web-Tutorial.com

Web-Tutorial 技术团队

由多位开发者共同维护的编程教程平台。每篇教程由对应领域的开发者编写和审核,确保内容准确可靠。如发现任何问题,欢迎向我们反馈。

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏