如何避免 JavaScript 開(kāi)發(fā)者常犯的 9 個(gè)錯(cuò)誤?

JavaScript 是一種給網(wǎng)頁(yè)添加功能和交互的腳本語(yǔ)言,對(duì)于使用不同編程語(yǔ)言的初學(xué)者來(lái)說(shuō)很容易理解。有了一些教程,你就可以馬上開(kāi)始使用它了。
但很多初學(xué)者都會(huì)犯一些常見(jiàn)的錯(cuò)誤。在這篇文章中,我們將介紹 9 個(gè)常見(jiàn)的錯(cuò)誤(或者說(shuō)不好的實(shí)踐)以及它們的解決方案,幫助你成為更好的 JavaScript 開(kāi)發(fā)者。
將賦值操作符(=)和相等操作符(==,===)混為一談
正如名稱(chēng)所示,賦值操作符是用來(lái)給變量賦值的。開(kāi)發(fā)者常常把它與相等操作符混淆。
舉個(gè)例子:
const?name?=?"javascript";
if?((name?=?"nodejs"))?{
????console.log(name);
}
//?output?-?nodejs
本例中,不是比較 name 變量和 ?nodejs ?字符串,而是為 name 賦值 ?nodejs,并將 ?nodejs ?輸出到控制臺(tái)。
在 JavaScript 中,兩個(gè)等號(hào)(==)和三個(gè)等號(hào)(===)是比較操作符。
對(duì)于上述代碼,可以使用以下方法比較值:
const?name?=?"javascript";
if?(name?==?"nodejs")?{
????console.log(name);
}
//?no?output
//?OR
if?(name?===?"nodejs")?{
????console.log(name);
}
//?no?output
這兩個(gè)比較操作符的區(qū)別是:兩個(gè)等號(hào)執(zhí)行寬松的比較,三個(gè)等號(hào)執(zhí)行嚴(yán)格的比較。
大致比較時(shí),只比較值。但嚴(yán)格地說(shuō),值和數(shù)據(jù)類(lèi)型都是要比較的。
下面的代碼更好地解釋了這一點(diǎn):
const?number?=?"1";
console.log(number?==?1);
//?true
console.log(number?===?1);
//?false
給變量 number 賦值 1。如果將 number 用雙等號(hào)與 1 進(jìn)行比較,會(huì)返回 true,因?yàn)閮蓚€(gè)值都是 1。
然而,在用三個(gè)等號(hào)的情況下,因?yàn)槊總€(gè)值的數(shù)據(jù)類(lèi)型不同,所以返回 false。
預(yù)期的回調(diào)是同步的
在 JavaScript 里,用回調(diào)方法處理異步操作。然而,Promises 和 async/await 是處理異步操作的首選方法,因?yàn)槎啻位卣{(diào)會(huì)導(dǎo)致回調(diào)地獄。
回調(diào)是不同步的。在延遲執(zhí)行完成操作之后,它們作為一個(gè)函數(shù)被調(diào)用。
例如,全局 ?setTimeout ?接收回調(diào)函數(shù)作為第一個(gè)參數(shù),接收持續(xù)時(shí)間(毫秒)作為第二個(gè)參數(shù):
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 之后,調(diào)用回調(diào)函數(shù)。但是代碼的其余部分在完成前運(yùn)行,因此,最后一個(gè) console.log 將首先運(yùn)行。
開(kāi)發(fā)者經(jīng)常犯的一個(gè)錯(cuò)誤就是誤解了回調(diào)是同步的,比如,認(rèn)為回調(diào)函數(shù)一個(gè)值用于其他操作。
錯(cuò)誤在于:
function?addTwoNumbers()?{
????let?firstNumber?=?5;
????let?secondNumber;
????setTimeout(function?()?{
????????secondNumber?=?10;
????},?200);
????console.log(firstNumber?+?secondNumber);
}
addTwoNumbers();
//?NaN
由于 ?secondNumber ?不確定,所以輸出 ?NaN。運(yùn)行 ?firstNumber+secondNumber ?的時(shí)候,仍然沒(méi)有定義 ?secondNumber,因?yàn)??setTimeout ?函數(shù)會(huì)在 200ms 之后執(zhí)行回調(diào)。
最好的方法是在回調(diào)函數(shù)中執(zhí)行剩余的代碼:
function?addTwoNumbers()?{
????let?firstNumber?=?5;
????let?secondNumber;
????setTimeout(function?()?{
????????secondNumber?=?10;
????????console.log(firstNumber?+?secondNumber);
????},?200);
}
addTwoNumbers();
//?15
this 指代錯(cuò)誤
在 JavaScript 中,this 是一個(gè)常被誤解的概念。在 JavaScript 使用 this,你需要理解它的作用是什么,這里的 this 跟其他語(yǔ)言中的 this 用法不同。
以下是關(guān)于 this 的常見(jiàn)錯(cuò)誤的示例:
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
第一個(gè)結(jié)果是 ?JavaScript,因?yàn)??this.name ?正確地指向?qū)ο蟮?name 屬性。第二個(gè)結(jié)果是 ?undefined,因?yàn)??this 未指代對(duì)象的屬性(包括 name)。
原因在于 ?this ?依賴(lài)于正在調(diào)用該函數(shù)的對(duì)象。每個(gè)函數(shù)都有一個(gè) ?this ?變量,但是它的指向由調(diào)用 ?this ?的對(duì)象決定。
bj.printName() ?的 ?this直接指向 ?obj。obj.printNameIn2Secs ?的 ?this ?直接指向 ?obj。然而,但是 ?this 在回調(diào)函數(shù) ?setTimeout ?中沒(méi)有指向任何對(duì)象,因?yàn)闆](méi)有任何對(duì)象調(diào)用它。
如果一個(gè)對(duì)象調(diào)用 ?setTimeout,則執(zhí)行obj.setTimeout...。因?yàn)闆](méi)有對(duì)象調(diào)用這個(gè)函數(shù),所以使用默認(rèn)對(duì)象(即 ?window)。
window ?上沒(méi)有 ?name,故返回 ?undefined。
在 ?setTimeout ?中保留 ?this ?指代的最好方法是使用 ?bind、call、apply 或箭頭功能(在 ES6 中引入)。不同于常規(guī)函數(shù),箭頭函數(shù)不創(chuàng)建自己的 ?this。
所以,下面的代碼會(huì)保留 ?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
忽視對(duì)象的可變性
JavaScript 對(duì)象中的引用數(shù)據(jù)類(lèi)型不像字符串、數(shù)字等原始數(shù)據(jù)類(lèi)型。比如,在鍵值對(duì)對(duì)象中:
const?obj1?=?{
????name:?"JavaScript",
};
const?obj2?=?obj1;
obj2.name?=?"programming";
console.log(obj1.name);
//?programming
obj1 ?和 ?obj2 ?在內(nèi)存中指向相同的地址。
在數(shù)組中:
const?arr1?=?[2,?3,?4];
const?arr2?=?arr1;
arr2[0]?=?"javascript";
console.log(arr1);
//?['javascript',?3,?4]
開(kāi)發(fā)者經(jīng)常犯的一個(gè)錯(cuò)誤是忽略了 JavaScript 的這個(gè)特性,而這將導(dǎo)致意外的錯(cuò)誤。
如果出現(xiàn)這種情況,訪問(wèn)原始屬性的任何嘗試都會(huì)返回 ?undefined ?或者引發(fā)錯(cuò)誤。
最好的方法是,當(dāng)你想復(fù)制一個(gè)對(duì)象的時(shí)候,總是創(chuàng)建一個(gè)新的引用。為了達(dá)到這個(gè)目的,擴(kuò)展運(yùn)算符(在 ES6 中引入的...)就是一個(gè)完美的解決方案。
比如,在鍵值對(duì)對(duì)象中:
const?obj1?=?{
????name:?"JavaScript",
};
const?obj2?=?{?...obj1?};
console.log(obj2);
//?{name:?'JavaScript'?}
obj2.name?=?"programming";
console.log(obj.name);
//?'JavaScript'
在數(shù)組中:
const?arr1?=?[2,?3,?4];
const?arr2?=?[...arr1];
console.log(arr2);
//?[2,3,4]
arr2[0]?=?"javascript";
console.log(arr1);
//?[2,?3,?4]
保存數(shù)組和對(duì)象至瀏覽器儲(chǔ)存
使用 JavaScript 的時(shí)候,開(kāi)發(fā)者可能希望利用 ?localStorage ?來(lái)保存值。然而,一個(gè)常見(jiàn)的錯(cuò)誤是直接將數(shù)組和對(duì)象保存在 ?localStorage ?中。localStorage ?只接收字符串。
JavaScript 將對(duì)象轉(zhuǎn)換成字符串以保來(lái)保存,其結(jié)果是對(duì)象保存為 ?[Object Object],數(shù)組保存為逗號(hào)分隔開(kāi)的字符串。
比如:
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
在保存這些對(duì)象時(shí),很難訪問(wèn)它們。例如,對(duì)于一個(gè)對(duì)象,通過(guò) ?.name ?訪問(wèn)它會(huì)導(dǎo)致錯(cuò)誤。因?yàn)??[Object Object] ?現(xiàn)在是一個(gè)字符串,而不包含 ?name ?屬性。
通過(guò)使用 ?JSON.stringify(將對(duì)象轉(zhuǎn)換為字符串)和 ?JSON.parse(將字符串轉(zhuǎn)換為對(duì)象),可以更好地保存本地存儲(chǔ)對(duì)象和數(shù)組。通過(guò)這種方式可以輕松訪問(wèn)對(duì)象。
上述代碼的正確版本為:
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
不使用默認(rèn)值
為動(dòng)態(tài)變量設(shè)置默認(rèn)值是一個(gè)很好的預(yù)防意外錯(cuò)誤的方法。這里有一個(gè)常見(jiàn)錯(cuò)誤的例子:
function?addTwoNumbers(a,?b)?{
????console.log(a?+?b);
}
addTwoNumbers();
//?NaN
由于 ?a ?為 ?undefined,b 也為 ?undefined,因此結(jié)果為 ?NaN。你可以使用默認(rèn)值防止類(lèi)似錯(cuò)誤,比如:
function?addTwoNumbers(a,?b)?{
????if?(!a)?a?=?0;
????if?(!b)?b?=?0;
????console.log(a?+?b);
}
addTwoNumbers();
//?0
此外,可以在 ES6 中這樣使用默認(rèn)值:
function?addTwoNumbers(a?=?0,?b?=?0)?{
????console.log(a?+?b);
}
addTwoNumbers();
//?0
此示例雖小,但強(qiáng)調(diào)了默認(rèn)值的重要性。
另外,如果沒(méi)有提供期望,開(kāi)發(fā)者可以提供一個(gè)錯(cuò)誤或者警告信息。
變量命名錯(cuò)誤
是的,開(kāi)發(fā)者還是會(huì)犯這個(gè)錯(cuò)誤。命名是困難的,但開(kāi)發(fā)人員沒(méi)有其他選擇。注解和命名變量一樣,都是編程的好習(xí)慣。
比如:
function?total(discount,?p)?{
????return?p?*?discount
}
變量 ?discount ?沒(méi)問(wèn)題,但是 ?p ?或者 ?total ?呢?是什么的 ?total?最好是:
function?totalPrice(discount,?price)?{
????return?discount?*?price
}
對(duì)變量進(jìn)行適當(dāng)?shù)拿浅V匾?,因?yàn)樵谔囟ǖ臅r(shí)間和將來(lái),可能有別的開(kāi)發(fā)者使用這個(gè)代碼庫(kù)。
適當(dāng)?shù)孛兞繒?huì)讓貢獻(xiàn)者很容易理解項(xiàng)目是如何運(yùn)行的。
檢查布爾值
const?isRaining?=?false
if(isRaining)?{
????console.log('It?is?raining')
}?else?{
????console.log('It?is?not?raining')
}
//?It?is?not?raining
上面的示例中是一種常見(jiàn)的檢查 ?Boolean 值的方法,但是在測(cè)試某些值時(shí)還是出現(xiàn)了錯(cuò)誤。
在 JavaScript 中,比較 ?0 ?和 ?false ?會(huì)返回 ?true,比較 ?1 ?和 ?true ?會(huì)返回 ?true。這就是說(shuō),如果 ?isRaining ?是 1,那么它就是 ?true。
這常在對(duì)象中出現(xiàn)錯(cuò)誤,比如:
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,這是一個(gè)假值,因此執(zhí)行了 ?else ?代碼。
所以,除非你確定了要使用的值的范圍,否則你應(yīng)該測(cè)試布爾值和對(duì)象中的屬性:
if(a?===?false)...
if(object.hasOwnProperty(property))...
使人迷惑的添加和連接
在 JavaScript 中,加號(hào)(+)有兩種功能:相加和連接。相加是針對(duì)數(shù)字,而連接是針對(duì)字符串。有些開(kāi)發(fā)者經(jīng)常誤用這個(gè)操作符。
比如:
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
將字符串和數(shù)字相加時(shí),JavaScript 會(huì)把數(shù)字轉(zhuǎn)換成字符串。而數(shù)字相加時(shí),則進(jìn)行數(shù)學(xué)運(yùn)算。
總結(jié)
除了上面羅列出的,肯定還有更多錯(cuò)誤(小錯(cuò)誤或大錯(cuò)誤)。所以,你需要知道最新的語(yǔ)言發(fā)展動(dòng)態(tài)。
學(xué)習(xí)和避免這些錯(cuò)誤將有助于你構(gòu)建更好、更可靠的 Web 應(yīng)用程序和工具。
原文鏈接:https://www.freecodecamp.org/news/nine-most-common-mistakes-developers-make-in-javascript/
作者:Dipto Karmakar
譯者:Chengjun.L
掃碼關(guān)注公眾號(hào),訂閱更多精彩內(nèi)容。

