JavaScript 作为一门灵活的编程语言,有着许多令人困惑的特性和行为。即使是经验丰富的开发者,有时也会掉入这些"陷阱"中,分享一些我遇到的也踩过的坑。
1. 类型转换的迷惑
JavaScript 的类型转换规则可能会让人摸不着头脑:
console.log([] + []); // 输出:""
console.log([] + {}); // 输出:"[object Object]"
console.log({} + []); // 输出:0(在某些浏览器中)
console.log([] == ![]); // 输出:true
这些看似不合理的结果,其实都遵循着 JavaScript 的类型转换规则。当进行加法运算时,JavaScript 会优先将操作数转换为原始类型,然后进行运算。
2. 变量提升的陷阱
console.log(a); // 输出:undefined
var a = 1;
console.log(b); // 报错:ReferenceError
let b = 2;
变量提升是 JavaScript 中一个经典的概念。使用 var 声明的变量会被提升到作用域顶部,但初始化不会提升。而 let 和 const 声明的变量存在暂时性死区(TDZ),在声明前访问会抛出错误。
3. this 指向问题
const obj = {
name: '小明',
sayHi() {
setTimeout(function() {
console.log('你好,' + this.name);
}, 100);
}
};
obj.sayHi(); // 输出:你好,undefined
在这个例子中,setTimeout 中的回调函数里的 this 指向全局对象(非严格模式下)或 undefined(严格模式下),而不是 obj。解决方案包括:
// 方案1:使用箭头函数
setTimeout(() => {
console.log('你好,' + this.name);
}, 100);
// 方案2:使用 bind
setTimeout(function() {
console.log('你好,' + this.name);
}.bind(this), 100);
4. 闭包陷阱
for (var i = 0; i < 3; i++) {
setTimeout(() => {
console.log(i);
}, 100);
}
// 输出:3, 3, 3
这是一个经典的闭包问题。使用 var 声明的变量 i 是函数作用域的,所有的 setTimeout 回调都共享同一个 i。解决方案:
5. 数值计算精度问题
console.log(0.1 + 0.2); // 输出:0.30000000000000004
console.log(0.1 + 0.2 === 0.3); // 输出:false
这是因为 JavaScript 使用 IEEE 754 双精度浮点数来表示数字,某些小数无法被精确表示。解决方案:
6. 数组方法的陷阱
解决方案:
7. Promise 的常见陷阱
正确的做法:
8. 事件监听器的内存泄漏
// 错误示例:可能造成内存泄漏
function addHandler() {
const element = document.getElementById('button');
element.addEventListener('click', () => {
console.log('Clicked');
});
}
// 正确示例:
function addHandler() {
const element = document.getElementById('button');
const handler = () => {
console.log('Clicked');
};
element.addEventListener('click', handler);
// 清理函数
return () => {
element.removeEventListener('click', handler);
};
}
该文章在 2025/1/16 12:22:18 编辑过