最近在review代码时,我们经常会发现一些看似不起眼,但却会导致程序出现问题的 JavaScript 错误。这些错误有些是概念不清导致的,有些则是粗心大意造成的。今天,我们就来盘点一下 20 个常见的 JavaScript 错误,并提供相应的解决方案,帮助大家写出更健壮的代码。
1. 使用==而不是===
JavaScript 中的 == 和 === 是两种不同的相等性比较运算符。== 会进行类型转换,而 === 不会。
let x = '5';
console.log(x == 5); // 输出 true (类型转换)
console.log(x === 5); // 输出 false (严格相等)
在大多数情况下,我们应该使用 === 来进行严格相等性比较,以避免类型转换带来的意外结果。
2. 忘记声明变量(隐式全局变量)
在函数内部直接给变量赋值,而没有使用 let、const或 var 声明,会导致该变量成为全局变量。
function myFunc() {
y = 10; // 错误: 创建了一个全局变量
}
let y = 10; // 正确: 使用 let 或 const 声明
隐式全局变量容易造成命名冲突和污染全局命名空间,应该尽量避免。
3. 错误使用箭头函数中的this
箭头函数中的 this 指向定义时所在的作用域,而不是调用时的作用域。
const obj = {
value: 42,
getValue: () => this.value // 错误: this 是 undefined
};
const objFixed = {
value: 42,
getValue() { return this.value; } // 正确: this 指向 objFixed
};
如果需要 this 指向当前对象,请使用普通函数,而不是箭头函数。
4. 没有处理async/await错误
async/await 是 JavaScript 中处理异步操作的强大工具,但是如果没有正确处理错误,可能会导致程序崩溃。
async function fetchData() {
const response = await fetch('https://api.example.com'); // 错误: 没有错误处理
}
async function fetchDataCorrect() {
try {
const response = await fetch('https://api.example.com');
} catch (error) {
console.error('Fetch error:', error); // 正确: 捕获错误
}
}
要使用 try...catch 语句来捕获 async/await 操作中可能出现的错误。
5. 错误地修改const对象或数组
const 声明的变量不能被重新赋值,但是可以修改对象或数组的内容。
const arr = [1, 2];
arr = [3, 4]; // 错误: 试图重新赋值
arr.push(3); // 正确: 修改数组内容
const 声明的数组或对象,可以通过方法对其内部元素进行修改,但是不能对变量重新赋值。
6. 忽略浮点数精度问题
JavaScript 中的浮点数运算可能会出现精度问题。
console.log(0.1 + 0.2 === 0.3); // 输出 false
console.log(Math.abs(0.1 + 0.2 - 0.3) < Number.EPSILON); // 输出 true
要避免直接使用 == 或 === 比较浮点数,应该使用 Number.EPSILON 来判断浮点数是否足够接近。
7. 在数组上错误地使用for...in循环
for...in 循环用于遍历对象的可枚举属性,而不是数组的元素。
const nums = [1, 2, 3];
for (let i in nums) {
console.log(nums[i]); // 错误: 遍历索引
}
for (let num of nums) {
console.log(num); // 正确: 遍历数组元素
}
遍历数组元素应该使用 for...of 循环,或者使用 forEach() 等数组方法。
8. 在switch语句中忘记break
switch 语句中的 case 如果没有 break,会发生 case 穿透现象。
switch (x) {
case 1:
console.log('One');
case 2: // 错误: 没有 break,会执行下一个 case
console.log('Two');
}
switch (x) {
case 1:
console.log('One');
break; // 正确: 跳出 switch 语句
case 2:
console.log('Two');
break;
}
每一个 case 语句都应该有对应的 break,除非你确实需要穿透的效果。
9. 不理解循环中的闭包
在循环中使用 setTimeout 等异步操作时,如果使用 var 声明循环变量,会导致闭包问题。
for (var i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 1000); // 错误: 输出 3, 3, 3
}
for (let i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 1000); // 正确: 输出 0, 1, 2
}
使用 let 声明循环变量可以解决闭包问题,因为 let 具有块级作用域。
10. 覆盖函数参数
在严格模式下,函数参数不能同名。
function sum(a, a) { // 错误: SyntaxError in strict mode
return a + a;
}
function sumCorrect(a, b) {
return a + b; // 正确: 参数名不重复
}
函数参数名应该保持唯一,避免混淆。
11. 访问未定义的属性
访问对象上不存在的属性会返回 undefined,如果试图访问 undefined 的属性,会抛出错误。
const obj1 = {};
console.log(obj1.name.length); // 错误: 不能读取 undefined 的属性 'length'
console.log(obj1.name?.length); // 正确: 可选链式调用
可以使用可选链式调用符 ?. 来安全地访问对象的属性,避免因属性不存在而导致的错误。
12. 使用map()而不返回值
map() 方法应该返回一个新数组,如果没有返回值,则新数组中元素均为 undefined。
const nums2 = [1, 2, 3];
const doubled = nums2.map(num => { num * 2; }); // 错误: 未返回
const doubledCorrect = nums2.map(num => num * 2); // 正确: 返回新值
确保 map() 的回调函数返回一个值。
13. 忽略let/const的块级作用域
var 声明的变量具有函数作用域,而 let 和 const 具有块级作用域。
if (true) {
var a = 1; // 错误: a 在块外部也可以访问
let b = 2; // 正确: b 只能在块内部访问
}
console.log(a); // 可以访问
console.log(b); // 报错
尽量使用 let 和 const 来声明变量,以避免作用域污染。
14. 使用未定义的变量
使用未声明的变量会抛出 ReferenceError 错误。
console.log(z); // 错误: 抛出 ReferenceError
let z = 10; // 正确: 先声明再使用
确保变量在使用之前已经声明。
15. 混用函数声明和表达式
在函数声明之前调用函数会报错,而函数表达式必须先定义再调用。
foo(); // 错误: ReferenceError
const foo = function() {}; // 正确: 先声明后调用
尽量先声明函数,然后再调用。
16. 错误使用Array.splice()
splice() 方法可以删除或添加数组元素,但是需要注意参数的含义。
let arr2 = [1, 2, 3];
arr2.splice(1, 1); // 删除索引 1 处的一个元素
arr2.splice(1, 0, 4); // 在索引 1 处添加元素 4
要理解 splice() 方法的参数含义,第一个参数是起始索引,第二个参数是删除的个数,后面的参数是要添加的元素。
17. 在回调函数中忘记return
在 filter() 等数组方法的回调函数中,如果没有 return,则会返回 undefined。
const filtered = nums.filter(num => { num > 1; }); // 错误: 未返回值
const filteredCorrect = nums.filter(num => num > 1); // 正确: 返回 boolean 值
确保回调函数返回一个值。
18. 使用var而不是let/const
var 声明的变量存在变量提升和作用域问题。
function varIssue() {
if (true) {
var x = 5;
}
console.log(x); // 错误: x 在块外部也可以访问
}
function varIssueCorrect() {
if (true) {
let x = 5; // 正确: x 只能在块内部访问
}
}
尽量使用 let 和 const 来声明变量,避免 var 的问题。
19. 错误地复制对象
直接赋值对象,只是复制了对象的引用,而不是复制对象本身。
const obj2 = { a: 1, b: 2 };
const copy = obj2; // 错误: 引用拷贝
const copyCorrect = { ...obj2 }; // 正确: 浅拷贝
要使用展开运算符 ... 或者 Object.assign() 等方法来进行浅拷贝,或者使用深拷贝方法来复制对象。
20. 忘记默认参数值
如果函数参数没有默认值,则在调用时没有传递参数会返回 undefined。
function greet(name) {
console.log('Hello, ${name}'); // 错误: undefined
}
function greetCorrect(name = 'Guest') {
console.log('Hello, ${name}'); // 正确: 默认参数
}
为函数参数设置默认值,可以避免因参数未定义而导致的错误。
总结
以上就是 20 个常见的 JavaScript 错误,我们通过分析错误原因,了解其背后的原理,并掌握正确的解决方法。避免这些常见的错误,编写出高质量的代码,是我们每个开发者都应该追求的目标。希望本文能够帮助大家在 JavaScript 开发道路上少走弯路,写出更健壮、可靠的程序。大家在实际开发中还遇到过哪些容易忽略的 JavaScript 错误呢?欢迎在评论区分享你的经验和看法。