新聞中心
墨菲定律指出,任何可能出錯(cuò)的事情最終都會(huì)出錯(cuò)。這在編程世界中應(yīng)用得太好了。如果您創(chuàng)建一個(gè)應(yīng)用程序,您很可能會(huì)產(chǎn)生錯(cuò)誤和其他問(wèn)題。JavaScript中的錯(cuò)誤就是這樣一個(gè)常見(jiàn)問(wèn)題!

讓客戶(hù)滿意是我們工作的目標(biāo),不斷超越客戶(hù)的期望值來(lái)自于我們對(duì)這個(gè)行業(yè)的熱愛(ài)。我們立志把好的技術(shù)通過(guò)有效、簡(jiǎn)單的方式提供給客戶(hù),將通過(guò)不懈努力成為客戶(hù)在信息化領(lǐng)域值得信任、有價(jià)值的長(zhǎng)期合作伙伴,公司提供的服務(wù)項(xiàng)目有:域名申請(qǐng)、網(wǎng)頁(yè)空間、營(yíng)銷(xiāo)軟件、網(wǎng)站建設(shè)、市北網(wǎng)站維護(hù)、網(wǎng)站推廣。
軟件產(chǎn)品的成功取決于其創(chuàng)建者在傷害用戶(hù)之前解決這些問(wèn)題的能力。在所有編程語(yǔ)言中, JavaScript因其平均錯(cuò)誤處理設(shè)計(jì)而臭名昭著。
如果您正在構(gòu)建一個(gè)JavaScript應(yīng)用程序,那么您很有可能會(huì)在某一時(shí)刻弄亂數(shù)據(jù)類(lèi)型。如果不是這樣,那么您最終可能會(huì)將undefined替換為null或?qū)⑷忍?hào)運(yùn)算符 ( ===) 替換為雙等號(hào)運(yùn)算符 ( ==)。
犯錯(cuò)是人之常情。這就是為什么我們將向您展示您需要了解的有關(guān)處理JavaScript錯(cuò)誤的所有信息。
本文將引導(dǎo)您了解JavaScript中的基本錯(cuò)誤,并解釋您可能遇到的各種錯(cuò)誤。然后,您將學(xué)習(xí)如何識(shí)別和修復(fù)這些錯(cuò)誤。還有一些技巧可以在生產(chǎn)環(huán)境中有效地處理錯(cuò)誤。
什么是JavaScript錯(cuò)誤?
編程錯(cuò)誤是指程序無(wú)法正常運(yùn)行的情況。當(dāng)程序不知道如何處理手頭的工作時(shí),可能會(huì)發(fā)生這種情況,例如嘗試打開(kāi)不存在的文件或在沒(méi)有網(wǎng)絡(luò)連接的情況下訪問(wèn)基于Web的API端點(diǎn)時(shí)。
這些情況促使程序向用戶(hù)拋出錯(cuò)誤,說(shuō)明它不知道如何繼續(xù)。該程序收集盡可能多的有關(guān)錯(cuò)誤的信息,然后報(bào)告它無(wú)法繼續(xù)前進(jìn)。
聰明的程序員試圖預(yù)測(cè)和覆蓋這些場(chǎng)景,這樣用戶(hù)就不必獨(dú)立地找出像“404”這樣的技術(shù)錯(cuò)誤信息。相反,它們顯示了一條更容易理解的信息:“找不到該頁(yè)面?!?/p>
JavaScript中的錯(cuò)誤是在發(fā)生編程錯(cuò)誤時(shí)顯示的對(duì)象。這些對(duì)象包含有關(guān)錯(cuò)誤類(lèi)型、導(dǎo)致錯(cuò)誤的語(yǔ)句以及發(fā)生錯(cuò)誤時(shí)的堆棧跟蹤的大量信息。JavaScript還允許程序員創(chuàng)建自定義錯(cuò)誤,以便在調(diào)試問(wèn)題時(shí)提供額外信息。
錯(cuò)誤的屬性
現(xiàn)在JavaScript錯(cuò)誤的定義已經(jīng)很清楚了,是時(shí)候深入研究細(xì)節(jié)了。
JavaScript中的錯(cuò)誤帶有某些標(biāo)準(zhǔn)和自定義屬性,有助于理解錯(cuò)誤的原因和影響。默認(rèn)情況下,JavaScript中的錯(cuò)誤包含三個(gè)屬性:
此外,error還可以攜帶columnNumber、lineNumber、fileName等屬性,以更好地描述錯(cuò)誤。但是,這些屬性不是標(biāo)準(zhǔn)的,可能會(huì)出現(xiàn)在JavaScript應(yīng)用程序生成的每個(gè)錯(cuò)誤對(duì)象中,也可能不會(huì)出現(xiàn)。
了解堆棧跟蹤
堆棧跟蹤是發(fā)生異常或警告等事件時(shí)程序所在的方法調(diào)用列表。這是伴隨異常的示例堆棧跟蹤的樣子:
堆棧跟蹤示例
如您所見(jiàn),它首先打印錯(cuò)誤名稱(chēng)和消息,然后是被調(diào)用的方法列表。每個(gè)方法調(diào)用都說(shuō)明其源代碼的位置以及調(diào)用它的行。您可以使用這些數(shù)據(jù)瀏覽您的代碼庫(kù)并確定是哪段代碼導(dǎo)致了錯(cuò)誤。
此方法列表以堆疊方式排列。它顯示了您的異常首次引發(fā)的位置以及它如何通過(guò)堆疊的方法調(diào)用傳播。為異常實(shí)現(xiàn)捕獲不會(huì)讓它通過(guò)堆棧向上傳播并使您的程序崩潰。但是,您可能希望在某些情況下故意不捕獲致命錯(cuò)誤以使程序崩潰。
錯(cuò)誤與異常
大多數(shù)人通常將錯(cuò)誤和異常視為同一件事。但是,必須注意它們之間的細(xì)微但根本的區(qū)別。
異常是已拋出的錯(cuò)誤對(duì)象。
為了更好地理解這一點(diǎn),讓我們舉一個(gè)簡(jiǎn)單的例子。以下是如何在JavaScript中定義錯(cuò)誤:
const wrongTypeError = TypeError("Wrong type found, expected character")
這就是wrongTypeError對(duì)象變成異常的方式:
throw wrongTypeError
然而,大多數(shù)人傾向于使用在拋出錯(cuò)誤對(duì)象時(shí)定義錯(cuò)誤對(duì)象的簡(jiǎn)寫(xiě)形式:
throw TypeError("Wrong type found, expected character")
這是標(biāo)準(zhǔn)做法。然而,這也是開(kāi)發(fā)人員傾向于混淆異常和錯(cuò)誤的原因之一。因此,即使您使用速記來(lái)快速完成工作,了解基礎(chǔ)知識(shí)也至關(guān)重要。
JavaScript中的錯(cuò)誤類(lèi)型
JavaScript中有一系列預(yù)定義的錯(cuò)誤類(lèi)型。只要程序員沒(méi)有明確處理應(yīng)用程序中的錯(cuò)誤,它們就會(huì)由 JavaScript 運(yùn)行時(shí)自動(dòng)選擇和定義。
本節(jié)將引導(dǎo)您了解JavaScript中一些最常見(jiàn)的錯(cuò)誤類(lèi)型,并了解它們發(fā)生的時(shí)間和原因。
范圍錯(cuò)誤
當(dāng)變量設(shè)置為超出其合法值范圍時(shí),會(huì)引發(fā)RangeError。它通常發(fā)生在將值作為參數(shù)傳遞給函數(shù)時(shí),并且給定值不在函數(shù)參數(shù)的范圍內(nèi)。使用文檔記錄不佳的第三方庫(kù)時(shí),有時(shí)修復(fù)起來(lái)會(huì)很棘手,因?yàn)槟枰绤?shù)的可能值范圍才能傳遞正確的值。
RangeError發(fā)生的一些常見(jiàn)場(chǎng)景是:
- 試圖通過(guò)Array構(gòu)造函數(shù)創(chuàng)建一個(gè)非法長(zhǎng)度的數(shù)組。
- 將錯(cuò)誤值傳遞給數(shù)字方法,如
toExponential(),toPrecision(),toFixed()等。 - 將非法值傳遞給字符串函數(shù),例如
normalize().
參考錯(cuò)誤
當(dāng)您的代碼中的變量引用出現(xiàn)問(wèn)題時(shí),就會(huì)發(fā)生ReferenceError。您可能忘記在使用變量之前為其定義值,或者您可能試圖在代碼中使用不可訪問(wèn)的變量。在任何情況下,通過(guò)堆棧跟蹤提供了充足的信息來(lái)查找和修復(fù)有錯(cuò)誤的變量引用。
ReferenceErrors發(fā)生的一些常見(jiàn)原因是:
- 在變量名中打錯(cuò)字。
- 試圖訪問(wèn)其范圍之外的塊范圍變量。
- 在加載之前從外部庫(kù)引用全局變量(例如 $ from jQuery)。
語(yǔ)法錯(cuò)誤
這些錯(cuò)誤是最容易修復(fù)的錯(cuò)誤之一,因?yàn)樗鼈儽砻鞔a語(yǔ)法有錯(cuò)誤。由于JavaScript是一種解釋而不是編譯的腳本語(yǔ)言,因此當(dāng)應(yīng)用程序執(zhí)行包含錯(cuò)誤的腳本時(shí)會(huì)拋出這些腳本語(yǔ)言。在編譯語(yǔ)言的情況下,在編譯過(guò)程中會(huì)識(shí)別出此類(lèi)錯(cuò)誤。因此,在修復(fù)之前不會(huì)創(chuàng)建應(yīng)用程序二進(jìn)制文件。
可能發(fā)生SyntaxErrors的一些常見(jiàn)原因是:
- 缺少引號(hào)
- 缺少右括號(hào)
- 花括號(hào)或其他字符的不正確對(duì)齊
最好在IDE中使用linting工具在此類(lèi)錯(cuò)誤出現(xiàn)在瀏覽器之前為您識(shí)別這些錯(cuò)誤。
類(lèi)型錯(cuò)誤
TypeError是JavaScript應(yīng)用程序中最常見(jiàn)的錯(cuò)誤之一。當(dāng)某些值不是特定的預(yù)期類(lèi)型時(shí),會(huì)創(chuàng)建此錯(cuò)誤。發(fā)生時(shí)的一些常見(jiàn)情況是:
- 調(diào)用不是方法的對(duì)象。
- 試圖訪問(wèn)空對(duì)象或未定義對(duì)象的屬性
- 將字符串視為數(shù)字,反之亦然
發(fā)生TypeError的可能性還有很多。稍后我們將查看一些著名的實(shí)例并學(xué)習(xí)如何修復(fù)它們。
內(nèi)部錯(cuò)誤
當(dāng)JavaScript運(yùn)行時(shí)引擎中發(fā)生異常時(shí)使用InternalError類(lèi)型。它可能表示也可能不表示您的代碼存在問(wèn)題。
通常,InternalError僅在兩種情況下發(fā)生:
- 當(dāng)JavaScript運(yùn)行時(shí)的補(bǔ)丁或更新帶有引發(fā)異常的錯(cuò)誤時(shí)(這種情況很少發(fā)生)
- 當(dāng)你的代碼包含對(duì)JavaScript引擎來(lái)說(shuō)太大的實(shí)體時(shí)(例如太多的switch case、太大的數(shù)組初始化器、太多的遞歸)
解決此錯(cuò)誤的最合適方法是通過(guò)錯(cuò)誤消息確定原因,并在可能的情況下重構(gòu)您的應(yīng)用程序邏輯,以消除JavaScript引擎上的工作負(fù)載突然激增。
URI錯(cuò)誤
URIError發(fā)生在全局URI處理函數(shù)如decodeURIComponent被非法使用時(shí)。它通常表示傳遞給方法調(diào)用的參數(shù)不符合URI標(biāo)準(zhǔn),因此沒(méi)有被方法正確解析。
診斷這些錯(cuò)誤通常很容易,因?yàn)槟恍枰獧z查參數(shù)是否存在畸形。
評(píng)估錯(cuò)誤
當(dāng)函數(shù)eval()調(diào)用發(fā)生錯(cuò)誤時(shí)會(huì)發(fā)生EvalError 。eval()函數(shù)用于執(zhí)行存儲(chǔ)在字符串中的 JavaScript 代碼。但是,由于安全問(wèn)題,強(qiáng)烈建議不要使用eval()函數(shù),并且當(dāng)前的ECMAScript規(guī)范不再拋出EvalError類(lèi),因此存在此錯(cuò)誤類(lèi)型只是為了保持與舊版JavaScript代碼的向后兼容性。
如果您使用的是舊版本的JavaScript,則可能會(huì)遇到此錯(cuò)誤。無(wú)論如何,最好調(diào)查eval()函數(shù)調(diào)用中執(zhí)行的代碼是否有任何異常。
創(chuàng)建自定義錯(cuò)誤類(lèi)型
雖然JavaScript提供了足夠的錯(cuò)誤類(lèi)型列表來(lái)涵蓋大多數(shù)場(chǎng)景,但如果列表不滿足您的要求,您始終可以創(chuàng)建新的錯(cuò)誤類(lèi)型。這種靈活性的基礎(chǔ)在于JavaScript允許您使用throw命令逐字地拋出任何東西。
因此,從技術(shù)上講,這些聲明是完全合法的:
throw 8 throw "An error occurred"
但是,拋出原始數(shù)據(jù)類(lèi)型不會(huì)提供有關(guān)錯(cuò)誤的詳細(xì)信息,例如其類(lèi)型、名稱(chēng)或隨附的堆棧跟蹤。為了解決這個(gè)問(wèn)題并標(biāo)準(zhǔn)化錯(cuò)誤處理過(guò)程,我們提供了Error這個(gè)類(lèi)。也不鼓勵(lì)在拋出異常時(shí)使用原始數(shù)據(jù)類(lèi)型。
您可以擴(kuò)展Error類(lèi)以創(chuàng)建您的自定義錯(cuò)誤類(lèi)。以下是如何執(zhí)行此操作的基本示例:
class ValidationError extends Error {
constructor(message) {
super(message);
this.name = "ValidationError";
}
}
您可以通過(guò)以下方式使用它:
throw ValidationError("Property not found: name")
然后您可以使用instanceof關(guān)鍵字識(shí)別它:
try {
validateForm() // code that throws a ValidationError
} catch (e) {
if (e instanceof ValidationError)
// do something
else
// do something else
}
JavaScript中最常見(jiàn)的10個(gè)錯(cuò)誤
現(xiàn)在您已經(jīng)了解了常見(jiàn)的錯(cuò)誤類(lèi)型以及如何創(chuàng)建自定義錯(cuò)誤類(lèi)型,是時(shí)候看看您在編寫(xiě)JavaScript代碼時(shí)會(huì)遇到的一些最常見(jiàn)的錯(cuò)誤了。
1. Uncaught RangeError
在幾種不同的情況下,Google Chrome中會(huì)出現(xiàn)此錯(cuò)誤。首先,如果您調(diào)用遞歸函數(shù)并且它不會(huì)終止,則可能會(huì)發(fā)生這種情況。您可以在Chrome開(kāi)發(fā)者控制臺(tái)中自行查看:
帶有遞歸函數(shù)調(diào)用的RangeError示例
因此,要解決此類(lèi)錯(cuò)誤,請(qǐng)確保正確定義遞歸函數(shù)的邊界情況。發(fā)生此錯(cuò)誤的另一個(gè)原因是您傳遞的值超出了函數(shù)的參數(shù)范圍。這是一個(gè)例子:
帶有toExponential()調(diào)用的RangeError示例
錯(cuò)誤消息通常會(huì)指出您的代碼有什么問(wèn)題。一旦你做出改變,它就會(huì)得到解決。
toExponential() 函數(shù)調(diào)用的輸出
2. Uncaught TypeError: Cannot set property
當(dāng)您在未定義的引用上設(shè)置屬性時(shí)會(huì)發(fā)生此錯(cuò)誤。您可以使用此代碼重現(xiàn)該問(wèn)題:
var list list.count = 0
這是您將收到的輸出:
類(lèi)型錯(cuò)誤示例
要修復(fù)此錯(cuò)誤,請(qǐng)?jiān)谠L問(wèn)其屬性之前使用值初始化引用。以下是修復(fù)后的外觀:
如何修復(fù)類(lèi)型錯(cuò)誤
3. Uncaught TypeError: Cannot read property
這是JavaScript中最常出現(xiàn)的錯(cuò)誤之一。當(dāng)您嘗試讀取屬性或調(diào)用未定義對(duì)象的函數(shù)時(shí),會(huì)發(fā)生此錯(cuò)誤。您可以通過(guò)在Chrome開(kāi)發(fā)人員控制臺(tái)中運(yùn)行以下代碼來(lái)非常輕松地重現(xiàn)它:
var func func.call()
這是輸出:
帶有未定義函數(shù)的TypeError示例
未定義的對(duì)象是導(dǎo)致此錯(cuò)誤的眾多可能原因之一。此問(wèn)題的另一個(gè)突出原因可能是在呈現(xiàn) UI 時(shí)未正確初始化狀態(tài)。這是來(lái)自React應(yīng)用程序的真實(shí)示例:
import React, { useState, useEffect } from "react";
const CardsList = () => {
const [state, setState] = useState();
useEffect(() => {
setTimeout(() => setState({ items: ["Card 1", "Card 2"] }), 2000);
}, []);
return (
<>
{state.items.map((item) => (
該應(yīng)用程序從一個(gè)空狀態(tài)容器開(kāi)始,并在延遲2秒后提供一些項(xiàng)目。延遲用于模擬網(wǎng)絡(luò)調(diào)用。即使您的網(wǎng)絡(luò)速度非常快,您仍然會(huì)面臨輕微的延遲,因?yàn)樵摻M件將至少呈現(xiàn)一次。如果您嘗試運(yùn)行此應(yīng)用程序,您將收到以下錯(cuò)誤:
瀏覽器中的TypeError堆棧跟蹤
這是因?yàn)?,在渲染時(shí),狀態(tài)容器是未定義的;因此,它不存在任何財(cái)產(chǎn)items。修復(fù)這個(gè)錯(cuò)誤很容易。您只需要為狀態(tài)容器提供初始默認(rèn)值。
// ...
const [state, setState] = useState({items: []});
// ...
現(xiàn)在,在設(shè)置延遲之后,您的應(yīng)用程序?qū)@示類(lèi)似的輸出:
代碼輸出
代碼中的確切修復(fù)可能會(huì)有所不同,但這里的本質(zhì)是始終在使用變量之前正確初始化它們。
4. TypeError: ‘undefined’ is not an object
當(dāng)您嘗試訪問(wèn)未定義對(duì)象的屬性或調(diào)用未定義對(duì)象的方法時(shí),會(huì)在Safari中發(fā)生此錯(cuò)誤。您可以從上面運(yùn)行相同的代碼來(lái)自己重現(xiàn)錯(cuò)誤。
帶有未定義函數(shù)的TypeError示例
這個(gè)錯(cuò)誤的解決方法也是一樣的——確保你已經(jīng)正確地初始化了你的變量,并且在訪問(wèn)一個(gè)屬性或方法時(shí)它們不是未定義的。
5. TypeError: null is not an object
這又與前面的錯(cuò)誤相似。它發(fā)生在Safari上,這兩個(gè)錯(cuò)誤之間的唯一區(qū)別是,當(dāng)正在訪問(wèn)其屬性或方法的對(duì)象null不是undefined. 您可以通過(guò)運(yùn)行以下代碼來(lái)重現(xiàn)這一點(diǎn):
var func = null func.call()
這是您將收到的輸出:
帶有null函數(shù)的TypeError示例
因?yàn)?code>null是顯式設(shè)置為變量的值,而不是由JavaScript自動(dòng)分配的值。僅當(dāng)您嘗試訪問(wèn)null自己設(shè)置的變量時(shí),才會(huì)發(fā)生此錯(cuò)誤。因此,您需要重新訪問(wèn)您的代碼并檢查您編寫(xiě)的邏輯是否正確。
6. TypeError: Cannot read property ‘length’
當(dāng)您嘗試讀取null或undefined對(duì)象的長(zhǎng)度時(shí),Chrome中會(huì)出現(xiàn)此錯(cuò)誤。這個(gè)問(wèn)題的原因和前面的問(wèn)題類(lèi)似,但是在處理列表的時(shí)候出現(xiàn)的頻率比較高;因此值得特別提及。以下是重現(xiàn)問(wèn)題的方法:
帶有未定義對(duì)象的TypeError示例
但是,在較新版本的Chrome中,此錯(cuò)誤報(bào)告為Uncaught TypeError: Cannot read properties of undefined. 這是它現(xiàn)在的樣子:
在較新的Chrome版本上帶有未定義對(duì)象的TypeError示例
再次,修復(fù)是確保您嘗試訪問(wèn)其長(zhǎng)度的對(duì)象存在并且未設(shè)置為null.
7. TypeError: ‘undefined’ is not a function
當(dāng)您嘗試調(diào)用腳本中不存在的方法或該方法存在但無(wú)法在調(diào)用上下文中引用時(shí),會(huì)發(fā)生此錯(cuò)誤。這個(gè)錯(cuò)誤通常發(fā)生在谷歌瀏覽器中,您可以通過(guò)檢查拋出錯(cuò)誤的代碼行來(lái)解決它。如果您發(fā)現(xiàn)拼寫(xiě)錯(cuò)誤,請(qǐng)修復(fù)它并檢查它是否能解決您的問(wèn)題。
如果您在代碼中使用了自引用關(guān)鍵字this,如果this沒(méi)有適當(dāng)?shù)亟壎ǖ侥纳舷挛?,則可能會(huì)出現(xiàn)此錯(cuò)誤??紤]下面的代碼:
function showAlert() {
alert("message here")
}
document.addEventListener("click", () => {
this.showAlert();
})
如果執(zhí)行上述代碼,它將拋出我們討論過(guò)的錯(cuò)誤。之所以會(huì)發(fā)生這種情況,是因?yàn)樽鳛槭录陕?tīng)器傳遞的匿名函數(shù)正在document的上下文中執(zhí)行。
相比之下,showAlert函數(shù)是在window的上下文中定義的。
為了解決這個(gè)問(wèn)題,您必須通過(guò)將函數(shù)與bind()方法綁定來(lái)傳遞對(duì)函數(shù)的正確引用:
document.addEventListener("click", this.showAlert.bind(this))
8. ReferenceError: event is not defined
當(dāng)您嘗試訪問(wèn)未在調(diào)用范圍內(nèi)定義的引用時(shí),會(huì)發(fā)生此錯(cuò)誤。這通常發(fā)生在處理事件時(shí),因?yàn)樗鼈兘?jīng)常為您提供event回調(diào)函數(shù)中調(diào)用的引用。如果您忘記在函數(shù)的參數(shù)中定義事件參數(shù)或拼寫(xiě)錯(cuò)誤,則可能會(huì)發(fā)生此錯(cuò)誤。
在Internet Explorer或Google Chrome中可能不會(huì)發(fā)生此錯(cuò)誤(因?yàn)镮E提供了一個(gè)全局事件變量,并且Chrome會(huì)自動(dòng)將事件變量附加到處理程序),但它可能在Firefox中發(fā)生。所以建議留意這樣的小錯(cuò)誤。
9. TypeError: Assignment to constant variable
這是由于粗心造成的錯(cuò)誤。如果您嘗試將新值分配給常量變量,您將遇到這樣的結(jié)果:
帶有常量對(duì)象分配的TypeError示例
雖然現(xiàn)在看起來(lái)很容易修復(fù),但想象一下數(shù)百個(gè)這樣的變量聲明,其中一個(gè)被錯(cuò)誤地定義為const而不是let! 與PHP等其他腳本語(yǔ)言不同,在JavaScript中聲明常量和變量的風(fēng)格差別很小。因此,當(dāng)您遇到此錯(cuò)誤時(shí),建議首先檢查您的聲明。如果您忘記上述引用是一個(gè)常量并將其用作變量,您也可能會(huì)遇到此錯(cuò)誤。這表明您的應(yīng)用程序邏輯存在粗心或缺陷。嘗試解決此問(wèn)題時(shí),請(qǐng)務(wù)必檢查此項(xiàng)。
10.(unknown): Script error
當(dāng)?shù)谌侥_本向您的瀏覽器發(fā)送錯(cuò)誤時(shí),就會(huì)發(fā)生腳本錯(cuò)誤。此錯(cuò)誤后跟(未知),因?yàn)?/i>第三方腳本與您的應(yīng)用屬于不同的域。瀏覽器隱藏了其他細(xì)節(jié),以防止第三方腳本泄露敏感信息。
在不了解完整詳細(xì)信息的情況下,您無(wú)法解決此錯(cuò)誤。您可以執(zhí)行以下操作來(lái)獲取有關(guān)該錯(cuò)誤的更多信息:
一旦您可以訪問(wèn)錯(cuò)誤的詳細(xì)信息,您就可以著手解決問(wèn)題,這可能與第三方庫(kù)或網(wǎng)絡(luò)有關(guān)。
如何識(shí)別和防止JavaScript中的錯(cuò)誤
雖然上面討論的錯(cuò)誤是JavaScript中最常見(jiàn)和最常見(jiàn)的錯(cuò)誤,但您會(huì)遇到,僅僅依靠幾個(gè)示例是遠(yuǎn)遠(yuǎn)不夠的。在開(kāi)發(fā)JavaScript應(yīng)用程序時(shí),了解如何檢測(cè)和防止任何類(lèi)型的錯(cuò)誤至關(guān)重要。以下是如何處理JavaScript中的錯(cuò)誤。
手動(dòng)拋出和捕獲錯(cuò)誤
處理手動(dòng)或運(yùn)行時(shí)拋出的錯(cuò)誤的最基本方法是捕獲它們。與大多數(shù)其他語(yǔ)言一樣,JavaScript提供了一組關(guān)鍵字來(lái)處理錯(cuò)誤。在著手處理JavaScript應(yīng)用程序中的錯(cuò)誤之前,必須深入了解它們中的每一個(gè)。
throw
該集合的第一個(gè)也是最基本的關(guān)鍵字是throw. 很明顯,throw關(guān)鍵字用于拋出錯(cuò)誤以在JavaScript運(yùn)行時(shí)手動(dòng)創(chuàng)建異常。我們已經(jīng)在本文前面討論過(guò)這個(gè)問(wèn)題,這里是這個(gè)關(guān)鍵字意義的要點(diǎn):
- 你可以
throw做任何事情,包括數(shù)字、字符串和Error對(duì)象。 - 但是,不建議拋出諸如字符串和數(shù)字之類(lèi)的原始數(shù)據(jù)類(lèi)型,因?yàn)樗鼈儾粩y帶有關(guān)錯(cuò)誤的調(diào)試信息。
- 例子:
throw TypeError("Please provide a string")
try
try關(guān)鍵字用于指示代碼塊可能會(huì)引發(fā)異常。它的語(yǔ)法是:
try {
// error-prone code here
}
重要的是要注意,catch塊必須始終跟隨try塊才能有效地處理錯(cuò)誤。
catch
catch關(guān)鍵字用于創(chuàng)建一個(gè)catch塊。此代碼塊負(fù)責(zé)處理尾隨try塊捕獲的錯(cuò)誤。這是它的語(yǔ)法:
catch (exception) {
// code to handle the exception here
}
這就是你如何一起實(shí)現(xiàn)try和catch塊的方式:
try {
// business logic code
} catch (exception) {
// error handling code
}
與C++或Java不同,您不能將多個(gè)catch塊附加到JavaScript中的try塊。這意味著您不能這樣做:
try {
// business logic code
} catch (exception) {
if (exception instanceof TypeError) {
// do something
}
} catch (exception) {
if (exception instanceof RangeError) {
// do something
}
}
相反,您可以在單個(gè)catch塊中使用if...else語(yǔ)句或switch case語(yǔ)句來(lái)處理所有可能的錯(cuò)誤情況。它看起來(lái)像這樣:
try {
// business logic code
} catch (exception) {
if (exception instanceof TypeError) {
// do something
} else if (exception instanceof RangeError) {
// do something else
}
}
finally
finally關(guān)鍵字用于定義在處理錯(cuò)誤后運(yùn)行的代碼塊。該塊在try和catch塊之后執(zhí)行。
此外,無(wú)論其他兩個(gè)塊的結(jié)果如何,都會(huì)執(zhí)行finally塊。這意味著即使catch塊不能完全處理錯(cuò)誤或者catch塊中拋出錯(cuò)誤,解釋器也會(huì)在程序崩潰之前執(zhí)行finally塊中的代碼。
要被認(rèn)為是有效的,JavaScript中的try塊需要后跟catch或finally塊。如果沒(méi)有這些,解釋器將引發(fā)SyntaxError。因此,在處理錯(cuò)誤時(shí),請(qǐng)確保至少遵循您的try塊。
使用onerror()方法全局處理錯(cuò)誤
onerror()方法適用于所有HTML元素,用于處理它們可能發(fā)生的任何錯(cuò)誤。例如,如果img標(biāo)簽找不到指定URL的圖像,它會(huì)觸發(fā)其onerror方法以允許用戶(hù)處理錯(cuò)誤。
通常,您會(huì)在onerror調(diào)用中提供另一個(gè)圖像URL,以便img標(biāo)記回退到。這是您可以通過(guò)JavaScript執(zhí)行此操作的方法:
const image = document.querySelector("img")
image.onerror = (event) => {
console.log("Error occurred: " + event)
}
但是,您可以使用此功能為您的應(yīng)用創(chuàng)建全局錯(cuò)誤處理機(jī)制。以下是您的操作方法:
window.onerror = (event) => {
console.log("Error occurred: " + event)
}
使用此事件處理程序,您可以擺脫try...catch代碼中的多個(gè)塊,并集中您的應(yīng)用程序的錯(cuò)誤處理,類(lèi)似于事件處理。您可以將多個(gè)錯(cuò)誤處理程序附加到窗口,以維護(hù)SOLID設(shè)計(jì)原則中的單一責(zé)任原則。解釋器將循環(huán)遍歷所有處理程序,直到到達(dá)適當(dāng)?shù)奶幚沓绦颉?/p>
通過(guò)回調(diào)傳遞錯(cuò)誤
雖然簡(jiǎn)單和線性函數(shù)允許錯(cuò)誤處理保持簡(jiǎn)單,但回調(diào)會(huì)使事情復(fù)雜化。
考慮以下代碼:
const calculateCube = (number, callback) => {
setTimeout(() => {
const cube = number * number * number
callback(cube)
}, 1000)
}
const callback = result => console.log(result)
calculateCube(4, callback)
上面的函數(shù)演示了一個(gè)異步條件,其中一個(gè)函數(shù)需要一些時(shí)間來(lái)處理操作并稍后在回調(diào)的幫助下返回結(jié)果。
如果您嘗試在函數(shù)調(diào)用中輸入字符串而不是4,您將得到NaN結(jié)果。
這需要妥善處理。就是這樣:
const calculateCube = (number, callback) => {
setTimeout(() => {
if (typeof number !== "number")
throw new Error("Numeric argument is expected")
const cube = number * number * number
callback(cube)
}, 1000)
}
const callback = result => console.log(result)
try {
calculateCube(4, callback)
} catch (e) { console.log(e) }
這應(yīng)該可以理想地解決問(wèn)題。但是,如果您嘗試將字符串傳遞給函數(shù)調(diào)用,您將收到以下信息:
錯(cuò)誤參數(shù)的錯(cuò)誤示例
即使您在調(diào)用函數(shù)時(shí)實(shí)現(xiàn)了try-catch塊,它仍然表示錯(cuò)誤未捕獲。由于超時(shí)延遲,在執(zhí)行catch塊后拋出錯(cuò)誤。
這可能在網(wǎng)絡(luò)調(diào)用中很快發(fā)生,在這種情況下會(huì)出現(xiàn)意外延遲。您需要在開(kāi)發(fā)應(yīng)用程序時(shí)涵蓋此類(lèi)情況。
以下是在回調(diào)中正確處理錯(cuò)誤的方法:
const calculateCube = (number, callback) => {
setTimeout(() => {
if (typeof number !== "number") {
callback(new TypeError("Numeric argument is expected"))
return
}
const cube = number * number * number
callback(null, cube)
}, 2000)
}
const callback = (error, result) => {
if (error !== null) {
console.log(error)
return
}
console.log(result)
}
try {
calculateCube('hey', callback)
} catch (e) {
console.log(e)
}
現(xiàn)在,控制臺(tái)的輸出將是:
帶有非法參數(shù)的TypeError示例
這表明錯(cuò)誤已得到適當(dāng)處理。
處理Promise中的錯(cuò)誤
大多數(shù)人傾向于使用Promise來(lái)處理異步活動(dòng)。Promise還有另一個(gè)優(yōu)點(diǎn)——被拒絕的Promise不會(huì)終止你的腳本。但是,您仍然需要實(shí)現(xiàn)一個(gè)catch塊來(lái)處理Promise中的錯(cuò)誤。為了更好地理解這一點(diǎn),讓我們使用Promises重寫(xiě)calculateCube()函數(shù):
const delay = ms => new Promise(res => setTimeout(res, ms));
const calculateCube = async (number) => {
if (typeof number !== "number")
throw Error("Numeric argument is expected")
await delay(5000)
const cube = number * number * number
return cube
}
try {
calculateCube(4).then(r => console.log(r))
} catch (e) { console.log(e) }
前面代碼中的超時(shí)已被隔離到delay函數(shù)中以便理解。如果您嘗試輸入一個(gè)字符串而不是4,您獲得的輸出將類(lèi)似于以下內(nèi)容:
在Promise中帶有非法參數(shù)的TypeError示例
同樣,這是由于Promise在其他所有內(nèi)容完成執(zhí)行后引發(fā)錯(cuò)誤。這個(gè)問(wèn)題的解決方案很簡(jiǎn)單。只需像這樣向Promise鏈添加調(diào)用catch():
calculateCube("hey")
.then(r => console.log(r))
.catch(e => console.log(e))
現(xiàn)在輸出將是:
處理帶有非法參數(shù)的TypeError示例
您可以觀察到使用Promise處理錯(cuò)誤是多么容易。此外,您可以鏈接finally()塊和promise調(diào)用以添加將在錯(cuò)誤處理完成后運(yùn)行的代碼。
或者,您也可以使用傳統(tǒng)的try-catch-finally技術(shù)來(lái)處理Promise中的錯(cuò)誤。在這種情況下,您的promise調(diào)用如下所示:
try {
let result = await calculateCube("hey")
console.log(result)
} catch (e) {
console.log(e)
} finally {
console.log('Finally executed")
}
但是,這僅適用于異步函數(shù)。因此,在Promise中處理錯(cuò)誤的最優(yōu)選方式是鏈?zhǔn)竭B接catch和finally連接到Promise調(diào)用。
throw/catch vs onerror() vs Callbacks vs Promises:哪種方法更佳?
有四種方法可供您使用,您必須知道如何在任何給定的用例中選擇最合適的方法。以下是您可以自己決定的方法:
throw/catch
您將在大多數(shù)情況下使用此方法。確保在你的catch塊中為所有可能的錯(cuò)誤實(shí)現(xiàn)條件,如果你需要在try塊之后運(yùn)行一些內(nèi)存清理例程,請(qǐng)記住包含一個(gè)finally塊。
但是,太多的try/catch塊會(huì)使您的代碼難以維護(hù)。如果您發(fā)現(xiàn)自己處于這種情況,您可能希望通過(guò)全局處理程序或promise方法來(lái)處理錯(cuò)誤。
在異步try/catch塊和promise的catch()之間做出決定時(shí),建議使用異步try/catch塊,因?yàn)樗鼈儗⑹鼓拇a線性且易于調(diào)試。
onerror()
當(dāng)您知道您的應(yīng)用程序必須處理許多錯(cuò)誤并且它們可以很好地分散在整個(gè)代碼庫(kù)中時(shí),最好使用onerror()方法。onerror方法使您能夠處理錯(cuò)誤,就好像它們只是您的應(yīng)用程序處理的另一個(gè)事件一樣。您可以定義多個(gè)錯(cuò)誤處理程序并在初始呈現(xiàn)時(shí)將它們附加到應(yīng)用程序的窗口。
但是,您還必須記住,在錯(cuò)誤范圍較小的較小項(xiàng)目中設(shè)置onerror()方法可能會(huì)帶來(lái)不必要的挑戰(zhàn)。如果您確定您的應(yīng)用程序不會(huì)拋出太多錯(cuò)誤,那么傳統(tǒng)的throw/catch方法將最適合您。
Callbacks and Promises
回調(diào)和承諾中的錯(cuò)誤處理因代碼設(shè)計(jì)和結(jié)構(gòu)而異。但是,如果您在編寫(xiě)代碼之前在這兩者之間進(jìn)行選擇,最好使用Promise。
這是因?yàn)镻romise具有用于鏈接catch()和finally()塊以輕松處理錯(cuò)誤的內(nèi)置結(jié)構(gòu)。這種方法比定義附加參數(shù)/重用現(xiàn)有參數(shù)來(lái)處理錯(cuò)誤更容易和更清晰。
使用Git存儲(chǔ)庫(kù)跟蹤更改
由于代碼庫(kù)中的手動(dòng)錯(cuò)誤,經(jīng)常會(huì)出現(xiàn)許多錯(cuò)誤。在開(kāi)發(fā)或調(diào)試代碼時(shí),您最終可能會(huì)進(jìn)行不必要的更改,這可能會(huì)導(dǎo)致代碼庫(kù)中出現(xiàn)新的錯(cuò)誤。自動(dòng)化測(cè)試是在每次更改后檢查代碼的好方法。但是,它只能告訴您是否有問(wèn)題。如果你不經(jīng)常備份你的代碼,你最終會(huì)浪費(fèi)時(shí)間試圖修復(fù)一個(gè)以前運(yùn)行良好的函數(shù)或腳本。
這就是git發(fā)揮作用的地方。通過(guò)適當(dāng)?shù)奶峤徊呗裕梢允褂媚膅it歷史作為備份系統(tǒng)來(lái)查看您的代碼在開(kāi)發(fā)過(guò)程中的演變過(guò)程。您可以輕松地瀏覽舊提交并找出該函數(shù)的版本之前運(yùn)行良好,但在不相關(guān)的更改后拋出錯(cuò)誤。
然后,您可以恢復(fù)舊代碼或比較兩個(gè)版本以確定哪里出了問(wèn)題?,F(xiàn)代Web開(kāi)發(fā)工具(如GitHub Desktop或GitKraken)可幫助您并排可視化這些更改并快速找出錯(cuò)誤。
一個(gè)可以幫助您減少錯(cuò)誤的習(xí)慣是 在您對(duì)代碼進(jìn)行重大更改時(shí)運(yùn)行代碼審查。如果您在一個(gè)團(tuán)隊(duì)中工作,您可以創(chuàng)建一個(gè)拉取請(qǐng)求并讓團(tuán)隊(duì)成員徹底審查它。這將幫助您使用第二雙眼睛來(lái)發(fā)現(xiàn)您可能遺漏的任何錯(cuò)誤。
處理JavaScript錯(cuò)誤的最佳實(shí)踐
上述方法足以幫助您為下一個(gè)JavaScript應(yīng)用程序設(shè)計(jì)一個(gè)健壯的錯(cuò)誤處理方法。但是,最好在實(shí)施時(shí)記住一些事情,以充分利用您的防錯(cuò)功能。這里有一些提示可以幫助您。
1. 處理操作異常時(shí)使用自定義錯(cuò)誤
我們?cè)诒局改系那懊娼榻B了自定義錯(cuò)誤,讓您了解如何根據(jù)應(yīng)用程序的獨(dú)特情況自定義錯(cuò)誤處理。建議盡可能使用自定義錯(cuò)誤而不是泛型Error類(lèi),因?yàn)樗鼮檎{(diào)用環(huán)境提供了有關(guān)錯(cuò)誤的更多上下文信息。
最重要的是,自定義錯(cuò)誤允許您調(diào)整錯(cuò)誤在調(diào)用環(huán)境中的顯示方式。這意味著您可以根據(jù)需要選擇隱藏特定詳細(xì)信息或顯示有關(guān)錯(cuò)誤的其他信息。
您可以根據(jù)需要格式化錯(cuò)誤內(nèi)容。這使您可以更好地控制錯(cuò)誤的解釋和處理方式。
2.不要吞下任何例外
即使是最資深的開(kāi)發(fā)人員也經(jīng)常犯一個(gè)新手錯(cuò)誤——在他們的代碼中使用異常級(jí)別。
您可能會(huì)遇到有一段代碼可以選擇運(yùn)行的情況。如果它有效,那就太好了;如果沒(méi)有,你不需要做任何事情。
在這些情況下,通常很想將這段代碼放在try塊中,并在其上附加一個(gè)空的catch塊。但是,通過(guò)這樣做,您將使那段代碼保持開(kāi)放狀態(tài),從而導(dǎo)致任何類(lèi)型的錯(cuò)誤并逃脫懲罰。如果你有一個(gè)龐大的代碼庫(kù)和許多這樣糟糕的錯(cuò)誤管理結(jié)構(gòu)的實(shí)例,這可能會(huì)變得很危險(xiǎn)。
處理異常的最好方法是確定所有異常都將被處理的級(jí)別并將它們提高到那里。此級(jí)別可以是控制器(在MVC架構(gòu)應(yīng)用程序中)或中間件(在傳統(tǒng)的面向服務(wù)器的應(yīng)用程序中)。
通過(guò)這種方式,您將了解在哪里可以找到應(yīng)用程序中發(fā)生的所有錯(cuò)誤并選擇如何解決它們,即使這意味著不對(duì)它們做任何事情。
3. 對(duì)日志和錯(cuò)誤警報(bào)使用集中策略
記錄錯(cuò)誤通常是處理錯(cuò)誤的一個(gè)組成部分。那些未能制定集中策略來(lái)記錄錯(cuò)誤的人可能會(huì)錯(cuò)過(guò)有關(guān)其應(yīng)用程序使用情況的寶貴信息。
應(yīng)用程序的事件日志可以幫助您找出有關(guān)錯(cuò)誤的關(guān)鍵數(shù)據(jù)并幫助快速調(diào)試它們。如果您在應(yīng)用程序中設(shè)置了適當(dāng)?shù)木瘓?bào)機(jī)制,您可以在錯(cuò)誤到達(dá)大部分用戶(hù)群之前知道應(yīng)用程序何時(shí)發(fā)生錯(cuò)誤。
建議使用預(yù)先構(gòu)建的記錄器或創(chuàng)建一個(gè)以滿足您的需求。您可以配置此記錄器以根據(jù)其級(jí)別(警告、調(diào)試、信息等)處理錯(cuò)誤,并且一些記錄器甚至可以立即將日志發(fā)送到遠(yuǎn)程記錄服務(wù)器。通過(guò)這種方式,您可以觀察應(yīng)用程序的邏輯在活動(dòng)用戶(hù)中的執(zhí)行情況。
4. 適當(dāng)?shù)赝ㄖ脩?hù)錯(cuò)誤
在定義錯(cuò)誤處理策略時(shí)要牢記的另一個(gè)要點(diǎn)是牢記用戶(hù)。
所有干擾您的應(yīng)用程序正常運(yùn)行的錯(cuò)誤都必須向用戶(hù)顯示可見(jiàn)的警報(bào),以通知他們出現(xiàn)問(wèn)題,以便用戶(hù)可以嘗試制定解決方案。如果您知道錯(cuò)誤的快速修復(fù)方法,例如重試操作或注銷(xiāo)并重新登錄,請(qǐng)務(wù)必在警報(bào)中提及它以幫助實(shí)時(shí)修復(fù)用戶(hù)體驗(yàn)。
如果錯(cuò)誤不會(huì)對(duì)日常用戶(hù)體驗(yàn)造成任何干擾,您可以考慮抑制警報(bào)并將錯(cuò)誤記錄到遠(yuǎn)程服務(wù)器以供以后解決。
5. 實(shí)現(xiàn)一個(gè)中間件(Node.js)
Node.js環(huán)境支持中間件向服務(wù)器應(yīng)用程序添加功能。 您可以使用此功能為您的服務(wù)器創(chuàng)建錯(cuò)誤處理中間件。
使用中間件的最大好處是所有錯(cuò)誤都集中在一個(gè)地方處理。您可以輕松選擇啟用/禁用此設(shè)置以進(jìn)行測(cè)試。
以下是創(chuàng)建基本中間件的方法:
const logError = err => {
console.log("ERROR: " + String(err))
}
const errorLoggerMiddleware = (err, req, res, next) => {
logError(err)
next(err)
}
const returnErrorMiddleware = (err, req, res, next) => {
res.status(err.statusCode || 500)
.send(err.message)
}
module.exports = {
logError,
errorLoggerMiddleware,
returnErrorMiddleware
}
然后,您可以在您的應(yīng)用程序中使用此中間件,如下所示:
const { errorLoggerMiddleware, returnErrorMiddleware } = require('./errorMiddleware')
app.use(errorLoggerMiddleware)
app.use(returnErrorMiddleware)
您現(xiàn)在可以在中間件中定義自定義邏輯以適當(dāng)?shù)靥幚礤e(cuò)誤。您不再需要擔(dān)心在整個(gè)代碼庫(kù)中實(shí)現(xiàn)單個(gè)錯(cuò)誤處理結(jié)構(gòu)。
6. 重新啟動(dòng)您的應(yīng)用程序以處理程序員錯(cuò)誤(Node.js)
當(dāng)Node.js應(yīng)用程序遇到程序員錯(cuò)誤時(shí),它們可能不一定會(huì)拋出異常并嘗試關(guān)閉應(yīng)用程序。此類(lèi)錯(cuò)誤可能包括由程序員錯(cuò)誤引起的問(wèn)題,例如高CPU消耗、內(nèi)存膨脹或內(nèi)存泄漏。處理這些問(wèn)題的最佳方法是通過(guò)Node.js集群模式或PM2等獨(dú)特工具使應(yīng)用程序崩潰,從而優(yōu)雅地重新啟動(dòng)應(yīng)用程序。這可以確保應(yīng)用程序不會(huì)因用戶(hù)操作而崩潰,從而呈現(xiàn)糟糕的用戶(hù)體驗(yàn)。
7. 捕獲所有未捕獲的異常(Node.js)
您永遠(yuǎn)無(wú)法確定您已經(jīng)涵蓋了您的應(yīng)用程序中可能出現(xiàn)的所有錯(cuò)誤。因此,實(shí)施備用策略以從您的應(yīng)用程序中捕獲所有未捕獲的異常非常重要。
您可以這樣做:
process.on('uncaughtException', error => {
console.log("ERROR: " + String(error))
// other handling mechanisms
})
您還可以確定發(fā)生的錯(cuò)誤是標(biāo)準(zhǔn)異常還是自定義操作錯(cuò)誤。根據(jù)結(jié)果??,您可以退出進(jìn)程并重新啟動(dòng)它以避免意外行為。
8. 捕獲所有未處理的Promise Rejections (Node.js)
與您永遠(yuǎn)無(wú)法涵蓋所有??可能的異常類(lèi)似,您很有可能會(huì)錯(cuò)過(guò)處理所有可能的Promise Rejections。但是,與異常不同,Promise Rejection不會(huì)引發(fā)錯(cuò)誤。
因此,一個(gè)重要的Promise Rejection可能會(huì)作為警告而溜走,并使您的應(yīng)用程序面臨遇到意外行為的可能性。因此,實(shí)現(xiàn)一個(gè)回退機(jī)制來(lái)處理Promise Rejection是至關(guān)重要的。
您可以這樣做:
const promiseRejectionCallback = error => {
console.log("PROMISE REJECTED: " + String(error))
}
process.on('unhandledRejection', callback)
小結(jié)
與任何其他編程語(yǔ)言一樣,JavaScript中的錯(cuò)誤非常頻繁且自然。在某些情況下,您甚至可能需要故意拋出錯(cuò)誤以向用戶(hù)指示正確的響應(yīng)。因此,了解它們的解剖結(jié)構(gòu)和類(lèi)型非常重要。
此外,您需要配備正確的工具和技術(shù)來(lái)識(shí)別和防止錯(cuò)誤導(dǎo)致您的應(yīng)用程序崩潰。
在大多數(shù)情況下,對(duì)于所有類(lèi)型的JavaScript應(yīng)用程序來(lái)說(shuō),通過(guò)仔細(xì)執(zhí)行來(lái)處理錯(cuò)誤的可靠策略就足夠了。
文章標(biāo)題:處理JavaScript錯(cuò)誤的權(quán)威指南
文章來(lái)源:http://www.5511xx.com/article/dghhhgj.html


咨詢(xún)
建站咨詢(xún)
