1.函数的返回值
如果你调用一个函数并得到的是undefined,那么你已经可能看到这个错误了,JavaScript中的函数会默认返回undefined,这意味这如果你没有使用return关键字进行返回,结果就会使undefined
const getValue = (a,b) =>{
a + b
}
替换成
const getValue = (a,b) =>{
return a + b
}
2.在加载DOM之前加载JavaScript
如果要在 JavaScript 中引用 DOM 元素,则该 DOM 元素必须已经加载到页面上。让我们看一个例子。
假设您有一些 HTML,它还导入了一个名为script.js
.注意 JavaScript 文件是在 id 的元素之前导入的
<body>
<script src="script.js"></script>
<h1 id="header">Hello world</h1>
</body>
然后,在 JavaScript 中,想要获取header
元素并更新其文本
const header = document.getElementById('header');
header.innerText = "Hello JavaScript!";
在这种情况下,你会得到一个错误:Cannot set properties of null (setting 'innerText')
. 这是因为 JavaScript 试图获取对尚未加载到页面上的 DOM 元素的获取。
为了解决这个问题,通常在加载完所有 HTML 元素后,在 HTML 底部导入 JavaScript。
<h1 id="header">Hello world</h1>
<script src="script.js"></script>
这样,JavaScript 文件在元素已经加载之后才被加载
3.重新分配常量变量
从 ES6(2015 年发布的 JavaScript 版本)开始,声明变量有两种主要方式:const
和let
. 这两个在很大程度上取代了var
现代 JavaScript 中的使用。
这两者之间的区别在于不能变更 const
声明的变量,而可以使用let
. 这是一段示例代码。
const count = 0;
for (let i = 0; i < 10; i++) {
count = count + i;
}
console.log(count);
这样会得到一个错误:Assignment to constant variable
,如果需要修改声明的变量,换成let即可
let count = 0;
for (let i = 0; i < 10; i++) {
count = count + i;
}
console.log(count);
4.误解变量作用域
对于新开发人员来说,变量作用域是一个棘手的概念,尤其是在JavaScript中。我在学习过程中看到的一个常见问题是,在函数内部定义变量,并期望能够在定义变量的函数外部访问该变量。
我看这是个很普遍的问题。让我们来看一个更具体的JavaScript示例。这又回到了const和let vs var的使用。我提到过var是一种更过时的声明变量的方式,但你仍然会在文档和源代码中遇到它。因此,理解作用域与它有何不同是很重要的。
例如,如果在for循环中使用var定义一个变量,那么在for循环之后也可以访问该变量。这里有一个简单的例子。
for (let i = 0; i < 10; i++) {
var count = 5;
}
console.log(count);
count的值在定义它的for循环之后可以访问。但是,这不适用于const和let变量。如果定义在for循环内部,它们只能在for循环内部访问。
for (let i = 0; i < 10; i++) {
const count = 5;
}
console.log(count); //error: count is not defined
5.命名不佳的变量
命名糟糕的变量会使代码的阅读和理解困难一百万倍。使用像thing1、thing2和anotherThing这样的名称并没有说明变量是什么。这使得我更难以帮助调试别人的代码。让我们看一个例子。
const arr = ["James", "Jess", "Lily", "Sevi"];
let str = "";
for(let i = 0; i < arr.length; i++){
const tmp = arr[i];
str += tmp[0];
}
console.log(str);
变量arr、str和tmp没有给出变量是什么以及代码在做什么的上下文。下面是一个使用命名约定的例子,它添加了更多的上下文。
const names = ["James", "Jess", "Lily", "Sevi"];
let retVal = "";
for(let i = 0; i < arr.length; i++){
const name = arr[i];
retVal += name[0];
}
console.log(retVal);
我有一个最简单的建议,就是将数组命名为它们所包含的信息类型的多元化版本。例如,名称数组应该被称为names。然后,可以将该数组中的单个项作为名称引用。我经常看到以单数形式命名的数组,这令人难以置信地困惑。
6.函数功能过多
这是一个非常重要的错误,但在早期非常常见。当开发人员了解JavaScript中的函数时,他们通常倾向于编写一个函数,然后将所需的所有代码都放入其中。但是,最好开始考虑如何将函数分解成更小的代码块,这样更容易阅读、组合等。
7.不必要的Else语句
经常被误解的是,函数内部的return语句实际上会停止该函数的执行。换句话说,在函数内部返回后,函数内部的其他代码不会运行。因此,我经常看到不必要的其他语句。这是一个例子。
const isOdd = (num) => {
if(num % 2 === 1) {
return true
}else {
return false;
}
}
因为if条件中已经有了return, else就不需要了。我们通过删除else条件来简化这段代码。
const isOdd = (num) => {
if(num % 2 === 1) {
return true
}
return false;
}
甚至可以更进一步,直接返回经过计算的表达式,因为num%2 === 1返回一个布尔值
const isOdd = (num) => {
return num % 2 === 1;
}
8.不是短路循环
短路是另一种改善for循环的方法。让我们写一个函数,其中你需要确定一个数字数组是否包含一个偶数。这里有一个你可能解决它的例子。
const hasEvenNumber = (numbersArr) => {
let retVal;
for(let i =0; i< numbersArr.length; i++){
if(numbersArr[i] % 2 === 0){
retVal = true;
}else {
retVal = false;
}
}
return retVal;
}
在本例中,我们对每个数字进行迭代,并根据数字是否是偶数相应地更新布尔值。不幸的是,有一个问题。假设第一个数是偶数,但下一个数是奇数。对于偶数,布尔值更新为true,对于奇数,则返回false。
这将给出不正确的答案,因为你只是想知道函数是否至少有一个偶数(它应该返回true)。在这种情况下,在你看到第一个偶数后,你就知道答案了。你不需要再看了。这就是短路发挥作用的地方。看到一个偶数后,返回。如果你从来没有看到过,在最后返回false。
const hasEvenNumber = (numbersArr) => {
for(let i =0; i< numbersArr.length; i++){
if(numbersArr[i] % 2 === 0){
return true;
}
}
return false;
}
这样,您的逻辑更加清晰,并且避免了对数组中的其他项进行不必要的迭代。
9.双等对三等
在JavaScript中,这是一个非常令人困惑的话题。首先,很重要的一点是,要知道double equals比较的是两个值,而不考虑它们的数据类型。另一方面,三重等于比较两个值,同时考虑它们的数据类型。
由于双等号没有考虑两个值的数据类型,JavaScript必须有某种方法来比较它们。为此,JavaScript会秘密地转换(将一种数据类型转换为另一种数据类型)每个值,以便比较它们。这意味着数字和字符串可以被认为是双相等的,而不是三重相等的,因为它们是不同的数据类型。
const jamesAge = "31";
const jessAge = 31;
console.log(jamesAge == jessAge); //equal
console.log(jamesAge === jessAge); //not equal
一般的建议:默认使用三重等号,除非有特定的原因使用双重等号。它通常更安全,并有助于避免意外结果。关于这些等式如何工作的更多细节,请查看MDN文档。
10.不正确的对象比较与原始比较
我看到的另一个类似的问题是试图比较基本类型和对象是否相等。JavaScript中有7个原始值
- number
- string
- bigint
- boolean
- undefined
- symbol
- null
其他所有内容都表示为对象,对象和原语的引用方式不同。在JavaScript中,基元是通过它们的值直接引用的。另一方面,对象更一般地引用内存中存储值的空间。这就导致了对象和原语的混淆。考虑一下这个例子。
const name1 = "James";
const name2 = "James";
console.log(name1 === name2);
由于这两个变量是具有相同值的原语,它们被认为是相等的,但是如果我们像这样比较具有相同name属性的两个对象会怎么样呢?
const person1 = {
name:"James"
}
const person2 = {
name:"James"
}
console.log(person1 === person2);
在这种情况下,这两个对象不被认为是相等的。这是因为这两个变量实际上引用了不同的“内存空间”。尽管它们看起来很像,但它们并不因此被认为是平等的。如果想更恰当地比较它们的相等性,可以直接比较它们的名称属性。
const person1 = {
name:"James"
}
const person2 = {
name:"James"
}
console.log(person1.name === person2.name);
11.无法读取未定义的属性
无论如何,初学者(和有经验的)开发人员忘记做的一件事是验证函数的输入参数。仅仅因为您期望接收到一个对象,并不意味着您真的会接收到。让我们看看这个打印对象name属性的函数。
const printNamedGreeting = (person) => {
console.log(person.name)
}
看起来很简单,但是如果有人调用这个函数但没有传递任何东西,会发生什么呢?你会得到一个不能读的属性名为undefined error。那么,如何改进这段代码呢?
您需要验证接收到的输入是否符合您的期望。在这种情况下,一个简单(虽然不完整)的解决方案是检查输入参数是否“假”。
const printNamedGreeting = (person) => {
if(!person){
return console.log("Invalid person object")
}
console.log(person.name)
}
在本例中,如果person参数为“false”,则会登出一个错误。否则,我们将继续注销name属性。
请记住,这是一种快速而简单的验证检查,但在实际应用程序中,您可能需要更深入的方法。您需要验证输入实际上是一个对象(例如,不是字符串),并且它还有一个name属性。
最后,在你说之前,是的,你可以用TypeScript…我知道!
12.数组突变
在JavaScript中,突变是一个有趣的概念。通常,当您对一个变量调用一个函数,然后以某种方式改变变量本身时,就会发生突变。例如,在数组上调用sort函数时,数组本身会发生变化。但是,如果在数组上调用map函数,原始数组将保持不变。它不是突变的。
const names = ["Jess", "James", "Sevi", "Lily"];
const copiedNames = [...names];
const sortedNames = names.sort();
console.log(names); //["James", "Jess", "Lily", "Sevi"]
console.log(copiedNames); //["Jess", "James", "Sevi", "Lily"]
console.log(sortedNames); //["James", "Jess", "Lily", "Sevi"]
在本例中,调用names.sort()将改变原来的名称数组,这意味着名称和sortedNames数组看起来是相同的。但是,copyednames数组不受影响,因为它是原始数组的真实副本。
但是,如果对数组调用map()函数,则原始名称数组不受影响。
const firstLettersArray = names.map( name => name[0]);
13.不理解异步代码
JavaScript的异步特性是初学者最难掌握的内容之一。这里并不是一个完整的异步JavaScript教程的地方,所以我将留给您一个最常见的初学者示例来介绍这个主题。这些日志语句将以什么顺序输出?
console.log("1");
setTimeout(() => {
console.log("2")
}, 0)
console.log("3");
如果你是新手,答案可能会让你大吃一惊。W3 Schools一直是我最喜欢的参考资料,所以这里是他们的异步JavaScript入门文档
14.不处理错误
许多新手教程的问题在于,它们很少涉及如何处理JavaScript中的错误。对于代码示例,您只能看到最好的情况,而不能看到“如果出现错误会怎样”的代码。这是可以理解的,因为在初学者教程中你只能涵盖这么多内容。但是,在某些时候,花一些时间学习如何适当地处理错误是很重要的。
同样,这可能不是关于错误处理的深入教程,所以我将留给您一条建议。在编写代码时,问问自己“如果这里出错了怎么办”。只要你在学习的时候保持这种想法,你就会处于一个很好的位置!
15.不格式化代码
这是另一个元例子,但它有很大的不同。与上面的变量命名部分类似,格式糟糕的代码非常难以阅读。开发人员习惯于以标准化和格式化的方式读取代码。因此,似乎不可能读取未格式化的代码。