错误处理与调试

张玥 2026年2月14日 阅读时间 30分钟
JavaScript 错误处理 调试 try...catch
JavaScript 错误处理与调试示意图

编写 JavaScript 应用时,错误处理与调试是保障代码质量与用户体验的关键。本文将深入讲解 JavaScript 中的错误类型、捕获机制、异步错误处理、全局错误监听,以及 Chrome DevTools 调试技巧。通过 20+ 个实战代码片段和详细解释,帮助你系统掌握错误处理与调试的核心技能,写出更健壮的代码。

为什么需要错误处理

任何应用都无法保证 100% 无错误。网络波动、用户输入异常、后端返回格式不符等都可能导致程序崩溃。良好的错误处理可以:

  • 防止脚本中断执行,提升用户体验
  • 提供有意义的错误反馈
  • 记录错误信息以便快速修复
  • 保证程序状态的一致性

错误类型大全

JS 内置了多种错误构造函数,每种对应特定的异常情况。

错误类型说明常见触发场景
SyntaxError语法错误缺少括号、变量名非法等,代码无法解析
ReferenceError引用不存在的变量console.log(x) 但 x 未定义
TypeError值不是期望的类型null.fn() 、 const a=1; a()
RangeError数值超出有效范围数组长度负数、递归过深
URIErrorURI 处理函数传参错误decodeURI('%')
EvalErroreval() 执行异常(很少用)已弃用,保留兼容性
// ReferenceError 示例
try {
  console.log(unknownVariable);
} catch (err) {
  console.log(err.name); // "ReferenceError"
  console.log(err.message); // "unknownVariable is not defined"
}

// TypeError 示例
const obj = null;
obj.toString(); // Uncaught TypeError: Cannot read property 'toString' of null

📌 错误对象属性

所有错误实例都包含三个标准属性:

  • name:错误类型名称
  • message:错误描述
  • stack:调用堆栈(非标准但广泛支持)
console.log(err.stack);
// ReferenceError: unknownVariable is not defined
//    at <anonymous>:3:15
//    ...

try...catch 捕获同步错误

// 基本语法
try {
  // 可能抛出错误的代码
  let result = riskyOperation();
  console.log('成功:', result);
} catch (error) {
  // 处理错误
  console.error('出错了:', error.message);
} finally {
  // 无论是否出错都会执行
  console.log('清理工作');
}

// 只捕获特定类型错误
try {
  JSON.parse('invalid json');
} catch (err) {
  if (err instanceof SyntaxError) {
    console.log('JSON 语法错误');
  } else {
    throw err; // 重新抛出其他错误
  }
}

🧪 finally 典型应用

释放资源、关闭连接、隐藏加载提示等操作放在 finally 中保证执行。

let loading = true;
try {
  // 网络请求
} catch (e) {
  // 显示错误
} finally {
  loading = false; // 无论如何都关闭loading
}

主动抛出错误 throw

使用 throw 可以抛出自定义错误,中断程序并进入最近的 catch 块。

// 抛出字符串(不推荐)
throw '发生错误'; // 但无法获取堆栈信息

// 推荐抛出 Error 对象
function divide(a, b) {
  if (b === 0) {
    throw new Error('除数不能为0');
  }
  return a / b;
}

try {
  divide(10, 0);
} catch (err) {
  console.log(err.message); // "除数不能为0"
}

📦 自定义错误类

class ValidationError extends Error {
  constructor(message) {
    super(message);
    this.name = "ValidationError";
  }
}

throw new ValidationError("邮箱格式错误");

异步错误处理

try...catch 无法捕获 setTimeout、Promise 中抛出的错误(除非使用 async/await)。

回调时代的错误处理

// Node.js 风格:错误优先回调
fs.readFile('file.txt', (err, data) => {
  if (err) {
    console.error('读取失败', err);
    return;
  }
  console.log(data);
});

Promise 错误处理

// 使用 .catch()
fetch('/api/data')
  .then(response => response.json())
  .then(data => console.log(data))
  .catch(err => console.error('请求失败:', err));

// 也可以捕获 then 中的异常
Promise.resolve().then(() => {
  throw new Error('then 内错误');
}).catch(e => console.log(e.message));

⚡ async/await 搭配 try/catch

async function getData() {
  try {
    const res = await fetch('/data');
    const json = await res.json();
    return json;
  } catch (err) {
    console.error('捕获异步错误:', err);
    // 可以返回默认值或重新抛出
  }
}

未捕获的 Promise 拒绝

没有 catch 的 Promise 拒绝会触发 unhandledrejection 事件。

window.addEventListener('unhandledrejection', event => {
  console.error('未处理的 Promise 拒绝:', event.reason);
  event.preventDefault(); // 阻止默认控制台警告
});

全局错误捕获

使用 window.onerroraddEventListener('error') 捕获运行时错误(含语法错误?不,语法错误无法被捕获)。

// 传统方式
window.onerror = function(msg, url, line, col, error) {
  console.log('全局错误:', msg, 'at', line);
  return true; // 阻止默认错误处理
};

// 现代方式(推荐)
window.addEventListener('error', (event) => {
  console.log('捕获错误:', event.error);
  // 可以上报到监控系统
});

📡 资源加载错误

图片、脚本加载失败也会触发 error 事件,可以通过捕获处理:

window.addEventListener('error', (e) => {
  if (e.target.tagName === 'IMG') {
    console.log('图片加载失败:', e.target.src);
  }
}, true); // 捕获阶段

调试利器:console 对象

除了常用的 console.log,还有多种实用方法。

// 不同级别
console.info('提示信息');
console.warn('警告信息');
console.error('错误信息');

// 表格展示
console.table([{name:'Tom', age:20}, {name:'Jerry', age:18}]);

// 分组
console.group('请求详情');
console.log('url: /api');
console.log('status: 200');
console.groupEnd();

// 计时
console.time('循环耗时');
for(let i=0;i<1000000;i++){}
console.timeEnd('循环耗时');

🧹 清理与断言

// 条件断言
const value = 5;
console.assert(value > 10, '值应该大于10'); // 不输出

// 堆栈追踪
function foo() { bar(); }
function bar() { console.trace(); }
foo(); // 打印调用栈

debugger 与浏览器断点

在代码中插入 debugger; 语句,当打开 DevTools 时,执行会自动暂停在该行。

function calculateTotal(price, count) {
  debugger; // 执行到此处会暂停(如果 DevTools 开启)
  return price * count;
}
calculateTotal(100, 3);

此外,可以在 Sources 面板手动添加断点、条件断点、DOM 断点等,是定位复杂问题的利器。

Source Map 调试生产代码

生产环境的代码通常经过压缩和混淆,错误堆栈难以阅读。Source Map 可以将压缩代码映射回原始源码。

// webpack 配置生成 .map 文件
// devtool: 'source-map'

// 错误监控平台利用 source map 还原原始位置
// 例如 Sentry 支持上传 source map

在 Chrome DevTools 中,只要 .map 文件可访问,会自动还原源码,便于调试线上问题。

典型错误案例解析

Uncaught TypeError: Cannot read property 'xxx' of undefined

// 错误代码
const user = { profile: { name: '张三' } };
console.log(user.profile.age.toFixed()); // profile.age 是 undefined

// 安全访问方式
console.log(user.profile?.age?.toFixed?.()); // 可选链

异步回调中的错误被吞没

// 糟糕的写法
try {
  setTimeout(() => {
    throw new Error('异步错误');
  }, 100);
} catch (e) { // 无法捕获
  console.log('不会执行');
}

// 正确:在回调内部捕获
setTimeout(() => {
  try { throw new Error('异步错误'); } catch(e) {}
}, 100);

错误处理最佳实践

  • 区分操作错误和程序员错误:操作错误(如网络超时)应优雅处理;程序员错误(如传错参数)应尽快失败并修复。
  • 始终捕获异步错误:使用 async/await + try/catch 或 Promise.catch()。
  • 不要吞掉错误:空的 catch 块会使调试困难,至少记录日志。
  • 抛出有意义的错误消息:便于快速定位。
  • 集中错误监控:使用 Sentry、Fundebug 等工具收集生产环境错误。
  • 为自定义错误设置正确的 name 和 stack:继承 Error 而不是单纯抛出对象。
// 推荐的自定义错误模式
class HttpError extends Error {
  constructor(message, status) {
    super(message);
    this.name = 'HttpError';
    this.status = status;
  }
}

错误处理与调试是 JavaScript 工程化的重要基石。掌握本文内容后,你将能更从容地应对运行时异常,并利用现代工具链快速定位和修复问题。