欢迎光临
我们一直在努力

前端错误排查路线

前端错误排查路线

遇到报错先别慌,这张「错误排查地图」帮你快速定位问题根源。


一、错误排查地图作用

前端开发中,错误信息满天飞——红色警告、控制台乱跳、网络请求失败……新手往往对着错误文案复制粘贴搜索,结果找到的解决方案治标不治本,过两天又复发了。

这本专辑的核心思路是:报错只是「症状」,分类才是「分诊」。先判断问题属于哪一类,再进入对应的排查通道——这比大海捞针式的搜索高效十倍。

学习目标:遇到控制台报错或页面异常,能在 30 秒内 判断问题归属 哪一类,并打开 对应的 DevTools 面板 取证。


二、前端错误分类族谱(六大派系)

A 派系:ECMAScript 与 Web API 原生错误

这是最常见的「红屏元凶」,由 JavaScript 引擎直接抛出。

错误类型含义典型场景
SyntaxError 源代码语法非法,无法解析 括号不配对、import 位置错误、JSON 解析失败
ReferenceError 引用了不存在的变量 拼写错误、let/const 暂时性死区、作用域误判
TypeError 类型操作不合法 null 上读取属性、把非函数当函数调用
RangeError 值超出允许范围 递归爆栈、Array 长度超限
URIError URI 编码/解码异常 少见,多与 encodeURI/decodeURI 相关
DOMException Web API 业务错误 AbortError、SecurityError、QuotaExceededError

定位通则:Console 读堆栈 → Sources 定位源码 → 启用 Source Map 回溯原始文件。


B 派系:未处理的 Promise 拒绝(异步黑洞)

定义:async 函数内部 throw 等价于返回被拒绝的 Promise,若调用链上既无 await 又无 .catch(),控制台会显示 Uncaught (in promise)。

特点:这类错误 不一定 导致页面崩溃,但会使后续逻辑「断片」——你以为代码执行了,实际上它悄悄失败了。

典型案例:

// ❌ 危险:async 函数抛出错误,但无人捕获
async function fetchUser() {
const res = await fetch('/api/user');
if (!res.ok) throw new Error('用户获取失败'); // ← 这里抛错
}

function loadPage() {
fetchUser(); // ← 没有 await,也没有 .catch()
showLoadingSpinner(); // ← 后面代码继续执行,用户看到假象
}

// ✅ 正确做法
async function loadPage() {
try {
await fetchUser();
showContent();
} catch (err) {
showErrorToast(err.message);
}
}


C 派系:资源加载失败(网络传输层)

表现:控制台出现 Failed to load resource、net::ERR_*,或 Network 面板中状态码非 2xx。

错误前缀含义常见场景
net::ERR_INTERNET_DISCONNECTED 网络断开 本地断网、VPN 掉线
net::ERR_CONNECTION_TIMED_OUT 连接超时 服务器无响应、跨地域延迟
net::ERR_CONNECTION_REFUSED 连接被拒绝 端口未开放、服务未启动
net::ERR_NAME_NOT_RESOLVED DNS 解析失败 域名拼写错误、DNS 服务异常
net::ERR_SSL_PROTOCOL_ERROR SSL 握手失败 证书过期、自签名证书

定位三步曲:Network 面板 → 选中失败请求 → 查看 Headers / Timing / Initiator,定位是 DNS、TLS 还是 HTTP 层问题。


D 派系:安全策略阻断(你被浏览器拦路了)

表现:控制台含 blocked、violates Content Security Policy、Mixed Content 等关键词,Network 中状态显示 (blocked:…)。

阻断类型原因解决思路
CORS 跨域 响应头缺少 Access-Control-Allow-Origin 服务端配置白名单 或 JSONP/代理
CSP 内容安全策略 代码/资源违反站点安全策略 审查 CSP 响应头,移除违规配置
混合内容 HTTPS 页面加载 HTTP 资源 将资源 URL 升级为 HTTPS
Iframe 跨域限制 子页面访问父页面受限 使用 postMessage 通信

E 派系:构建期错误(浏览器可能啥都不显示)

特点:构建失败时,浏览器控制台可能干干净净——错误全在终端或 CI 日志里。

错误场景表现排查入口
模块解析失败 终端报 Cannot find module 查看终端输出,检查路径/别名配置
TypeScript 类型错误 tsc 编译失败 tsc –noEmit 查看详细错误
ESLint 卡壳 提交被阻止 CI 日志定位违规行
Vite/Webpack 构建报错 终端红色高亮 Vite overlay 或终端日志

F 派系:无异常日志的逻辑错误(最隐蔽的敌人)

特点:控制台一片祥和,界面却「行为诡异」——数据显示不对、按钮点了没反应、状态莫名切换。

典型场景原因排查方法
数据不对 竞态条件,后返回的数据覆盖了先返回的 添加请求 ID + 按时间顺序日志
缓存问题 加载了过期的缓存脚本 Network 勾选「禁用缓存」+ 强制刷新
特性开关 代码逻辑依赖开关状态,线上与测试不一致 检查 Feature Flag 配置
时区问题 new Date() 显示时间差 8 小时 统一使用 UTC 或指定时区

定位工具:断点调试、console.log 有序日志、Network 瀑布图时间线交叉验证。


三、首轮问询清单(建议养成固定顺序)

遇到报错时,先花 30 秒回答这五个问题,比直接搜错误文案有效得多。

问题 1:范围——全局还是局部?

现象优先检查方向
整页空白/崩溃 文档请求(Network)、入口脚本加载、主 JS 是否 404
局部组件异常 组件边界、接口响应数据、渲染逻辑

问题 2:时机——刷新即现还是交互触发?

现象优先检查方向
刷新即现 构建产物、路由回退(try_files)、首包资源
交互触发 事件处理、异步请求、状态更新逻辑

问题 3:级别——Error 还是 Warning?

⚠️ Warning 也会搞事情!废弃 API(Deprecated)的 Warning 可能导致功能静默失效。

级别应对策略
Error 必须修复,关注堆栈定位
Warning 查清来源,部分 Warning 是 Error 的前兆

问题 4:网络——接口状态码正常吗?

状态码含义优先行动
2xx 成功 检查响应体数据格式
3xx 重定向 确认重定向次数是否过多
4xx 客户端错误 检查请求参数、认证状态
5xx 服务端错误 联系后端、查看服务端日志

问题 5:环境——只有生产环境出问题?

差异点排查重点
环境变量 是否注入错误值、本地是否缺失 .env
Source Map 生产环境是否缺少 map 导致无法定位
CORS 白名单 生产域名是否在白名单内
HTTPS 混合内容问题是否只在 HTTPS 环境出现

四、DevTools 面板路由表(按症状精准匹配)

不是每个面板都要打开,对症下药才高效。

症状首选面板备选面板关键操作
异常堆栈阅读 Console Sources 点击堆栈跳转源码
接口请求失败 Network Issues 查看状态码/Timing
布局跳动(CLS) Elements(Computed) Performance 观察 Layout Shift
卡顿/掉帧 Performance(Main) Performance monitor 录制长任务
Cookie/Storage Application Network → Cookies 查看存储大小
Service Worker Application → Service Workers Network 确认 SW 来源
内存泄漏 Memory Performance monitor 录制堆快照对比

五、堆栈阅读实战指南

基础:堆栈从下往上看

TypeError: Cannot read properties of null (reading 'id')
at save (app.a1b2.js:1:23456) ← 栈顶:直接错误位置
at onClick (app.a1b2.js:1:24000) ← 第二层:触发者
at handleClick (app.a1b2.js:1:24500) ← 第三层:更上层调用

阅读原则:从下往上,表示调用链方向。栈顶帧是直接抛出错误的地方,是排查的起点。

进阶:识别压缩代码的套路

// 压缩后:定位困难
TypeError: Cannot read properties of null (reading 'id')
at Object.<computed_member_Q神 (app.a1b2.js:1:23456) ← 混淆后的函数名

// 有 Source Map:真相大白
TypeError: Cannot read properties of null (reading 'id')
at ProductCard.save (product-card.ts:87:12) ← 原始文件+行号

高级:利用 Source Map 定位生产环境问题

// 构建时启用 Source Map(按需选择)
// hidden-source-map:不上传.map到生产,只用于内部错误监控
{
"devtool": "hidden-source-map"
}

// 生产错误上报平台(如 Sentry)会自动关联.map


六、常见错误场景速查字典

场景 1:null is not iterable

原因:对数组/集合类型的假设错误,null 没有迭代器。

// ❌ 错误
function renderItems(items) {
items.forEach(item => render(item)); // items 可能为 null
}

// ✅ 防御
function renderItems(items) {
(items ?? []).forEach(item => render(item));
}

场景 2:Cannot read properties of undefined

原因:链式调用中某个环节返回 undefined,常见于接口字段不存在。

// ❌ 危险:接口返回结构与预期不符
const city = user.address.city; // 若 address 为 null/undefined

// ✅ 防御
const city = user?.address?.city;

场景 3:Async/await in non-async function

原因:async 函数内部可以直接用 await,但普通函数中直接使用 await 会报语法错误。

// ❌ 错误
function loadData() {
const data = await fetch('/api/data'); // 语法错误
}

// ✅ 正确
async function loadData() {
const data = await fetch('/api/data');
}

场景 4:Promise.then is not a function

原因:链式调用 .then() 前面的值不是 Promise。

// ❌ 危险:忘记 return Promise
async function fetchData() {
fetch('/api/data')
.then(res => res.json()); // 返回 undefined,不是 Promise
}

// ✅ 正确
async function fetchData() {
return fetch('/api/data')
.then(res => res.json());
}

场景 5:ResizeObserver loop limit exceeded

原因:ResizeObserver 回调触发了导致无限循环的布局变更。

解决:使用 ResizeObserver.observe() 并在回调中使用防抖/节流。

场景 6:__proto__ 循环引用

原因:JSON.stringify 遇到包含循环引用的对象时报错。

// ❌ 错误
const obj = { name: 'test' };
obj.self = obj;
JSON.stringify(obj); // TypeError: Converting circular structure to JSON

// ✅ 正确:使用 replacer 或第三方库
const getCircularReplacer = () => {
const seen = new WeakSet();
return (key, value) => {
if (typeof value === 'object' && value !== null) {
if (seen.has(value)) return '[Circular]';
seen.add(value);
}
return value;
};
};
JSON.stringify(obj, getCircularReplacer());

场景 7:Invalid assignment to const

原因:尝试重新赋值 const 声明的变量。

// ❌ 错误
const config = { theme: 'dark' };
config = { theme: 'light' }; // TypeError

// ✅ 正确:修改对象属性而非对象本身
const config = { theme: 'dark' };
config.theme = 'light'; // 合法

场景 8:Cannot assign to read only property

原因:尝试修改只读属性,如 Object.freeze() 冻结的对象。

// ❌ 错误
const frozen = Object.freeze({ name: 'test' });
frozen.name = 'new'; // TypeError

// ✅ 正确:先复制再修改
const copy = { frozen, name: 'new' };


七、错误处理最佳实践

1 全局错误捕获(兜底防护)

// JavaScript 运行时错误
window.addEventListener('error', (event) => {
console.error('全局捕获错误:', event.error);
reportError(event.error); // 上报到监控系统
});

// 未处理的 Promise 拒绝
window.addEventListener('unhandledrejection', (event) => {
console.error('未处理的 Promise 拒绝:', event.reason);
reportError(event.reason);
});

2 组件级错误边界(React/Vue)

// React Error Boundary 示例
class ErrorBoundary extends React.Component {
componentDidCatch(error, errorInfo) {
reportError(error, errorInfo);
}

render() {
return this.props.children;
}
}

// 使用
<ErrorBoundary>
<Dashboard />
</ErrorBoundary>

3 接口请求统一封装

async function safeRequest(url, options) {
try {
const res = await fetch(url, options);
if (!res.ok) throw new HttpError(res.status, res.statusText);
return await res.json();
} catch (err) {
if (err instanceof HttpError) {
showErrorToast(`请求失败: ${err.message}`);
}
throw err; // 重新抛出,由调用方决定处理方式
}
}


八、调试技巧与断点高级用法

1 条件断点:精准命中

当循环中某次迭代出错时,不需要每次都断住。

// 在 Sources 面板右键 → Add conditional breakpoint
// 条件:i === 99 时断住
for (let i = 0; i < 100; i++) {
processItem(i); // 只在 i === 99 时断住
}

2 Logpoint:日志打印不污染代码

在不断点的情况下输出日志。

// 在 Sources 面板行号右键 → Add logpoint
// 输入:itemId: ${item.id}, price: ${item.price}
// 效果:类似 console.log,但不实际插入代码

3 XHR/Fetch 断点:拦截请求

在 Network 面板右键请求 → Block request URL,可模拟接口失败场景。

4 事件监听器断点

在 Sources 面板 → Event Listener Breakpoints,可以针对特定事件设断点:

  • click 事件
  • keydown 事件
  • error 事件
  • unhandledrejection 事件

5 DOM 变化断点

在 Elements 面板右键元素 → Break on,可设置:

  • Subtree modifications(子树修改)
  • Attributes modifications(属性修改)
  • Node removal(节点移除)

6 异常捕获断点

勾选 Pause on caught exceptions,可以停在被 try/catch 捕获的异常处。


九、浏览器兼容性错误排查

1 常见兼容性陷阱

API/特性旧版浏览器不支持解决方案
Optional chaining (?.) IE、Node < 14 Babel 转译或手动判空
Promise.allSettled IE Polyfill 或使用 Promise.all + try/catch
BigInt IE、旧版 Safari 使用普通 Number 或 big.js
Intl.DateTimeFormat 部分移动端 WebView 使用 date-fns 等库
ResizeObserver IE 使用 element-resize-detector 库

2 快速定位兼容性

使用 Can I Use 查询 API 兼容性。

// 检测特性是否支持
const supportBigInt = typeof BigInt !== 'undefined';
const supportOptionalChain = (() => {
try {
eval('obj?.prop');
return true;
} catch {
return false;
}
})();

3 Polyfill 策略

// 条件加载 polyfill
import 'core-js/stable'; // 全部 polyfill
// 或按需加载
import 'core-js/features/promise';
import 'core-js/features/set';


十、移动端特定问题排查

1 iOS Safari 独特问题

问题表现解决方案
安全区域 底部按钮被刘海遮挡 使用 env(safe-area-inset-*)
橡皮筋效果 页面弹性回弹导致 touch 事件异常 overscroll-behavior: none
输入框聚焦 页面偏移、软键盘遮挡输入框 scrollIntoView + visualViewport
300ms 点击延迟 点击响应慢 <meta name="viewport"> 添加 user-scalable=no 或使用 FastClick

2 Android WebView 问题

// 检测是否为 WebView
const isWebView = /WebView|Android WebView/i.test(navigator.userAgent);

// WebView 版本检测
const match = navigator.userAgent.match(/Android\\s([\\d.]+)/);
const androidVersion = match ? parseFloat(match[1]) : null;

3 微信小程序 H5 桥接

// 检测微信环境
const isWeChat = /MicroMessenger/i.test(navigator.userAgent);

// 调用微信 JSSDK
if (isWeChat) {
wx.config({
debug: false,
appId: 'your-app-id',
timestamp: timestamp,
nonceStr: nonceStr,
signature: signature,
jsApiList: ['updateAppMessageShareData', 'chooseImage']
});
}

4 移动端调试工具

工具用途
Chrome Remote Debug 通过 USB 调试 Android Chrome
Safari Web Inspector 调试 iOS Safari
Eruda 移动端引入的迷你 DevTools
vConsole 腾讯出品的移动端调试面板

<!– 引入 Eruda –>
<script src="https://cdn.jsdelivr.net/npm/eruda"></script>
<script>
eruda.init();
</script>


十一、网络请求相关错误深度剖析

1 HTTP 状态码速查

状态码含义前端应对
200 成功 处理响应数据
201 资源创建成功 通常用于 POST 请求
204 无内容 不要尝试解析 JSON
301/302 重定向 检查重定向次数,防止无限循环
304 未修改 使用缓存
400 请求参数错误 检查请求参数格式
401 未认证 跳转登录页
403 无权限 提示权限不足
404 资源不存在 检查接口路径
429 请求过于频繁 实现限流逻辑
500 服务端错误 联系后端,查看服务端日志
502/503/504 网关错误 服务端问题,联系运维

2 请求超时问题

// ❌ 危险:不设置超时
const res = await fetch('/api/data'); // 可能永远等待

// ✅ 正确:使用 AbortController 设置超时
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), 5000);

try {
const res = await fetch('/api/data', { signal: controller.signal });
clearTimeout(timeoutId);
} catch (err) {
if (err.name === 'AbortError') {
showErrorToast('请求超时,请检查网络');
}
}

3 请求重复提交防护

// 请求映射表
const pendingRequests = new Map();

async function requestWithDedup(url, options) {
const key = `${url}:${JSON.stringify(options)}`;
if (pendingRequests.has(key)) {
return pendingRequests.get(key);
}

const promise = fetch(url, options).then(r => r.json());
pendingRequests.set(key, promise);

try {
return await promise;
} finally {
pendingRequests.delete(key);
}
}

4 请求取消与竞态

// ❌ 危险:搜索场景下的竞态问题
function onSearch(keyword) {
fetch(`/api/search?q=${keyword}`).then(r => r.json())
.then(results => setResults(results)); // 后输入先响应可能覆盖先输入结果
}

// ✅ 正确:使用 AbortController 取消旧请求
function onSearch(keyword) {
currentController?.abort(); // 取消上一个请求
currentController = new AbortController();

fetch(`/api/search?q=${keyword}`, { signal: currentController.signal })
.then(r => r.json())
.then(results => setResults(results));
}


十二、前端存储相关错误

1 localStorage 常见错误

// ❌ 危险:直接写入,不处理配额超限
localStorage.setItem('cache', JSON.stringify(largeData)); // 可能 QuotaExceededError

// ✅ 正确:捕获异常,清理旧数据
function saveWithFallback(key, value) {
try {
localStorage.setItem(key, JSON.stringify(value));
} catch (e) {
if (e.name === 'QuotaExceededError') {
clearOldCache(); // 清理旧缓存
localStorage.setItem(key, JSON.stringify(value));
}
}
}

2 Cookie 大小限制

Cookie 单条限制 4KB,每次请求都会携带,建议只存必要数据。

// 设置 Cookie(带过期时间)
document.cookie = `token=${token}; expires=${new Date(Date.now() + 7 * 24 * 60 * 60 * 1000).toUTCString()}; path=/; SameSite=Strict`;

3 IndexedDB 错误处理

// 打开数据库
function openDB() {
return new Promise((resolve, reject) => {
const request = indexedDB.open('myDB', 1);

request.onerror = () => reject(request.error);
request.onsuccess = () => resolve(request.result);

request.onupgradeneeded = (event) => {
const db = event.target.result;
if (!db.objectStoreNames.contains('data')) {
db.createObjectStore('data', { keyPath: 'id' });
}
};
});
}


十三、框架特定错误模式

1 React 常见错误

错误原因解决
Cannot update during an existing state transition 生命周期中 setState 使用 componentDidUpdate 或 useEffect
Rendered fewer hooks than expected 条件调用 Hook 确保 hooks 在组件顶部无条件调用
Max update depth exceeded setState 导致无限更新 检查状态更新逻辑,避免循环依赖

2 Vue 常见错误

错误原因解决
Avoid mutating a prop directly 直接修改 props 使用 emit 或 sync 修饰符
Failed to resolve component 组件未正确注册或导入 检查组件路径和注册方式
Do not access vue-router 路由对象访问时机错误 在 next() 或 onMounted 中访问

3 小程序常见错误

// ❌ 危险:Page 中直接修改 data
Page({
data: { count: 0 },
add() {
this.data.count++; // 不触发视图更新
}
});

// ✅ 正确:使用 setData
Page({
data: { count: 0 },
add() {
this.setData({ count: this.data.count + 1 });
}
});


十四、错误监控与上报体系

1 为什么要监控

  • 线上问题发现滞后?—— 实时告警
  • 用户报 bug 但无法复现?—— 现场回放
  • 同类问题反复出现?—— 趋势分析

2 核心指标

指标定义目标
Error Rate 错误请求 / 总请求 < 0.1%
JS Error Rate JS 错误数 / PV < 0.01%
API Error Rate 接口错误数 / 调用数 < 1%
P0/P1 占比 严重错误占比 < 5%

3 监控方案对比

方案优点缺点
Sentry 功能完善、开源自建 有学习成本
Bugsnag 稳定性好 收费较贵
自建监控 完全可控 维护成本高
FunDebug 适合国内 功能有限

4 错误上报最佳实践

// 基础上报
window.addEventListener('error', (event) => {
reportError({
type: 'js_error',
message: event.message,
filename: event.filename,
lineno: event.lineno,
colno: event.colno,
userAgent: navigator.userAgent,
url: location.href,
timestamp: Date.now()
});
});

// Promise 拒绝上报
window.addEventListener('unhandledrejection', (event) => {
reportError({
type: 'unhandled_rejection',
reason: event.reason?.message || String(event.reason),
stack: event.reason?.stack,
timestamp: Date.now()
});
});

// API 错误上报
async function reportAPIError(url, status, response) {
reportError({
type: 'api_error',
url,
status,
response: response?.slice(0, 500),
timestamp: Date.now()
});
}

5 采样策略

// 高频错误采样上报(避免带宽浪费)
const errorSampleRate = 0.1; // 10% 采样
const sampleRate = Math.random() < errorSampleRate;

if (shouldReport || (isHighFrequency && sampleRate)) {
sendToMonitor(errorInfo);
}


十五、Chrome 扩展程序错误排查

1 Chrome 扩展架构概览

Chrome 扩展由多个隔离环境组成,每个环境有独立的上下文:

环境代码调试工具
Content Script 注入到网页中运行 浏览器 DevTools Elements/Console
Background Script 后台常驻服务 扩展管理页 → Service Worker
Popup 弹窗页面 右键扩展图标 → 检查弹出内容
Options Page 选项页面 右键扩展图标 → 选项

2 Content Script 特殊限制

Content Script 运行在隔离的世界(Isolated World)中:

// ❌ 错误:在 Content Script 中访问页面的变量
// 页面定义的变量无法访问
console.log(pageVariable); // undefined

// ❌ 错误:Content Script 中的变量对页面不可见
const mySecret = 'hello'; // 页面代码无法访问

// ✅ 正确:使用消息通信
// Content Script 发送消息
chrome.runtime.sendMessage({ type: 'GET_DATA', data: mySecret });

// 页面接收(需注入通信脚本)
window.addEventListener('message', (event) => {
if (event.data.type === 'FROM_EXTENSION') {
console.log('收到扩展消息:', event.data.payload);
}
});

3 Manifest V3 安全限制

Manifest V3(MV3)对扩展做了更严格的限制:

限制说明应对
不允许远程代码 host_permissions 不能用 *://* 声明具体域名
Service Worker 替代 Background 后台脚本会休眠 使用 chrome.alarms 保持活跃
Declarative Net Request 替代 webRequest 使用规则配置替代代码
chrome.runtime.getURL 资源访问方式变化 需正确声明 web_accessible_resources

4 常见扩展错误与解决

错误 1:Extension context invalidated

原因:扩展的 Service Worker 被重启或页面刷新导致上下文失效。

场景:在 setTimeout 回调中调用扩展 API,但此时扩展上下文已失效。

// ❌ 危险:上下文失效后调用 API
setTimeout(() => {
chrome.runtime.sendMessage({ type: 'FETCH' }); // 可能失败
}, 5000);

// ✅ 正确:检查上下文有效性
if (chrome.runtime?.id) {
chrome.runtime.sendMessage({ type: 'FETCH' });
} else {
console.warn('扩展上下文已失效');
}

错误 2:No matching service worker found

原因:Manifest V3 中 Service Worker 休眠后未重新激活。

解决:在 manifest.json 中配置正确的 Service Worker 文件路径。

{
"manifest_version": 3,
"background": {
"service_worker": "background.js",
"type": "module"
}
}

错误 3:Cannot access a chrome:// URL

原因:Content Script 无法直接访问 Chrome 内部页面。

解决:通过 chrome.tabs.executeScript 注入,或使用 chrome.runtime.getURL。

5 扩展调试方法

// 方法 1:在扩展页面右键 → 检查弹出内容(Popup)
// 方法 2:chrome://extensions → Service Worker 链接(Background)
// 方法 3:Content Script → 打开目标页面 → 浏览器 DevTools

// Content Script 中打印日志(会显示在浏览器控制台)
console.log('Content Script 运行中');

// Background Script 日志(会在扩展页面显示)
console.log('Background Service Worker 启动');


十六、Electron 桌面端错误排查

1 Electron 架构概览

Electron = Chromium(渲染进程)+ Node.js(主进程)。

进程职责调试工具
Main Process(主进程) 系统 API、窗口管理、应用生命周期 终端输出 + –enable-logging
Renderer Process(渲染进程) 页面内容、Web 技术栈 DevTools(F12)
Preload Script(预加载脚本) 桥接主进程与渲染进程 DevTools Console

2 进程间通信错误

主进程与渲染进程通过 IPC(Inter-Process Communication)通信。

错误:Electron IPC: Object has been destroyed

原因:尝试向已关闭的窗口发送消息。

// ❌ 危险:窗口关闭后仍发送消息
mainWindow.webContents.send('update', data);
mainWindow.close();
// 此时若窗口已关闭,send 调用可能失败

// ✅ 正确:检查窗口状态
if (!mainWindow.isDestroyed()) {
mainWindow.webContents.send('update', data);
}

错误:Cannot read property of undefined in IPC handler

原因:主进程 IPC handler 中访问了未初始化的对象。

// ❌ 错误:db 未初始化就使用
ipcMain.handle('get-user', async (event, id) => {
return db.findUser(id); // db 可能为 undefined
});

// ✅ 正确:初始化检查
let db = null;

async function initDB() {
db = await openDatabase();
}

ipcMain.handle('get-user', async (event, id) => {
if (!db) throw new Error('数据库未初始化');
return db.findUser(id);
});

3 常见 Electron 错误与解决

错误 1:NODE_MODULE_VERSION mismatch

原因:Node.js 版本与 Electron 内置版本不匹配。

解决:使用 electron-rebuild 重新编译原生模块。

npm install –save-dev @electron/rebuild
npx electron-rebuild

错误 2:Uncaught Exception: Error: EACCES: permission denied

原因:尝试访问受限文件或目录。

解决:使用管理员权限运行,或修改文件权限。

// 检查是否有写入权限
const fs = require('fs');
if (fs.accessSync(path, fs.constants.W_OK)) {
console.log('无写入权限');
}

错误 3:Failed to fetch 在 Electron 中

原因:Chromium 的网络限制或代理配置问题。

解决:

// 在创建 BrowserWindow 时配置
const mainWindow = new BrowserWindow({
webPreferences: {
// 允许加载不安全的内容(仅开发环境)
webSecurity: false // ⚠️ 仅测试用,生产环境禁用
}
});

// 或配置代理
app.commandLine.appendSwitch('proxy-server', 'http://proxy.example.com:8080');

4 Electron 调试方法

问题调试方法
主进程崩溃 启动时添加 –enable-logging=stderr –v=1 参数
渲染进程崩溃 BrowserWindow 中右键 → 检查 → DevTools
IPC 通信问题 主进程使用 console.log,渲染进程使用 DevTools Console
原生模块问题 使用 electron-rebuild 重新编译
打包后闪退 使用 electron-log 捕获崩溃日志

// 安装 electron-log
const log = require('electron-log');

// 主进程日志
log.transports.file.level = 'info';
log.transports.console.level = 'debug';

// 捕获未处理异常
process.on('uncaughtException', (error) => {
log.error('未捕获异常:', error);
app.exit(1);
});

process.on('unhandledRejection', (reason, promise) => {
log.error('未处理的 Promise 拒绝:', reason);
});

5 生产环境问题

打包后缺少 native module

// 检查模块是否存在
const nativeModule = require('native-module-name');
// 若报错,在控制台会显示具体缺少什么

// 使用 electron-builder 时配置
{
"asar": true,
"asarUnpack": [
"node_modules/native-module-name/**/*"
]
}

Node.js 版本问题

// 在 package.json 中指定 Electron 版本
{
"devDependencies": {
"electron": "^28.0.0"
}
}

// 指定 Node 版本(使用 nvm)
nvm use 18 // Electron 28 使用 Node 18


十七、PDA(手持终端)错误排查

1 PDA 环境特点

PDA(Personal Digital Assistant)是一种工业级手持设备,常见于仓库管理、物流追踪、零售盘点等场景。

特点说明
操作系统 Android(居多)、Windows CE/Mobile、定制 Linux
浏览器 系统 WebView、部分设备自带 Chrome/Samsung Internet
屏幕 较小(3.5-5 英寸),需适配
性能 较弱,CPU/内存有限
网络 依赖 WiFi/4G,可能不稳定

2 PDA 特有错误场景

场景 1:WebView 版本过低

问题:部分老旧 PDA 的 WebView 版本停留在 Android 4.x,不支持现代 JS API。

表现:

  • const/let 语法报错
  • async/await 报错
  • fetch 不存在

解决:

// 检测并提示升级
const supportsModernJS = (() => {
try {
eval('const x = 1; async function a() { await 1; }');
return true;
} catch {
return false;
}
})();

if (!supportsModernJS) {
alert('您的设备版本过低,请联系管理员升级系统');
}

场景 2:本地存储限制

问题:部分 PDA 设备的 localStorage 配额极小(约 2MB)。

表现:QuotaExceededError

解决:

// 使用更小的存储或 IndexedDB
const STORAGE_LIMIT = 1 * 1024 * 1024; // 1MB 安全阈值

function safeSetItem(key, value) {
const serialized = JSON.stringify(value);
if (serialized.length > STORAGE_LIMIT) {
console.warn('存储数据过大,已自动压缩');
// 使用更激进的压缩或清理策略
return false;
}
localStorage.setItem(key, serialized);
return true;
}

场景 3:条码扫描器集成

问题:PDA 自带条码扫描器通过键盘模拟输入,但速度太快导致截断。

表现:扫码得到的条码不完整

解决:

// 监听条码扫描器的特殊前缀后缀
const SCANNER_PREFIX = ']d';
const SCANNER_SUFFIX = '\\r';

let buffer = '';
let scannerTimeout = null;

document.addEventListener('keydown', (e) => {
// 检测到扫描开始
if (e.key === SCANNER_PREFIX || e.key === '`') {
buffer = '';
clearTimeout(scannerTimeout);
return;
}

// 收集字符
if (buffer.length > 0 || e.key.match(/^[0-9A-Za-z]$/)) {
buffer += e.key;

// 清空之前的超时
clearTimeout(scannerTimeout);

// 设置新的超时(扫描结束后 100ms 无新字符则认为结束)
scannerTimeout = setTimeout(() => {
if (buffer.length >= 8) { // 最小条码长度
handleBarcode(buffer);
}
buffer = '';
}, 100);
}
});

场景 4:摄像头扫码(ZXing)

// 使用 ZXing 库进行扫码
import { BrowserMultiFormatReader } from '@zxing/browser';

async function scanBarcode() {
const reader = BrowserMultiFormatReader();
const videoElement = document.getElementById('video');

try {
// 获取摄像头
const devices = await reader.listVideoInputDevices();
const cameraId = devices[0]?.deviceId;

// 开始扫描
const result = await reader.decodeFromVideoDevice(cameraId, videoElement, (result, error) => {
if (result) {
console.log('扫描结果:', result.getText());
reader.reset();
}
});

return result?.getText();
} catch (err) {
if (err.name === 'NotAllowedError') {
alert('请允许摄像头访问权限');
} else {
console.error('扫码失败:', err);
}
}
}

3 PDA 调试方法

工具说明
Chrome Remote Debug 通过 USB 连接 Android PDA,使用 adb 进行调试
Weinre 远程 Web 检查工具,适合无法直连的情况
Eruda/vConsole 内嵌式调试面板,适合移动端
USB 调试日志 部分 PDA 支持串口输出日志

# 使用 ADB 调试 Android PDA
adb devices # 查看已连接设备
adb -s <device-id> shell "logcat" # 查看日志
adb -s <device-id> forward tcp:9222 localabstract:chrome-devtools socket
# 然后用 Chrome 访问 chrome://inspect

<!– 内嵌 Eruda –>
<script src="https://cdn.jsdelivr.net/npm/eruda"></script>
<script>
// 只在调试模式启用
if (location.search.includes('debug')) {
eruda.init();
}
</script>

4 PDA 性能优化

问题解决方案
内存不足 减少 DOM 节点,使用虚拟列表
页面卡顿 使用 requestAnimationFrame,避免重排
白屏时间长 使用离线缓存(Service Worker/Application Cache)
耗电量大 减少动画,使用 will-change 提示浏览器

十八、微信小程序错误排查

1 小程序架构概览

微信小程序运行在 双线程模型 中:

线程职责技术栈
逻辑层(App Service) JS 逻辑、API 调用 JavaScript(限制版)
渲染层(WebView) WXML/WXSS 渲染 WebView
原生层 拍照、支付等原生能力 微信客户端

2 小程序特有错误类型

错误 1:errMsg: "request:fail"

原因:网络请求失败,可能是域名未配置或 SSL 证书问题。

排查步骤:

  • 检查 mp.weixin.qq.com 控制台 → 开发管理 → 服务器域名配置
  • 确认域名已添加到 request 和 uploadFile 白名单
  • 检查 SSL 证书是否有效(微信要求 TLS 1.2+)
  • 确认已配置 sslVerify: false(仅开发调试用)
  • // 检查网络状态
    wx.getNetworkType({
    success(res) {
    console.log('网络类型:', res.networkType);
    if (res.networkType === 'none') {
    wx.showToast({ title: '网络不可用' });
    }
    }
    });

    错误 2:errMsg: "chooseImage:fail cancel"

    原因:用户取消选择图片。

    处理:

    wx.chooseImage({
    count: 1,
    success(res) {
    console.log('选中图片:', res.tempFilePaths);
    },
    fail(err) {
    if (err.errMsg.includes('cancel')) {
    console.log('用户取消选择');
    } else {
    console.error('选择失败:', err);
    }
    }
    });

    错误 3:Cannot read property 'xxx' of undefined

    原因:在 this.data 中读取了不存在的字段,或在回调中 this 指向错误。

    解决:

    // ❌ 错误:在异步回调中使用 this
    Page({
    data: { user: null },
    onLoad() {
    wx.request({
    url: '/api/user',
    success(res) {
    this.setData({ user: res.data }); // this 指向错误
    }
    });
    }
    });

    // ✅ 正确:使用 that 保存 this
    Page({
    data: { user: null },
    onLoad() {
    const that = this;
    wx.request({
    url: '/api/user',
    success(res) {
    that.setData({ user: res.data });
    }
    });
    }
    });

    // ✅ 更优:箭头函数
    Page({
    data: { user: null },
    onLoad() {
    wx.request({
    url: '/api/user',
    success: (res) => {
    this.setData({ user: res.data });
    }
    });
    }
    });

    3 小程序调试方法

    方法 1:开发者工具

    在微信开发者工具中:

    • Console:查看日志和错误
    • Sources:查看源码(编译后的代码)
    • Network:查看请求(需开启「不校验合法域名」)
    • Storage:查看本地存储
    方法 2:真机调试

    // 添加日志输出
    console.log('当前数据:', this.data);
    console.error('错误信息:', error);

    // 使用 wx.getLogManager
    const log = wx.getLogManager();
    log.info({ key: 'value' });
    log.warn('警告信息');
    log.error('错误信息');

    方法 3:告警配置

    在微信公众平台 → 开发管理 → 运维中心配置告警:

    • JS 错误告警
    • API 请求告警
    • 组件渲染告警

    4 小程序常见问题速查

    问题原因解决
    页面不跳转 路径未配置或跳转方式错误 使用 wx.navigateTo 或 navigateTo
    数据不更新 未使用 setData 必须调用 this.setData() 更新
    请求无响应 域名未配置或超时 检查白名单配置
    样式不生效 缺少 scoped 或选择器问题 检查 WXSS 选择器
    onLoad 未执行 页面未在 app.json 注册 检查 pages 配置
    授权失败 用户拒绝或未配置权限 使用 wx.openSetting 引导

    5 支付宝/百度小程序差异

    支付宝小程序(my)和百度小程序(swan)与微信小程序类似,但 API 有差异:

    // 微信
    wx.request({ url, success() {} });

    // 支付宝
    my.httpRequest({ url, success() {} });

    // 百度
    swan.request({ url, success() {} });

    兼容性封装示例:

    const platform = wx ? 'wechat' : my ? 'alipay' : 'baidu' : 'unknown';

    const request = (options) => {
    const adapter = {
    wechat: wx.request,
    alipay: my.httpRequest,
    baidu: swan.request
    }[platform];

    return new Promise((resolve, reject) => {
    adapter({
    options,
    success: resolve,
    fail: reject
    });
    });
    };


    十九、支付宝/其他小程序补充

    1 支付宝小程序特点

    特性说明
    生命周期 与微信类似,但部分 API 名称不同
    支付能力 my.pay() 直接扣费能力更强
    扫码 my.scan()
    授权 my.getAuthCode()

    2 抖音/字节小程序

    API微信抖音
    网络请求 wx.request tt.request
    跳转 wx.navigateTo tt.navigateTo
    存储 wx.setStorage tt.setStorage
    登录 wx.login tt.login

    3 跨平台开发框架

    使用 Taro、UniApp 等框架可以一份代码多端运行:

    // Taro 示例
    import Taro from '@tarojs/taro';

    Taro.request({
    url: '/api/data'
    }).then(res => {
    console.log(res.data);
    });

    // 构建命令
    // taro build –type weapp 微信小程序
    // taro build –type alipay 支付宝小程序
    // taro build –type swan 百度小程序
    // taro build –type tt 抖音小程序


    二十、生产环境错误排查 Checklist

    上线前检查这几点,能避免 80% 的生产事故。

    代码层面

    • Source Map 配置:生产环境使用 hidden-source-map,不上传源码到客户端
    • 可选链 ?. 检查:所有链式访问添加可选链
    • 空值合并 ?? 检查:为默认值提供判断
    • try/catch:所有异步调用添加异常捕获
    • AbortController:组件卸载时取消进行中的请求

    架构层面

    • 错误监控接入:接入 Sentry/Bugsnag 等监控系统
    • 接口契约校验:使用 Zod/TypeScript 类型守卫验证接口返回
    • 异常状态 UI:加载中、空状态、错误状态都有兜底 UI
    • Error Boundary:React/Vue 组件设置错误边界
    • 全局兜底:window.addEventListener('error') 和 unhandledrejection

    环境配置

    • 环境变量校验:启动时检查必要环境变量是否注入
    • 灰度发布策略:先小流量验证,再全量发布
    • CDN 缓存策略:确保静态资源正确缓存,版本号更新
    • HTTPS 配置:确保证书有效,混合内容问题已解决

    兼容性

    • Polyfill 检查:使用 core-js 或 babel polyfill
    • 浏览器兼容测试:IE11/Safari/旧版 Chrome 测试
    • 移动端测试:iOS Safari 和 Android WebView 测试
    • 微信环境测试:在微信内置浏览器测试

    二十一、专辑内容索引

    本专辑采用「总览 + 分册」结构,建议按需阅读:

    文章编号主题核心内容
    18 总览与路线图 错误分类、首轮问询清单、DevTools 路由表
    19 JavaScript 与异步错误定位 运行时错误、Promise、Source Map
    20 网络资源与浏览器安全类错误 HTTP 状态码、CORS、CSP、MIME、TLS
    21 构建期与白屏类问题 构建失败、白屏排查表、Hydration

    相关依赖知识(这些基础文章能帮你理解更深):

    文章编号主题为什么相关
    05 前端网络层 HTTP 协议、CORS 机制是网络错误的根基
    09 异步与主线程 理解事件循环才能理解异步错误
    10 前端安全 CSP、CORS 等安全策略的详细原理
    11 性能与可观测性 Performance 面板、Memory 面板使用

    二十二、结语:报错是朋友,不是敌人

    每个报错文案都是浏览器在喊:「这里有问题!」学会倾听和解读它们,你的问题排查效率会提升一个量级。

    记住三句话:

  • 报错是症状,分类是诊断——先判断类别,再找方案
  • 工具要用对——DevTools 每个面板都有专长,别乱点
  • 防御式编程——提前兜底,比事后救火省心十倍
  • 错误排查的终极心法:

    遇到报错 → 冷静分析 → 分类定位 → DevTools 取证 → 修复 + 监控
    `

    赞(0)
    未经允许不得转载:171主机测评 » 前端错误排查路线
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址