当心!这 11 种 JavaScript 写法让你的代码比*还臭!


JavaScript 是现代 Web 开发中不可或缺的一部分,但是它也有一些需要注意的坑。在这篇文章中,我们将介绍十种常见的 JavaScript
写法,帮助你避免一些常见的错误和陷阱。

1. 隐式类型转换

隐式类型转换指的是 JavaScript 在运行时自动将一个数据类型转换成另一个数据类型。例如,当你使用加号连接两个字符串时,JavaScript
会自动将它们转换成字符串并连接起来。虽然这看似方便,但它往往会引发一些问题,特别是对于没有经验的开发者来说。

let result = "3" + 4 + 5; // '345'  
let result2 = 3 + 4 + "5"; // '75'  

以上代码中,第一行输出结果是字符串 ‘345’,而第二行输出结果则是字符串 ‘75’。这是因为 JavaScript
在处理加号时会先把数字转换成字符串,然后再执行连接操作。因此,当你使用加号连接数字和字符串时,必须小心处理以避免意外的行为。

2. 使用 var 声明变量

在 ES6 之前,JavaScript 中唯一声明变量的方法是使用 var 关键字。但是 var
存在一些作用域问题,容易造成变量污染等问题。因此,最好使用 letconst 来声明变量,它们都是块级作用域的。

function example() {  
  var foo = "bar";  
  if (true) {  
    var foo = "baz";  
    console.log(foo); // 'baz'  
  }  
  console.log(foo); // 'baz'  
}  
  
function example2() {  
  let foo = "bar";  
  if (true) {  
    let foo = "baz";  
    console.log(foo); // 'baz'  
  }  
  console.log(foo); // 'bar'  
}  
  
example();  
example2();  

在上面的代码中,第一个例子使用 var 声明变量时,内层的 foo 覆盖了外层的 foo ,导致输出结果为两个都是字符串
‘baz’。而第二个例子使用 let 声明变量后,内层的 foo 只存在于内层作用域内,不会影响外层的 foo

另外一个例子就是下面这个面试经常遇到的问题了:

for (var i = 0; i < 5; i++) {  
  setTimeout(function() {  
    console.log(i);  
  }, 1000);  
}  
// 输出 5, 5, 5, 5, 5  

这是因为 setTimeout 的回调函数是异步执行的,而当它们执行时,i 的值已经变成了 5,可以使用 let 或闭包解决这个问题。

3. 频繁操作 DOM

直接操作 DOM 是一种非常低效的方式,因为这样做会导致页面反复重新渲染,从而降低性能和用户体验。相反,应该采用更高效的方式来操作
DOM,比如减少DOM查询次数、使用虚拟 DOM 技术等。

// 不推荐  
document.getElementById('my-button').addEventListener('click', function() {  
  document.getElementById('my-text').innerText = 'Hello, world!';  
});  
  
// 推荐  
const myText = document.getElementById('my-text');  
document.getElementById('my-button').addEventListener('click', function() {  
  myText.innerText = 'Hello, world!';  
});  

在上面的代码中,第一个例子每次点击按钮时都会重新查询 DOM 并更新它,而第二个例子只查询一次 DOM,然后更新元素的文本内容。

4. 修改原型对象

修改原型对象可能会导致一些意外的问题。例如:

Array.prototype.sum = function() {  
  return this.reduce(function(a, b) {  
    return a + b;  
  }, 0);  
};  
  
var arr = [1, 2, 3];  
console.log(arr.sum()); // 6  

虽然上述代码可以正常工作,但是它可能会在其他部分产生意外的影响。为了避免这个问题,我们应该尽量避免修改原型对象。

5. 使用全局变量

在 JavaScript 中,全局变量是一种常见的变量类型,但它们可能会导致命名冲突和其他问题。因此,最好避免使用全局变量,并采用模块化方式来组织代码。

// 不推荐  
function foo() {  
  globalVar = "bar";  
}  
  
// 推荐  
const myModule = (function() {  
  const localVar = "baz";  
  
  function foo() {  
    // do something with localVar  
  }  
  
  return {  
    foo: foo  
  };  
})();  

在上面的代码中,第一个例子使用了全局变量 globalVar ,这样就有可能会被其他代码覆盖。而第二个例子使用了
IIFE(立即执行函数表达式)来创建一个模块,其中的变量只在该模块内部可见,不会影响到其他代码。

6. 忽略分号

JavaScript 允许在语句结尾省略分号,但这往往会导致一些错误,特别是当代码压缩或合并时。因此,建议始终在语句结尾加上分号,以避免潜在的问题。

// 不推荐  
let x = 1  
let y = 2  
  
// 推荐  
let x = 1;  
let y = 2;  

在上面的代码中,第一个例子省略了分号,可能会导致一些意外行为。而第二个例子则明确地加上了分号,更加规范。

7. 使用 for-in 循环

for-in 循环是一种遍历对象属性的方式,但它有一些缺陷。因为它不仅遍历对象自身的属性,还会遍历从原型链继承来的属性,这可能会导致一些意外的结果。

const person = {  
  name: "Alice",  
  age: 30,  
  gender: "female"  
};  
  
// 不推荐  
for (const key in person) {  
  console.log(person[key]);  
}  
  
// 推荐  
Object.keys(person).forEach(key => console.log(person[key]));  

在上面的代码中,第一个例子使用了 for-in 循环遍历对象属性,而第二个例子则使用了 Object.keys()
方法来获取对象自身的属性名,并配合 forEach() 方法进行遍历。

8. 比较 NaN

NaN 是一种特殊的数值类型,表示 Not-a-Number(非数字)。但是,由于 NaN 与任何值都不相等,因此在比较时需要使用 isNaN() 函数来判断。

// 不推荐  
if (x == NaN) {  
  // do something  
}  
  
// 推荐  
if (isNaN(x)) {  
  // do something  
}  

在上面的代码中,第一个例子错误地使用了相等运算符来比较 NaN ,而第二个例子则使用了 isNaN() 函数进行判断。

9. 使用 eval()

eval() 是一种执行字符串代码的方式,但它往往会导致安全问题和性能问题。因此,最好避免在生产环境中使用 eval()

// 不推荐  
eval("console.log('Hello, world!')");  
  
// 推荐  
const code = "console.log('Hello, world!')";  
Function(code)();  

在上面的代码中,第一个例子使用了 eval() 函数来执行字符串代码,而第二个例子则使用了 Function()
函数来创建一个新函数并执行它。

10. 忽略错误处理

忽略错误处理是一个常见的错误,因为它可能会导致代码崩溃。例如:

try {  
  // some code  
} catch (e) {}  

尽管这个抛出异常的代码可能无关紧要,但是如果没有正确处理异常,就可能在开发或运行时期间导致问题。

为了避免这个问题,我们应该始终正确处理异常,例如通过日志记录或显示用户友好的错误信息。

11. 将函数作为参数传递时忘记绑定 this

将函数作为参数传递时,需要注意绑定 this 的值。例如:

const obj = {  
  name: 'Alice',  
  greet: function() {  
    console.log('Hello, ' + this.name);  
  }  
};  
setTimeout(obj.greet, 1000); // Hello, undefined  

正确的写法应该是使用 bind 方法:

const obj = {  
  name: 'Alice',  
  greet: function() {  
    console.log('Hello, ' + this.name);  
  }  
};  
setTimeout(obj.greet.bind(obj), 1000); // Hello, Alice  

结语

JavaScript 是现代 Web 开发中不可或缺的一部分,它为我们提供了丰富的功能和灵活性,在编写 JavaScript
代码时,我们应该尽量保持代码的可读性、简洁性以及可维护性,以此来构建出色的 Web 应用程序。