JavaScript 错误 - throw、try 和 catch
JavaScript 错误处理throw、try 和 catch在 JavaScript 中错误处理是构建健壮应用程序的关键。通过try...catch...finally结构和throw语句我们可以优雅地捕获、处理和抛出错误防止程序崩溃并提供更好的用户体验。一、错误类型 (Error Types)JavaScript 内置了多种错误类型了解它们有助于精准捕获和处理。错误类型描述常见触发场景Error通用错误基类手动抛出或未知错误SyntaxError语法错误eval()中的非法语法JSON 解析错误ReferenceError引用错误访问未声明的变量TypeError类型错误对非对象调用方法null/undefined调用RangeError范围错误数组长度非法递归过深URIErrorURI 错误decodeURI()参数非法EvalErrorEval 错误eval()使用不当 (现代 JS 中很少见)// 示例leta;console.log(a);// undefined (不是错误)console.log(b);// ReferenceError: b is not definedhello.length;// 5 (正常)(5).length;// TypeError: Cannot read property length of numberJSON.parse({a:1});// SyntaxError: Unexpected token a二、try…catch…finally 结构这是 JavaScript 处理同步错误的核心机制。1. 基本语法try{// 可能抛出错误的代码riskyOperation();}catch(error){// 捕获并处理错误console.error(发生错误:,error.message);}finally{// 无论是否出错都会执行的代码 (清理资源)console.log(清理工作完成);}2. 执行流程try: 执行代码。若无错误跳过catch执行finally(如果有)然后继续后续代码。若有错误立即停止try中剩余代码跳转到catch。catch: 接收错误对象。执行错误处理逻辑。如果catch中再次抛出错误finally仍会执行然后错误继续向上传播。finally: 无论发生什么都会执行。常用于关闭文件、清除定时器、隐藏加载动画等。3. 代码示例functiondivide(a,b){try{if(b0){thrownewError(除数不能为零);}returna/b;}catch(err){console.error(计算失败:${err.message});returnnull;// 返回默认值}finally{console.log(除法操作结束);}}console.log(divide(10,2));// 5, 除法操作结束console.log(divide(10,0));// null, 计算失败: 除数不能为零, 除法操作结束三、throw 语句throw用于手动抛出错误。抛出的值可以是任何类型但通常抛出Error对象或其子类。1. 抛出标准错误对象functionvalidateAge(age){if(typeofage!number){thrownewTypeError(年龄必须是数字);}if(age0||age150){thrownewRangeError(年龄必须在 0 到 150 之间);}returntrue;}try{validateAge(20);// 抛出 TypeError}catch(e){console.log(e.name);// TypeErrorconsole.log(e.message);// 年龄必须是数字console.log(e.stack);// 堆栈跟踪信息}2. 抛出自定义错误 (ES2022)现代 JavaScript 允许直接继承Error创建自定义错误类使错误处理更语义化。classValidationErrorextendsError{constructor(message,field){super(message);this.nameValidationError;this.fieldfield;// 保持堆栈跟踪正确 (在 V8 引擎中)Error.captureStackTrace(this,ValidationError);}}functionregisterUser(user){if(!user.email){thrownewValidationError(邮箱不能为空,email);}if(!user.password||user.password.length6){thrownewValidationError(密码长度至少6位,password);}}try{registerUser({name:Alice});}catch(err){if(errinstanceofValidationError){console.log(字段${err.field}验证失败${err.message});}}3. 抛出非 Error 对象 (不推荐)虽然可以抛出字符串或数字但这会导致调试困难且丢失堆栈信息。// 不推荐throw出错了;throw404;// 推荐thrownewError(出错了);throw{code:404,message:Not Found};// 如果必须用对象也要小心处理四、错误处理最佳实践1. 捕获特定错误避免使用空的catch或捕获所有错误而不区分这可能会掩盖真正的 bug。// 不推荐捕获所有错误并忽略try{risky();}catch(e){// 什么都不做错误被吞掉了}// 推荐区分处理try{risky();}catch(e){if(einstanceofNetworkError){showRetryButton();}elseif(einstanceofValidationError){showFormError(e.field);}else{// 未知错误记录日志并上报console.error(未知错误,e);reportToSentry(e);}}2. 不要吞掉错误在catch块中如果无法处理应该重新抛出 (throw e) 或记录日志。try{doSomething();}catch(e){console.error(处理失败,e);throwe;// 重新抛出让上层处理}3. 使用 finally 清理资源确保资源如定时器、文件流、锁被释放。lettimer;try{timersetInterval((){// ...},1000);// 可能出错的代码}catch(e){console.error(e);}finally{if(timer)clearInterval(timer);// 确保定时器被清除}4. 异步错误处理try...catch不能直接捕获异步回调如setTimeout中的错误但可以捕获async/await中的错误。场景 A: Promise 链fetchData().then(dataprocessData(data)).catch(err{console.error(Promise 错误:,err);});场景 B: Async/Await (推荐)asyncfunctionloadData(){try{constresponseawaitfetch(/api/data);constdataawaitresponse.json();returndata;}catch(err){console.error(加载失败:,err);throwerr;// 重新抛出}finally{setLoading(false);}}场景 C: 全局未捕获错误对于未被catch捕获的 Promise 拒绝需要监听全局事件window.addEventListener(unhandledrejection,event{event.preventDefault();// 阻止默认控制台报错console.error(未处理的 Promise 错误:,event.reason);});五、常见陷阱与技巧1. 错误对象属性属性含义name错误名称 (如 “TypeError”)message错误描述信息stack堆栈跟踪字符串 (调试神器)try{undefined.func();}catch(e){console.log(e.name);// TypeErrorconsole.log(e.message);// Cannot read properties of undefinedconsole.log(e.stack);// 包含文件行号}2. 重新抛出错误在catch中处理部分逻辑后如果需要上层知道错误必须throw。try{// ...}catch(e){logError(e);// 忘记 throw e; - 错误被吞掉上层以为成功了throwe;}3. 错误边界 (React 等框架)在 UI 框架中通常有专门的“错误边界”组件来捕获子组件渲染时的错误防止整个应用崩溃。4. 调试技巧在catch中打断点查看error对象。使用console.trace()在抛出前打印调用栈。使用debugger语句配合try...catch定位问题。六、总结关键字作用关键点try包裹可能出错的代码必须与catch或finally配合使用catch捕获并处理错误接收错误对象可区分类型处理finally无论成败都执行的清理代码常用于资源释放throw手动抛出错误推荐抛出Error对象或子类核心原则不要吞掉错误要么处理要么抛出。精准捕获尽量捕获特定类型的错误。保持堆栈抛出错误时保留原始堆栈信息。异步处理使用async/awaittry/catch处理异步错误。掌握这些机制能让你的 JavaScript 代码更加健壮、易维护