如何避免 JavaScript 开发者常犯的 9 个错误?
共 7114字,需浏览 15分钟
·
2020-10-09 11:16
JavaScript 是一种给网页添加功能和交互的脚本语言,对于使用不同编程语言的初学者来说很容易理解。有了一些教程,你就可以马上开始使用它了。
但很多初学者都会犯一些常见的错误。在这篇文章中,我们将介绍 9 个常见的错误(或者说不好的实践)以及它们的解决方案,帮助你成为更好的 JavaScript 开发者。
将赋值操作符(=)和相等操作符(==,===)混为一谈
正如名称所示,赋值操作符是用来给变量赋值的。开发者常常把它与相等操作符混淆。
举个例子:
const name = "javascript";
if ((name = "nodejs")) {
console.log(name);
}
// output - nodejs
本例中,不是比较 name 变量和 nodejs
字符串,而是为 name 赋值 nodejs
,并将 nodejs
输出到控制台。
在 JavaScript 中,两个等号(==)和三个等号(===)是比较操作符。
对于上述代码,可以使用以下方法比较值:
const name = "javascript";
if (name == "nodejs") {
console.log(name);
}
// no output
// OR
if (name === "nodejs") {
console.log(name);
}
// no output
这两个比较操作符的区别是:两个等号执行宽松的比较,三个等号执行严格的比较。
大致比较时,只比较值。但严格地说,值和数据类型都是要比较的。
下面的代码更好地解释了这一点:
const number = "1";
console.log(number == 1);
// true
console.log(number === 1);
// false
给变量 number
赋值 1
。如果将 number
用双等号与 1
进行比较,会返回 true,因为两个值都是 1。
然而,在用三个等号的情况下,因为每个值的数据类型不同,所以返回 false。
预期的回调是同步的
在 JavaScript 里,用回调方法处理异步操作。然而,Promises 和 async/await 是处理异步操作的首选方法,因为多次回调会导致回调地狱。
回调是不同步的。在延迟执行完成操作之后,它们作为一个函数被调用。
例如,全局 setTimeout
接收回调函数作为第一个参数,接收持续时间(毫秒)作为第二个参数:
function callback() {
console.log("I am the first");
}
setTimeout(callback, 300);
console.log("I am the last");
// output
// I am the last
// I am the first
在 300ms 之后,调用回调函数。但是代码的其余部分在完成前运行,因此,最后一个 console.log 将首先运行。
开发者经常犯的一个错误就是误解了回调是同步的,比如,认为回调函数一个值用于其他操作。
错误在于:
function addTwoNumbers() {
let firstNumber = 5;
let secondNumber;
setTimeout(function () {
secondNumber = 10;
}, 200);
console.log(firstNumber + secondNumber);
}
addTwoNumbers();
// NaN
由于 secondNumber
不确定,所以输出 NaN
。运行 firstNumber+secondNumber
的时候,仍然没有定义 secondNumber
,因为 setTimeout
函数会在 200ms 之后执行回调。
最好的方法是在回调函数中执行剩余的代码:
function addTwoNumbers() {
let firstNumber = 5;
let secondNumber;
setTimeout(function () {
secondNumber = 10;
console.log(firstNumber + secondNumber);
}, 200);
}
addTwoNumbers();
// 15
this 指代错误
在 JavaScript 中,this 是一个常被误解的概念。在 JavaScript 使用 this,你需要理解它的作用是什么,这里的 this 跟其他语言中的 this 用法不同。
以下是关于 this 的常见错误的示例:
const obj = {
name: "JavaScript",
printName: function () {
console.log(this.name);
},
printNameIn2Secs: function () {
setTimeout(function () {
console.log(this.name);
}, 2000);
},
};
obj.printName();
// JavaScript
obj.printNameIn2Secs();
// 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
指代:
const obj = {
name: "JavaScript",
printName: function () {
console.log(this.name);
},
printNameIn2Secs: function () {
setTimeout(() => {
console.log(this.name);
}, 2000);
},
};
obj.printName();
// JavaScript
obj.printNameIn2Secs();
// JavaScript
忽视对象的可变性
JavaScript 对象中的引用数据类型不像字符串、数字等原始数据类型。比如,在键值对对象中:
const obj1 = {
name: "JavaScript",
};
const obj2 = obj1;
obj2.name = "programming";
console.log(obj1.name);
// programming
obj1
和 obj2
在内存中指向相同的地址。
在数组中:
const arr1 = [2, 3, 4];
const arr2 = arr1;
arr2[0] = "javascript";
console.log(arr1);
// ['javascript', 3, 4]
开发者经常犯的一个错误是忽略了 JavaScript 的这个特性,而这将导致意外的错误。
如果出现这种情况,访问原始属性的任何尝试都会返回 undefined
或者引发错误。
最好的方法是,当你想复制一个对象的时候,总是创建一个新的引用。为了达到这个目的,扩展运算符(在 ES6 中引入的...
)就是一个完美的解决方案。
比如,在键值对对象中:
const obj1 = {
name: "JavaScript",
};
const obj2 = { ...obj1 };
console.log(obj2);
// {name: 'JavaScript' }
obj2.name = "programming";
console.log(obj.name);
// 'JavaScript'
在数组中:
const arr1 = [2, 3, 4];
const arr2 = [...arr1];
console.log(arr2);
// [2,3,4]
arr2[0] = "javascript";
console.log(arr1);
// [2, 3, 4]
保存数组和对象至浏览器储存
使用 JavaScript 的时候,开发者可能希望利用 localStorage
来保存值。然而,一个常见的错误是直接将数组和对象保存在 localStorage
中。localStorage
只接收字符串。
JavaScript 将对象转换成字符串以保来保存,其结果是对象保存为 [Object Object]
,数组保存为逗号分隔开的字符串。
比如:
const obj = { name: "JavaScript" };
window.localStorage.setItem("test-object", obj);
console.log(window.localStorage.getItem("test-object"));
// [Object Object]
const arr = ["JavaScript", "programming", 45];
window.localStorage.setItem("test-array", arr);
console.log(window.localStorage.getItem("test-array"));
// JavaScript, programming, 45
在保存这些对象时,很难访问它们。例如,对于一个对象,通过 .name
访问它会导致错误。因为 [Object Object]
现在是一个字符串,而不包含 name
属性。
通过使用 JSON.stringify
(将对象转换为字符串)和 JSON.parse
(将字符串转换为对象),可以更好地保存本地存储对象和数组。通过这种方式可以轻松访问对象。
上述代码的正确版本为:
const obj = { name: "JavaScript" };
window.localStorage.setItem("test-object", JSON.stringify(obj));
const objInStorage = window.localStorage.getItem("test-object");
console.log(JSON.parse(objInStorage));
// {name: 'JavaScript'}
const arr = ["JavaScript", "programming", 45];
window.localStorage.setItem("test-array", JSON.stringify(arr));
const arrInStorage = window.localStorage.getItem("test-array");
console.log(JSON.parse(arrInStorage));
// JavaScript, programming, 45
不使用默认值
为动态变量设置默认值是一个很好的预防意外错误的方法。这里有一个常见错误的例子:
function addTwoNumbers(a, b) {
console.log(a + b);
}
addTwoNumbers();
// NaN
由于 a
为 undefined
,b
也为 undefined
,因此结果为 NaN
。你可以使用默认值防止类似错误,比如:
function addTwoNumbers(a, b) {
if (!a) a = 0;
if (!b) b = 0;
console.log(a + b);
}
addTwoNumbers();
// 0
此外,可以在 ES6 中这样使用默认值:
function addTwoNumbers(a = 0, b = 0) {
console.log(a + b);
}
addTwoNumbers();
// 0
此示例虽小,但强调了默认值的重要性。
另外,如果没有提供期望,开发者可以提供一个错误或者警告信息。
变量命名错误
是的,开发者还是会犯这个错误。命名是困难的,但开发人员没有其他选择。注解和命名变量一样,都是编程的好习惯。
比如:
function total(discount, p) {
return p * discount
}
变量 discount
没问题,但是 p
或者 total
呢?是什么的 total
?最好是:
function totalPrice(discount, price) {
return discount * price
}
对变量进行适当的命名非常重要,因为在特定的时间和将来,可能有别的开发者使用这个代码库。
适当地命名变量会让贡献者很容易理解项目是如何运行的。
检查布尔值
const isRaining = false
if(isRaining) {
console.log('It is raining')
} else {
console.log('It is not raining')
}
// It is not raining
上面的示例中是一种常见的检查 Boolean 值的方法,但是在测试某些值时还是出现了错误。
在 JavaScript 中,比较 0
和 false
会返回 true
,比较 1
和 true
会返回 true
。这就是说,如果 isRaining
是 1,那么它就是 true
。
这常在对象中出现错误,比如:
const obj = {
name: 'JavaScript',
number: 0
}
if(obj.number) {
console.log('number property exists')
} else {
console.log('number property does not exist')
}
// number property does not exist
尽管存在 number
属性,但 obj.number
返回 0
,这是一个假值,因此执行了 else
代码。
所以,除非你确定了要使用的值的范围,否则你应该测试布尔值和对象中的属性:
if(a === false)...
if(object.hasOwnProperty(property))...
使人迷惑的添加和连接
在 JavaScript 中,加号(+
)有两种功能:相加和连接。相加是针对数字,而连接是针对字符串。有些开发者经常误用这个操作符。
比如:
const num1 = 30;
const num2 = "20";
const num3 = 30;
const word1 = "Java"
const word2 = "Script"
console.log(num1 + num2);
// 3020
console.log(num1 + num3);
// 60
console.log(word1 + word2);
// JavaScript
将字符串和数字相加时,JavaScript 会把数字转换成字符串。而数字相加时,则进行数学运算。
总结
除了上面罗列出的,肯定还有更多错误(小错误或大错误)。所以,你需要知道最新的语言发展动态。
学习和避免这些错误将有助于你构建更好、更可靠的 Web 应用程序和工具。
原文链接:https://www.freecodecamp.org/news/nine-most-common-mistakes-developers-make-in-javascript/
作者:Dipto Karmakar
译者:Chengjun.L
扫码关注公众号,订阅更多精彩内容。