错误处理与调试
结构化错误类型、try/catch 与全局未捕获处理是健壮应用的基础。
一、错误构造函数
JavaScript 内置了多个错误构造函数,所有内置错误都继承自 Error 类。
1. Error
最通用的错误类型,用于创建自定义错误。接受可选的 message 参数。
const err = new Error("Something went wrong");
console.log(err.name); // 'Error'
console.log(err.message); // 'Something went wrong'
console.log(err.stack); // 堆栈信息
2. 特定错误类型
| 构造函数 | 触发场景 |
|---|---|
SyntaxError | 代码语法错误(如缺少括号、非法变量名) |
TypeError | 值类型与预期不符(如调用非函数、读取 null 的属性) |
RangeError | 数值超出有效范围(如数组长度负数、递归栈溢出) |
ReferenceError | 引用未声明的变量(在严格模式下更常见) |
URIError | 全局 URI 处理函数(decodeURI 等)传入了无效参数 |
EvalError | 已废弃,不再使用 |
示例
// SyntaxError(解析阶段,如缺少括号)
// function foo( {
// TypeError
const n = null;
n.toString();
// RangeError
new Array(-1);
// ReferenceError
console.log(undeclaredVar);
可以用 instanceof 判断错误类型:
try {
// 可能抛出 TypeError 的代码
} catch (err) {
if (err instanceof TypeError) {
console.error("类型错误");
} else if (err instanceof RangeError) {
console.error("范围错误");
} else {
console.error("其他错误");
}
}
二、自定义错误类(继承 Error)
通过继承 Error 可以创建自定义错误类型,便于区分业务逻辑错误和系统错误。
最佳实践
- 自定义子类必须调用
super(message)。 - 需要手动设置
name属性和维护原型链(ES6 的extends会自动修复原型,但设置name仍可取)。 - 可选地设置
code、status等自定义属性。
class ValidationError extends Error {
constructor(message, field) {
super(message);
this.name = "ValidationError";
this.field = field; // 自定义属性
// 维护堆栈(可选,ES6 后 prototype 已自动关联)
if (Error.captureStackTrace) {
Error.captureStackTrace(this, ValidationError);
}
}
}
try {
throw new ValidationError("Invalid email", "email");
} catch (err) {
console.log(err.name); // 'ValidationError'
console.log(err.message); // 'Invalid email'
console.log(err.field); // 'email'
console.log(err.stack); // 堆栈信息
}
注意:在 TypeScript 中,自定义错误类可能会丢失 instanceof 行为,建议同时设置原型。
三、全局错误捕获
1. 浏览器全局捕获
window.onerror
捕获大多数同步错误和运行时错误(不能捕获 unhandledrejection 和资源加载错误)。
window.onerror = function (message, source, lineno, colno, error) {
console.log("捕获到错误:", message, source, lineno, colno, error);
// 返回 true 可以阻止默认控制台输出
return true;
};
window.addEventListener('error')
可以捕获资源加载错误(如 <img>、<script> 加载失败)。
window.addEventListener(
"error",
event => {
if (event.target.tagName) {
// 资源加载错误
console.log("资源加载失败:", event.target.src);
} else {
// JS 运行时错误
console.log("JS 错误:", event.message);
}
},
true
); // 使用捕获阶段
unhandledrejection
捕获未处理的 Promise rejection。
window.addEventListener("unhandledrejection", event => {
console.log("未处理的 Promise 拒绝:", event.reason);
event.preventDefault(); // 可选:阻止控制台默认输出
});
2. Node.js 全局捕获
process.on('uncaughtException')
捕获未捕获的同步异常(不推荐作为生产环境的常规恢复手段,因为程序状态可能不一致)。
process.on("uncaughtException", err => {
console.error("未捕获异常:", err);
// 最佳实践:记录日志后优雅退出
process.exit(1);
});
process.on('unhandledRejection')
捕获未处理的 Promise rejection。
process.on("unhandledRejection", (reason, promise) => {
console.error("未处理的拒绝:", reason);
});
四、try/catch/finally 与 throw
1. try/catch/finally
try {
// 可能抛出错误的代码
riskyOperation();
} catch (err) {
// 错误处理
console.error(err.message);
} finally {
// 无论是否发生错误都会执行(资源清理、关闭连接等)
cleanup();
}
finally中的return会覆盖catch中的return。- 如果
try中有return,finally依然会在返回之前执行。
2. throw
可以抛出任何值(字符串、数字、对象等),但最佳实践是抛出 Error 实例或其子类。
function divide(a, b) {
if (b === 0) {
throw new Error("Division by zero");
}
return a / b;
}
注意:在 catch 块中可以重新抛出错误,以便上层处理。
try {
// ...
} catch (err) {
if (err instanceof ValidationError) {
// 处理已知错误
} else {
throw err; // 向上传递未知错误
}
}
五、栈追踪与断点调试
1. 栈追踪(Stack Trace)
错误对象的 stack 属性提供了调用栈信息,便于定位问题。
手动获取栈追踪
function trace() {
console.log(new Error().stack);
}
trace();
在 Node.js 中获取更详细的栈信息(使用 --stack-trace-limit 或 Error.captureStackTrace)
const err = new Error("custom");
Error.captureStackTrace(err, myFunction); // 排除 myFunction 自身
2. 浏览器 DevTools 调试
- 断点:在 Sources 面板点击行号设置,代码执行到此处暂停。
- 条件断点:右键行号,设置条件表达式。
- 日志点:打印日志而不暂停。
- 调用栈面板:查看函数调用链。
- Scope 面板:查看当前作用域内的变量。
- Watch 表达式:监控特定变量变化。
- 步进控制:Step over (F10)、Step into (F11)、Step out (Shift+F11)、Resume (F8)。
常用技巧
debugger语句:在代码中手动插入debugger;,当 DevTools 打开时自动断点。
function complexCalc(a, b) {
debugger; // 执行到这里会暂停
return a + b;
}
- 异步堆栈:Chrome 支持异步调用链追踪,可以查看
setTimeout、Promise、async/await的完整调用路径。
3. Node.js 调试
命令行调试(--inspect)
node --inspect-brk app.js
然后在 Chrome 浏览器打开 chrome://inspect,即可使用 DevTools 进行调试。
VS Code 调试
配置 .vscode/launch.json 文件,支持断点、变量查看、调用栈等。
打印调试与 util.inspect
const util = require("util");
console.log(
util.inspect(obj, { showHidden: false, depth: null, colors: true })
);
Node.js 内置调试器(node inspect)
node inspect app.js
使用 cont、next、watch 等命令,但体验不如 Chrome DevTools。
4. 高级:长栈追踪(Long Stack Traces)
对于异步错误,传统栈可能仅显示事件循环的当前片段。可以使用第三方库(如 longjohn)或 Promise 库的 done() 方法来增强,但现代 Node.js 的 async_hooks 和 Chrome 的异步栈已经部分解决此问题。
总结
| 方面 | 核心要点 |
|---|---|
| 错误构造函数 | Error 及子类型 SyntaxError、TypeError、RangeError、ReferenceError |
| 自定义错误 | 继承 Error,设置 name 属性,可添加额外字段 |
| 全局捕获(浏览器) | window.onerror(同步错误)、unhandledrejection(Promise 拒绝) |
| 全局捕获(Node.js) | uncaughtException(谨慎使用)、unhandledRejection |
try/catch/finally | 结构化异常处理,finally 用于清理,throw 抛出任意值(建议 Error 实例) |
| 调试 | 栈追踪 + 浏览器 DevTools(断点、条件断点、debugger)+ Node.js --inspect |
生产环境应记录日志并监控未捕获异常;具体工具选型因项目而异。
参考文献
以下链接在编写时均可正常访问:
| 资料 | 说明 |
|---|---|
| MDN:Error | 错误对象 |
| MDN:try...catch | 异常处理 |
| MDN:unhandledrejection | Promise 未处理拒绝 |
| Node.js:process 事件 | Node 全局错误(英文) |
相关文章
浏览器相关 API
DOM 事件、存储、fetch、Worker、Observer 等由浏览器提供的 Web API(非 ECMAScript 核心)。HTML 结构见 标签。
性能优化
防抖/节流、渲染与内存、资源预加载及引擎行为是前端性能的主要抓手。相关 CSS 见 视觉效果、Head 资源提示。
模块化与工程化
ESM、CommonJS 与打包工具(Tree Shaking、代码分割)是前端工程化的基础。入门见 JavaScript 基础 · 模块。
执行模型与作用域
执行上下文与词法作用域决定变量在何处可访问、闭包如何形成,以及 var / let / const 的差异。语言概览见 JavaScript 基础。
数据结构与集合增强
Map / Set、WeakMap / WeakSet、数组高阶方法、TypedArray 与 Symbol 等扩展了内置数据结构能力。见 语言基础。
JavaScript 基础
JavaScript(ECMAScript)是 Web 页面与 Node.js 等运行时的脚本语言:在浏览器中操作 DOM、发起网络请求;在服务端处理 I/O。语法与 HTML、CSS 配合构成前端技术栈。