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 选项让监听器自动移除。
解决方案:使用命名函数便于移除监听器,或使用 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 声明变量(推荐)或使用立即执行函数创建独立作用域。
解决方案:使用 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。
解决方案:使用箭头函数(推荐)或在构造函数中使用 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 控制精度,或使用专门的数学库处理货币计算。
解决方案:转换为整数计算、使用 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 统一处理错误。
解决方案:使用 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:代码质量检查和格式化