27

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

 3 years ago
source link: http://mp.weixin.qq.com/s?__biz=MzI1NTcxOTQ1Nw%3D%3D&%3Bmid=2247493122&%3Bidx=1&%3Bsn=babd2055ad0bb9f3dccd4c069b18dba2
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

eyUFjy2.jpg!mobile

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 直接指向   objobj.printNameIn2Secs 的   this 直接指向   obj 。然而,但是   this 在回调函数   setTimeout 中没有指向任何对象,因为没有任何对象调用它。

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

window 上没有   name ,故返回   undefined

setTimeout 中保留   this 指代的最好方法是使用   bindcallapply 或箭头功能(在 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 为   undefinedb 也为   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

扫码关注公众号,订阅更多精彩内容。

ErIJ7n6.png!mobile

你点的每个赞,我都认真当成了喜欢


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK