🐛 前端常见 Bug 及解决方案

帮助开发者快速定位和修复常见问题

Bug #1: 事件监听器未移除导致内存泄漏

❌ 错误代码
// 每次调用都添加新的监听器,但从不移除 function initButton() { document.getElementById('myBtn').addEventListener('click', function() { console.log('Button clicked!'); }); } // 多次调用会创建多个监听器 initButton(); initButton(); initButton();
✅ 正确代码
// 使用命名函数,便于添加和移除 function handleClick() { console.log('Button clicked!'); } function initButton() { const btn = document.getElementById('myBtn'); // 先移除旧的监听器(如果存在) btn.removeEventListener('click', handleClick); // 再添加新的监听器 btn.addEventListener('click', handleClick); } // 或者使用 once 选项 btn.addEventListener('click', handleClick, { once: true });
问题原因:重复添加事件监听器而不移除,会导致同一个事件触发多次回调,造成内存泄漏和性能问题。
解决方案:使用命名函数便于移除监听器,或使用 once 选项让监听器自动移除。

Bug #2: 循环中的闭包问题

❌ 错误代码
// 所有按钮都会输出 5 for (var i = 0; i < 5; i++) { document.getElementById('btn' + i).addEventListener('click', function() { console.log('Button ' + i + ' clicked'); // 总是输出 5 }); }
✅ 正确代码(方法1:使用 let)
// 使用 let 创建块级作用域 for (let i = 0; i < 5; i++) { document.getElementById('btn' + i).addEventListener('click', function() { console.log('Button ' + i + ' clicked'); // 正确输出 0-4 }); }
✅ 正确代码(方法2:使用 IIFE)
// 使用立即执行函数创建闭包 for (var i = 0; i < 5; i++) { (function(index) { document.getElementById('btn' + index).addEventListener('click', function() { console.log('Button ' + index + ' clicked'); }); })(i); }
问题原因:var 声明的变量是函数作用域,循环结束后 i 的值为 5,所有事件处理函数共享同一个 i。
解决方案:使用 let 声明变量(推荐)或使用立即执行函数创建独立作用域。

Bug #3: 异步操作中 this 指向丢失

❌ 错误代码
class MyComponent { constructor() { this.name = 'MyComponent'; this.count = 0; } handleClick() { console.log(this.name); // undefined this.count++; // Error: Cannot read property 'count' of undefined } init() { document.getElementById('btn').addEventListener('click', this.handleClick); } }
✅ 正确代码(方法1:箭头函数)
class MyComponent { constructor() { this.name = 'MyComponent'; this.count = 0; } // 使用箭头函数,自动绑定 this handleClick = () => { console.log(this.name); // 'MyComponent' this.count++; } init() { document.getElementById('btn').addEventListener('click', this.handleClick); } }
✅ 正确代码(方法2:bind)
class MyComponent { constructor() { this.name = 'MyComponent'; this.count = 0; // 在构造函数中绑定 this this.handleClick = this.handleClick.bind(this); } handleClick() { console.log(this.name); // 'MyComponent' this.count++; } init() { document.getElementById('btn').addEventListener('click', this.handleClick); } }
问题原因:事件处理函数被调用时,this 指向触发事件的 DOM 元素,而不是类实例。
解决方案:使用箭头函数(推荐)或在构造函数中使用 bind 绑定 this。

Bug #4: JavaScript 浮点数精度问题

❌ 错误示例
console.log(0.1 + 0.2); // 0.30000000000000004 console.log(0.1 + 0.2 === 0.3); // false let price = 19.9; let quantity = 3; console.log(price * quantity); // 59.699999999999996
✅ 正确代码
// 方法1:转换为整数计算 function add(a, b) { const factor = 10; return (a * factor + b * factor) / factor; } console.log(add(0.1, 0.2)); // 0.3 // 方法2:使用 toFixed 并转回数字 function calculate(a, b) { return Number((a + b).toFixed(10)); } // 方法3:使用第三方库(如 decimal.js) // import Decimal from 'decimal.js'; // new Decimal(0.1).plus(0.2).toNumber(); // 0.3 // 实用函数 function formatMoney(amount) { return Math.round(amount * 100) / 100; // 保留两位小数 } let price = 19.9; let quantity = 3; console.log(formatMoney(price * quantity)); // 59.7
问题原因:JavaScript 使用 IEEE 754 标准的双精度浮点数,某些十进制小数无法精确表示。
解决方案:转换为整数计算、使用 toFixed 控制精度,或使用专门的数学库处理货币计算。

Bug #5: 异步操作执行顺序混乱

❌ 错误代码
// 回调地狱,难以维护 function getUserData(userId) { fetchUser(userId, function(user) { fetchUserPosts(user.id, function(posts) { fetchPostComments(posts[0].id, function(comments) { console.log(comments); // 嵌套太深,难以阅读 }); }); }); }
✅ 正确代码(使用 async/await)
// 使用 async/await 使代码更清晰 async function getUserData(userId) { try { const user = await fetchUser(userId); const posts = await fetchUserPosts(user.id); const comments = await fetchPostComments(posts[0].id); console.log(comments); return comments; } catch (error) { console.error('Error fetching user data:', error); throw error; } } // 并行执行多个异步操作 async function getAllData() { try { const [users, posts, comments] = await Promise.all([ fetchUsers(), fetchPosts(), fetchComments() ]); return { users, posts, comments }; } catch (error) { console.error('Error:', error); } }
问题原因:回调嵌套导致代码难以阅读和维护(回调地狱),错误处理也很困难。
解决方案:使用 Promise 链式调用或 async/await(推荐),并使用 try/catch 统一处理错误。

📊 常见前端 Bug 汇总表

Bug 类型 常见场景 快速解决方案 预防措施
内存泄漏 未移除事件监听器、定时器未清除 使用 removeEventListener, clearInterval 组件销毁时清理资源
XSS 攻击 直接插入用户输入的 HTML 使用 textContent 或转义 HTML 始终验证和清理用户输入
CORS 错误 跨域请求被浏览器阻止 配置服务器 CORS 头或使用代理 开发时配置代理,生产环境配置 CORS
性能问题 频繁 DOM 操作、大列表渲染 使用虚拟滚动、防抖节流 使用 React.memo、懒加载等优化
兼容性问题 新 API 在旧浏览器不支持 使用 polyfill 或 Babel 转译 使用 Can I Use 检查兼容性

🛠️ 实用调试工具和最佳实践

推荐调试工具:

  • Chrome DevTools:查看控制台错误、网络请求、性能分析
  • React DevTools:调试 React 组件和状态
  • ESLint + Prettier:代码质量检查和格式化
豫ICP备2026003309号-1