核心要點
軟件開發中,墨菲定律同樣適用:任何可能出錯的事情,都會出錯。對於非簡單的程序,問題不是是否會出錯,而是何時會出錯。標準不兼容、不支持的功能和瀏覽器特性只是 web 開發人員面臨的潛在問題的幾個來源。鑑於可能出現的所有問題,JavaScript 卻擁有一個令人驚訝的簡單錯誤處理方法——它只是放棄並默默失敗。至少,這是用戶看到的行為。實際上,幕後發生了很多事情。
當 JavaScript 語句產生錯誤時,據說它拋出了一個異常。 JavaScript 解釋器不會繼續執行下一條語句,而是檢查異常處理代碼。如果沒有異常處理程序,則程序將從拋出異常的任何函數返回。這將對調用堆棧上的每個函數重複進行,直到找到異常處理程序或到達頂級函數,導致程序終止。
錯誤對象
發生異常時,會創建一個表示錯誤的對象並將其拋出。 JavaScript 語言定義了七種類型的內置錯誤對象。這些錯誤類型是異常處理的基礎。下面詳細描述每種錯誤類型。
“Error” 類型用於表示通用異常。此異常類型最常用於實現用戶定義的異常。本文稍後將重新討論創建用戶定義異常的主題。 “Error” 對象通過調用其構造函數來實例化,如下例所示:
<code class="language-javascript">var error = new Error("error message");</code>
“Error” 對象包含兩個屬性,“name” 和“message”。 “name” 屬性指定異常類型(在本例中為“Error”)。 “message” 屬性提供對異常的更詳細描述。 “message” 從傳遞給異常構造函數的字符串中獲取其值。其餘異常類型表示更具體的錯誤類型,但它們的使用方式與通用“Error” 類型相同。
當數字超出指定範圍時,會生成“RangeError” 異常。例如,JavaScript 數字具有 toFixed() 方法,該方法採用“digits” 參數,表示小數點後出現的數字位數。此參數應在 0 到 20 之間(儘管某些瀏覽器支持更寬的範圍)。如果“digits” 的值超出此範圍,則會拋出“RangeError”。以下示例顯示了這種情況。
<code class="language-javascript">var error = new Error("error message");</code>
當訪問不存在的變量時,會拋出“ReferenceError” 異常。這些異常通常發生在拼寫錯誤的現有變量名時。在下面的示例中,當訪問“bar”時會發生“ReferenceError”。請注意,此示例假設在嘗試遞增操作時,“bar” 不存在於任何活動作用域中。
<code class="language-javascript">var pi = 3.14159; pi.toFixed(100000); // RangeError</code>
當違反 JavaScript 語言規則時,會拋出“SyntaxError”。熟悉 C 和 Java 等語言的開發人員習慣於在編譯過程中遇到語法錯誤。但是,由於 JavaScript 是一種解釋型語言,因此直到執行代碼時才會識別語法錯誤。語法錯誤是獨一無二的,因為它們是唯一一種無法從中恢復的異常類型。以下示例會生成語法錯誤,因為“if” 語句缺少右花括號。
<code class="language-javascript">function foo() { bar++; // ReferenceError }</code>
當值不是預期類型時,會發生“TypeError” 異常。嘗試調用不存在的對象方法是此類異常的常見原因。以下示例創建一個名為“foo”的空對象,然後嘗試調用其 bar() 方法。由於未定義 bar(),因此在嘗試調用時會拋出“TypeError”。
<code class="language-javascript">if (foo) { // SyntaxError // 缺少右花括号 }</code>
當 encodeURI() 和 decodeURI() 等方法遇到格式錯誤的 URI 時,會拋出“URIError” 異常。以下示例在嘗試解碼字符串“%”時會生成“URIError”。 “%” 字符表示 URI 轉義序列的開頭。由於在此示例中“%”之後沒有任何內容,因此該字符串是無效的轉義序列,因此是格式錯誤的 URI 組件。
<code class="language-javascript">var foo = {}; foo.bar(); // TypeError</code>
當 eval() 函數使用不當時,會拋出“EvalError” 異常。在最新版本的 EcmaScript 標準中未使用這些異常。但是,為了保持與舊版本標準的向後兼容性,它們仍然受支持。
處理異常
既然我們知道了異常是什麼,那麼現在就該學習如何阻止它們導致程序崩潰。 JavaScript 通過“try…catch…finally”語句處理異常。下面顯示了一個通用的示例語句。
<code class="language-javascript">decodeURIComponent("%"); // URIError</code>
“try…catch…finally” 語句的第一部分是“try” 子句。 “try” 子句是必需的,用於分隔程序員懷疑可能產生異常的代碼塊。 “try” 子句後面必須跟有一個或兩個“catch” 和“finally” 子句。
“catch” 子句
“try…catch…finally” 的第二部分是“catch” 子句。 “catch” 子句是一段僅在“try” 子句中發生異常時才執行的代碼塊。儘管“catch” 子句是可選的,但如果沒有它,就不可能真正處理異常。這是因為“catch” 子句阻止異常在調用堆棧中傳播,從而允許程序恢復。如果“try” 塊中發生異常,則控制權會立即傳遞給“catch” 子句。發生的異常也會傳遞到“catch” 塊以進行處理。以下示例顯示瞭如何使用“catch” 子句來處理“ReferenceError”。請注意,“ReferenceError” 對象可通過“exception” 變量在“catch” 子句中使用。
<code class="language-javascript">var error = new Error("error message");</code>
複雜的應用程序可以生成各種異常。在這種情況下,可以使用“instanceof” 運算符來區分各種類型的異常。在以下示例中,假設“try” 子句可以生成幾種類型的異常。相應的“catch” 子句使用“instanceof” 將“TypeError” 和“ReferenceError” 異常與所有其他類型的錯誤分開處理。
<code class="language-javascript">var pi = 3.14159; pi.toFixed(100000); // RangeError</code>
“finally” 子句
“try…catch…finally” 語句的最後一個組件是可選的“finally” 子句。 “finally” 子句是一段在“try” 和“catch” 子句之後執行的代碼塊,無論是否出現錯誤。 “finally” 子句對於包含無論如何都需要執行的清理代碼(關閉文件等)很有用。請注意,即使發生未捕獲的異常,“finally” 子句也會執行。在這種情況下,“finally” 子句會執行,然後拋出的異常會正常繼續。
關於“finally” 子句的一個有趣的說明是,即使“try” 或“catch” 子句執行“return” 語句,它也會執行。例如,以下函數返回 false,因為“finally” 子句是最後執行的內容。
<code class="language-javascript">function foo() { bar++; // ReferenceError }</code>
拋出異常
JavaScript 允許程序員通過名為“throw” 的語句拋出自己的異常。這個概念對於沒有經驗的開發人員來說可能有點令人困惑。畢竟,開發人員努力編寫沒有錯誤的代碼,但是“throw” 語句卻故意引入錯誤。但是,故意拋出異常實際上可以使代碼更易於調試和維護。例如,通過創建有意義的錯誤消息,可以更容易地識別和解決問題。
下面顯示了“throw” 語句的幾個示例。對可以作為異常拋出的數據類型沒有限制。對可以捕獲和拋出相同數據的次數也沒有限制。換句話說,可以拋出異常,捕獲異常,然後再次拋出異常。
<code class="language-javascript">if (foo) { // SyntaxError // 缺少右花括号 }</code>
雖然“throw” 語句可以與任何數據類型一起使用,但使用內置異常類型具有一定的優勢。例如,Firefox 通過添加調試信息(例如發生異常的文件名和行號)來對這些對象進行特殊處理。
例如,假設您的應用程序中的某個地方發生了除法運算。由於可能出現除以零的情況,因此除法可能很麻煩。在 JavaScript 中,此類操作會導致“NaN”。這可能導致難以調試的令人困惑的結果。如果應用程序大聲抱怨除以零,情況會簡單得多。以下“if” 語句通過拋出異常來實現這一點。
<code class="language-javascript">var error = new Error("error message");</code>
當然,使用“RangeError” 可能更合適,如下所示:
<code class="language-javascript">var pi = 3.14159; pi.toFixed(100000); // RangeError</code>
自定義異常對象
我們剛剛學習瞭如何使用內置異常類型生成自定義錯誤消息。但是,另一種方法是通過擴展現有的“Error” 類型來創建新的異常類型。由於新類型繼承自“Error”,因此它可以像其他內置異常類型一樣使用。雖然本文不討論 JavaScript 中的繼承主題,但這裡介紹了一種簡單的技術。
以下示例將回到處理除以零的問題。我們不再像前面那樣使用“Error” 或“RangeError” 對象,而是要創建我們自己的異常類型。在此示例中,我們正在創建“DivisionByZeroError” 異常類型。示例中的函數充當我們新類型的構造函數。構造函數負責分配“name” 和“message” 屬性。示例的最後兩行使新類型繼承自“Error” 對象。
<code class="language-javascript">function foo() { bar++; // ReferenceError }</code>
需要記住的事項
圖片來自 Fotolia
JavaScript 異常處理常見問題解答
語法錯誤,也稱為解析錯誤,在傳統的編程語言中發生在編譯時,在 JavaScript 中發生在解釋時。當代碼在語法上不正確且無法解析時,就會發生語法錯誤。例如,如果您忘記了右括號或使用了非法字符,則會發生語法錯誤。
另一方面,運行時錯誤發生在程序執行期間,在成功編譯之後。這些錯誤通常是由於代碼執行的非法操作造成的。例如,嘗試訪問未定義的變量或調用不存在的函數會導致運行時錯誤。
JavaScript 提供 try…catch 語句來處理異常。 try 塊包含可能拋出異常的代碼,而 catch 塊包含如果 try 塊中拋出異常將執行的代碼。這是一個簡單的示例:
<code class="language-javascript">var error = new Error("error message");</code>
JavaScript 中的“finally” 塊用於在 try 和 catch 塊之後執行代碼,無論結果如何。這意味著無論是否拋出異常,“finally” 塊中的代碼都會執行。它通常用於在代碼執行完畢後進行清理,例如關閉文件或清除資源。
是的,JavaScript 允許您使用“throw” 語句拋出自己的異常。您可以拋出任何類型的異常,包括字符串、數字、布爾值或對象。一旦拋出異常,程序的正常流程就會停止,控制權就會傳遞給最近的異常處理程序。
JavaScript 允許您通過擴展內置的 Error 對象來創建自定義錯誤類型。當您想要在代碼中拋出特定類型的錯誤時,這很有用。這是一個示例:
<code class="language-javascript">var pi = 3.14159; pi.toFixed(100000); // RangeError</code>
錯誤傳播是指將錯誤從拋出它的位置向上傳遞到調用堆棧中最近的異常處理程序的過程。如果在拋出錯誤的函數中沒有捕獲錯誤,它將向上傳播到調用函數,依此類推,直到被捕獲或到達全局範圍,此時程序將終止。
JavaScript 提供 console.error() 方法來記錄錯誤。此方法的工作方式與 console.log() 相同,但它還在日誌中包含堆棧跟踪,並在控制台中將消息突出顯示為錯誤。這是一個示例:
<code class="language-javascript">function foo() { bar++; // ReferenceError }</code>
在 JavaScript 中,“null” 和“undefined” 都表示值的缺失。但是,它們在略微不同的上下文中使用。 “Undefined” 表示已聲明變量但尚未為其賦值。另一方面,“null” 是一個賦值值,表示沒有值或沒有對象。
可以使用 JavaScript 中的 Promise 或 async/await 來處理異步錯誤。 Promise 具有一個“catch” 方法,當 Promise 被拒絕時會調用該方法。使用 async/await,您可以像使用同步代碼一樣使用 try/catch 塊。這是一個使用 async/await 的示例:
<code class="language-javascript">if (foo) { // SyntaxError // 缺少右花括号 }</code>
當無法執行操作時,通常是當值不是預期類型時,就會在 JavaScript 中拋出 TypeError。例如,嘗試調用非函數或訪問未定義的屬性會導致 TypeError。
以上是JavaScript中的出色異常處理的詳細內容。更多資訊請關注PHP中文網其他相關文章!