如何避免 JavaScript 开发者常犯的 9 个错误?

开发 前端
JavaScript 是一种给网页添加功能和交互的脚本语言,对于使用不同编程语言的初学者来说很容易理解。

JavaScript 是一种给网页添加功能和交互的脚本语言,对于使用不同编程语言的初学者来说很容易理解。有了一些教程,你就可以马上开始使用它了。

但很多初学者都会犯一些常见的错误。在这篇文章中,我们将介绍 9 个常见的错误(或者说不好的实践)以及它们的解决方案,帮助你成为更好的 JavaScript 开发者。

[[345094]]

将赋值操作符(=)和相等操作符(==,===)混为一谈

正如名称所示,赋值操作符是用来给变量赋值的。开发者常常把它与相等操作符混淆。

举个例子:

  1. const name = "javascript"
  2.  
  3. if ((name = "nodejs")) { 
  4.  
  5. console.log(name); 
  6.  
  7.  
  8. // output - nodejs 

本例中,不是比较 name 变量和 nodejs 字符串,而是为 name 赋值 nodejs ,并将 nodejs 输出到控制台。

在 JavaScript 中,两个等号(==)和三个等号(===)是比较操作符。

对于上述代码,可以使用以下方法比较值:

  1. const name = "javascript"
  2.  
  3. if (name == "nodejs") { 
  4.  
  5. console.log(name); 
  6.  
  7.  
  8. // no output 
  9.  
  10. // OR 
  11.  
  12. if (name === "nodejs") { 
  13.  
  14. console.log(name); 
  15.  
  16.  
  17. // no output 

这两个比较操作符的区别是:两个等号执行宽松的比较,三个等号执行严格的比较。

大致比较时,只比较值。但严格地说,值和数据类型都是要比较的。

下面的代码更好地解释了这一点:

  1. const number = "1"
  2.  
  3. console.log(number == 1); 
  4.  
  5. // true 
  6.  
  7. console.log(number === 1); 
  8.  
  9. // false 

给变量 number 赋值 1 。如果将 number 用双等号与 1 进行比较,会返回 true,因为两个值都是 1。

然而,在用三个等号的情况下,因为每个值的数据类型不同,所以返回 false。

预期的回调是同步的

在 JavaScript 里,用回调方法处理异步操作。然而,Promises 和 async/await 是处理异步操作的首选方法,因为多次回调会导致回调地狱。

回调是不同步的。在延迟执行完成操作之后,它们作为一个函数被调用。

例如,全局 setTimeout 接收回调函数作为第一个参数,接收持续时间(毫秒)作为第二个参数:

  1. function callback() { 
  2.  
  3. console.log("I am the first"); 
  4.  
  5.  
  6. setTimeout(callback, 300); 
  7.  
  8. console.log("I am the last"); 
  9.  
  10. // output 
  11.  
  12. // I am the last 
  13.  
  14. // I am the first 

在 300ms 之后,调用回调函数。但是代码的其余部分在完成前运行,因此,最后一个 console.log 将首先运行。

开发者经常犯的一个错误就是误解了回调是同步的,比如,认为回调函数一个值用于其他操作。

错误在于:

  1. function addTwoNumbers() { 
  2.  
  3. let firstNumber = 5
  4.  
  5. let secondNumber; 
  6.  
  7. setTimeout(function () { 
  8.  
  9. secondNumber = 10
  10.  
  11. }, 200); 
  12.  
  13. console.log(firstNumber + secondNumber); 
  14.  
  15.  
  16. addTwoNumbers(); 
  17.  
  18. // NaN 

由于 secondNumber 不确定,所以输出 NaN 。运行 firstNumber+secondNumber 的时候,仍然没有定义 secondNumber ,因为 setTimeout 函数会在 200ms 之后执行回调。

最好的方法是在回调函数中执行剩余的代码:

  1. function addTwoNumbers() { 
  2.  
  3. let firstNumber = 5
  4.  
  5. let secondNumber; 
  6.  
  7. setTimeout(function () { 
  8.  
  9. secondNumber = 10
  10.  
  11. console.log(firstNumber + secondNumber); 
  12.  
  13. }, 200); 
  14.  
  15.  
  16. addTwoNumbers(); 
  17.  
  18. // 15 

this 指代错误

在 JavaScript 中,this 是一个常被误解的概念。在 JavaScript 使用 this,你需要理解它的作用是什么,这里的 this 跟其他语言中的 this 用法不同。

以下是关于 this 的常见错误的示例:

  1. const obj = { 
  2.  
  3. name: "JavaScript"
  4.  
  5. printName: function () { 
  6.  
  7. console.log(this.name); 
  8.  
  9. }, 
  10.  
  11. printNameIn2Secs: function () { 
  12.  
  13. setTimeout(function () { 
  14.  
  15. console.log(this.name); 
  16.  
  17. }, 2000); 
  18.  
  19. }, 
  20.  
  21. }; 
  22.  
  23. obj.printName(); 
  24.  
  25. // JavaScript 
  26.  
  27. obj.printNameIn2Secs(); 
  28.  
  29. // undefined 

第一个结果是 JavaScript ,因为 this.name 正确地指向对象的 name 属性。第二个结果是 undefined ,因为 this 未指代对象的属性(包括 name)。

原因在于 this 依赖于正在调用该函数的对象。每个函数都有一个 this 变量,但是它的指向由调用 this 的对象决定。

bj.printName() 的 this 直接指向 obj 。 obj.printNameIn2Secs 的 this 直接指向 obj 。然而,但是 this 在回调函数 setTimeout 中没有指向任何对象,因为没有任何对象调用它。

如果一个对象调用 setTimeout ,则执行 obj.setTimeout... 。因为没有对象调用这个函数,所以使用默认对象(即 window )。

window 上没有 name ,故返回 undefined 。

在 setTimeout 中保留 this 指代的最好方法是使用 bind 、 call 、 apply 或箭头功能(在 ES6 中引入)。不同于常规函数,箭头函数不创建自己的 this 。

所以,下面的代码会保留 this 指代:

  1. const obj = { 
  2.  
  3. name: "JavaScript"
  4.  
  5. printName: function () { 
  6.  
  7. console.log(this.name); 
  8.  
  9. }, 
  10.  
  11. printNameIn2Secs: function () { 
  12.  
  13. setTimeout(() => { 
  14.  
  15. console.log(this.name); 
  16.  
  17. }, 2000); 
  18.  
  19. }, 
  20.  
  21. }; 
  22.  
  23. obj.printName(); 
  24.  
  25. // JavaScript 
  26.  
  27. obj.printNameIn2Secs(); 
  28.  
  29. // JavaScript 

忽视对象的可变性

JavaScript 对象中的引用数据类型不像字符串、数字等原始数据类型。比如,在键值对对象中:

  1. const obj1 = { 
  2.  
  3. name: "JavaScript"
  4.  
  5. }; 
  6.  
  7. const obj2 = obj1; 
  8.  
  9. obj2.name = "programming"
  10.  
  11. console.log(obj1.name); 
  12.  
  13. // programming 

obj1 和 obj2 在内存中指向相同的地址。

在数组中:

  1. const arr1 = [234]; 
  2.  
  3. const arr2 = arr1; 
  4.  
  5. arr2[0] = "javascript"
  6.  
  7. console.log(arr1); 
  8.  
  9. // ['javascript', 3, 4] 

开发者经常犯的一个错误是忽略了 JavaScript 的这个特性,而这将导致意外的错误。

如果出现这种情况,访问原始属性的任何尝试都会返回 undefined 或者引发错误。

最好的方法是,当你想复制一个对象的时候,总是创建一个新的引用。为了达到这个目的,扩展运算符(在 ES6 中引入的 ... )就是一个完美的解决方案。

比如,在键值对对象中:

  1. const obj1 = { 
  2.  
  3. name: "JavaScript"
  4.  
  5. }; 
  6.  
  7. const obj2 = { ...obj1 }; 
  8.  
  9. console.log(obj2); 
  10.  
  11. // {name: 'JavaScript' } 
  12.  
  13. obj2.name = "programming"
  14.  
  15. console.log(obj.name); 
  16.  
  17. // 'JavaScript' 

在数组中:

  1. const arr1 = [234]; 
  2.  
  3. const arr2 = [...arr1]; 
  4.  
  5. console.log(arr2); 
  6.  
  7. // [2,3,4] 
  8.  
  9. arr2[0] = "javascript"
  10.  
  11. console.log(arr1); 
  12.  
  13. // [2, 3, 4] 

保存数组和对象至浏览器储存

使用 JavaScript 的时候,开发者可能希望利用 localStorage 来保存值。然而,一个常见的错误是直接将数组和对象保存在 localStorage 中。 localStorage 只接收字符串。

JavaScript 将对象转换成字符串以保来保存,其结果是对象保存为 [Object Object] ,数组保存为逗号分隔开的字符串。

比如:

  1. const obj = { name: "JavaScript" }; 
  2.  
  3. window.localStorage.setItem("test-object", obj); 
  4.  
  5. console.log(window.localStorage.getItem("test-object")); 
  6.  
  7. // [Object Object] 
  8.  
  9. const arr = ["JavaScript""programming"45]; 
  10.  
  11. window.localStorage.setItem("test-array", arr); 
  12.  
  13. console.log(window.localStorage.getItem("test-array")); 
  14.  
  15. // JavaScript, programming, 45 

在保存这些对象时,很难访问它们。例如,对于一个对象,通过 .name 访问它会导致错误。因为 [Object Object] 现在是一个字符串,而不包含 name 属性。

通过使用 JSON.stringify (将对象转换为字符串)和 JSON.parse (将字符串转换为对象),可以更好地保存本地存储对象和数组。通过这种方式可以轻松访问对象。

上述代码的正确版本为:

  1. const obj = { name: "JavaScript" }; 
  2.  
  3. window.localStorage.setItem("test-object", JSON.stringify(obj)); 
  4.  
  5. const objInStorage = window.localStorage.getItem("test-object"); 
  6.  
  7. console.log(JSON.parse(objInStorage)); 
  8.  
  9. // {name: 'JavaScript'} 
  10.  
  11. const arr = ["JavaScript""programming"45]; 
  12.  
  13. window.localStorage.setItem("test-array", JSON.stringify(arr)); 
  14.  
  15. const arrInStorage = window.localStorage.getItem("test-array"); 
  16.  
  17. console.log(JSON.parse(arrInStorage)); 
  18.  
  19. // JavaScript, programming, 45 

不使用默认值

为动态变量设置默认值是一个很好的预防意外错误的方法。这里有一个常见错误的例子:

  1. function addTwoNumbers(a, b) { 
  2.  
  3. console.log(a + b); 
  4.  
  5.  
  6. addTwoNumbers(); 
  7.  
  8. // NaN 

由于 a 为 undefined , b 也为 undefined ,因此结果为 NaN 。你可以使用默认值防止类似错误,比如:

  1. function addTwoNumbers(a, b) { 
  2.  
  3. if (!a) a = 0
  4.  
  5. if (!b) b = 0
  6.  
  7. console.log(a + b); 
  8.  
  9.  
  10. addTwoNumbers(); 
  11.  
  12. // 0 

此外,可以在 ES6 中这样使用默认值:

  1. function addTwoNumbers(a = 0, b = 0) { 
  2.  
  3. console.log(a + b); 
  4.  
  5.  
  6. addTwoNumbers(); 
  7.  
  8. // 0 

此示例虽小,但强调了默认值的重要性。

另外,如果没有提供期望,开发者可以提供一个错误或者警告信息。

变量命名错误

是的,开发者还是会犯这个错误。命名是困难的,但开发人员没有其他选择。注解和命名变量一样,都是编程的好习惯。

比如:

  1. function total(discount, p) { 
  2.  
  3. return p * discount 
  4.  

变量 discount 没问题,但是 p 或者 total 呢?是什么的 total ?最好是:

  1. function totalPrice(discount, price) { 
  2.  
  3. return discount * price 
  4.  

对变量进行适当的命名非常重要,因为在特定的时间和将来,可能有别的开发者使用这个代码库。

适当地命名变量会让贡献者很容易理解项目是如何运行的。

检查布尔值

  1. const isRaining = false 
  2.  
  3. if(isRaining) { 
  4.  
  5. console.log('It is raining'
  6.  
  7. else { 
  8.  
  9. console.log('It is not raining'
  10.  
  11.  
  12. // It is not raining 

上面的示例中是一种常见的检查 Boolean 值的方法,但是在测试某些值时还是出现了错误。

在 JavaScript 中,比较 0 和 false 会返回 true ,比较 1 和 true 会返回 true 。这就是说,如果 isRaining 是 1,那么它就是 true 。

这常在对象中出现错误,比如:

  1. const obj = { 
  2.  
  3. name: 'JavaScript'
  4.  
  5. number: 0 
  6.  
  7.  
  8. if(obj.number) { 
  9.  
  10. console.log('number property exists'
  11.  
  12. else { 
  13.  
  14. console.log('number property does not exist'
  15.  
  16.  
  17. // number property does not exist 

尽管存在 number 属性,但 obj.number 返回 0 ,这是一个假值,因此执行了 else 代码。

所以,除非你确定了要使用的值的范围,否则你应该测试布尔值和对象中的属性:

  1. if(a === false)... 
  2.  
  3. if(object.hasOwnProperty(property))... 

使人迷惑的添加和连接

在 JavaScript 中,加号( + )有两种功能:相加和连接。相加是针对数字,而连接是针对字符串。有些开发者经常误用这个操作符。

比如:

  1. const num1 = 30
  2.  
  3. const num2 = "20"
  4.  
  5. const num3 = 30
  6.  
  7. const word1 = "Java" 
  8.  
  9. const word2 = "Script" 
  10.  
  11. console.log(num1 + num2); 
  12.  
  13. // 3020 
  14.  
  15. console.log(num1 + num3); 
  16.  
  17. // 60 
  18.  
  19. console.log(word1 + word2); 
  20.  
  21. // JavaScript 

将字符串和数字相加时,JavaScript 会把数字转换成字符串。而数字相加时,则进行数学运算。

总结

除了上面罗列出的,肯定还有更多错误(小错误或大错误)。所以,你需要知道最新的语言发展动态。

学习和避免这些错误将有助于你构建更好、更可靠的 Web 应用程序和工具。 

 

责任编辑:张燕妮 来源: freeCodeCamp
相关推荐

2013-12-27 09:03:47

开发项目

2022-12-14 07:31:35

JavaScript错误关键字

2015-04-21 12:54:21

2014-10-09 09:29:25

AngularJS

2011-03-17 15:25:31

2011-03-16 09:33:45

数据库开发错误

2011-03-16 09:38:05

2023-03-10 09:00:49

Swift开发者工具

2009-04-29 11:20:24

.NET开发常见错误

2019-07-18 10:14:32

前端Javascript图表库

2011-04-18 12:55:04

JavaScript开发者

2020-05-21 18:38:49

JavaScript前端技术

2016-12-05 09:20:37

机器学习算法

2018-02-27 13:48:01

大数据管理者数据

2014-02-01 21:31:10

JavaScriptJS框架

2013-10-23 09:24:12

开发者功能

2013-07-10 15:29:56

移动应用开发移动应用避免产品被删除

2019-08-07 15:08:48

开发者技能工具

2020-11-20 20:49:49

Python开发代码

2017-11-02 15:42:32

开发错误代码
点赞
收藏

51CTO技术栈公众号