Skip to main content

“肌肉记忆”和“语言系统”脱节

面试题:前端如何处理项目中遇到的异常情况 【防 -> 抓 -> 扛 -> 报】

异常捕获、日志上报、用户侧兜底和工程化预防四个维度,不仅要解决问题,还要做好防御。

1.防御性编程(怎么防)

最好的处理是提前规避,展现你的工程素养:

(1)类型安全:强制使用 TypeScript 的 strict 模式,结合 unknown 类型处理第三方数据,避免“Cannot read property of undefined”。

(2)可选链与空值合并:大量使用 ?. 和 ?? 避免深层取值报错。

(3)参数校验:在工具函数入口处使用 assert 或 zod 校验参数,尽早抛出可读性强的错误信息,而非让内部逻辑隐晦地崩溃。

2.异常分类与捕获策略(怎么抓)

首先要区分异常类型,不同场景用不同手段:

(1)JS 运行时错误:使用 window.onerror(捕获同步和部分异步错误)配合 window.addEventListener('unhandledrejection')(专门捕获未处理的 Promise reject)。注意,try...catch 更适合处理可预见的业务逻辑(如 JSON.parse)。

(2)资源加载错误(图片、Script、CSS 加载失败):使用 window.addEventListener('error', handler, true) 在捕获阶段拦截,因为加载失败不会冒泡。

(3)接口请求异常(HTTP 状态码、超时、网络断连):在 Axios 或 Fetch 的拦截器(Interceptors)中统一处理,不要在每个请求里重复写 catch。

(4)框架边界错误(React/Vue):使用 Error Boundaries(React)或全局配置 Vue.config.errorHandler 捕获组件渲染时的崩溃,防止白屏蔓延到整个应用。

3.用户侧优雅降级(怎么扛)

捕获异常后,不能让用户面对白屏或卡死,必须提供可感知的反馈:

(1)关键路径降级:对于核心模块(如支付、表单),若出错则展示“网络繁忙,请稍后重试”的占位图,并提供显式的重试按钮(Retry),而非自动刷新。

(2)非关键路径静默处理:如埋点上报失败、推荐位加载失败,直接隐藏该模块(Skeleton 替换为 null),不影响主体功能。

(3)状态管理兜底:在 Redux/Vuex 中为每个异步状态定义 loading、error、data 三种状态,确保 UI 层始终有 error 态对应的界面分支。

4.错误上报与监控闭环(怎么报)

线上报错必须能还原现场,否则无法修复:

(1)结构化上报:上报内容需包含 message、stack、URL、userId、timestamp,以及用户操作路径(点击了什么按钮)。

(2)SourceMap 还原:生产环境代码是压缩的,需将报错堆栈上传至监控平台(如 Sentry、ARMS),利用 SourceMap 还原为源码位置。注意:SourceMap 文件切勿暴露在公网,只上传到内部监控服务器。

(3)多维度的错误隔离:区分“偶发性错误”(可重试)和“严重错误”(影响主流程),对严重错误触发报警机器人(飞书/钉钉)通知。

面试加分项(展现深度):

(1)关于“重试”与“幂等”:对于接口超时或 5xx 错误,我会引入指数退避(Exponential Backoff)重试策略(如重试 3 次,间隔 1s, 2s, 4s)。但必须确保接口是幂等的(尤其是 POST 请求),避免因重试导致用户重复下单。

(2)关于“自杀式”重启:如果发生 WebAssembly 崩溃或 WebGL 上下文丢失等不可恢复的致命错误,我会引导用户刷新页面,并利用 localStorage 暂存当前未提交的表单数据,刷新后自动恢复输入内容,将用户体验损失降到最低。

如果面试官追问“如何处理跨域脚本的报错”,你可以补充:需要在前端资源标签加 crossorigin="anonymous" 属性,同时后端响应头需配置 Access-Control-Allow-Origin,否则 window.onerror 只能捕获到“Script error.”,拿不到具体堆栈。

也就4-3这个点在项目中没区分直接通知负责人处理,面试的时候,我有点懵了,啥,面试官问的啥