無論是瀏覽器控制台還是Node.js的服務端,我們會在各種地方看到JavaScript異常,異常處理是編寫程序必備的基礎能力,在學習異常處理之前,了解 JavaScript 中的幾種異常類型是非常有必要的。
1、Error
Error 是最基本的錯誤類型,其他的錯誤類型都繼承自該類型。
Error 對象主要有兩個重要屬性 message 和 name 分別表示錯誤信息和錯誤名稱。
程序運行過程中拋出的異常一般都有具體的類型,
Error類型一般都是開發人員自己拋出的異常。

try { throw new Error('ConardLi拋出的異常'); } catch (error) { console.log(error); }
2、SyntaxError - 語法錯誤
語法錯誤也稱為解析錯誤。語法錯誤在任何編程語言中都是最常見的錯誤類型,表示不符合編程語言的語法規范。
JavaScript 是一門解釋性語言,執行一段代碼時需要經歷 詞法分析 -> 語法分析 -> 語法樹 就可以開始解釋執行了:
詞法分析是將字符流(char stream)轉換為記號流(token stream)、語法分析階段會將記號流(token stream)生成抽象語法樹(AST)。

在這兩個階段,如果 Javascript引擎發現了預期之外/無法抓換的 token,或者 token 順序和預期不一致時,就會拋出 SyntaxError。
因此 SyntaxError 應該和其他類型的異常區分開,此類異常發生在 JavaScript 解析/編譯時,此類異常一旦發生,導致整個js文件都無法執行,而其他異常發生在代碼運行時,這一類的錯誤會導致在錯誤出現的那一行之后的代碼無法執行,但在那一行之前的代碼不會受到影響。
SyntaxError 類型的錯誤通常是語法錯誤,遇到這中錯誤時建議通過你所用的 IDE 排查,比如 VSCode 能夠直接跳出這類型的錯誤提示。
如下圖,VSCode 用紅色波浪線提示 family 對象有錯誤,當出現錯誤時會建議不要只檢查當前行,錯誤可能會存在於上下文中(有可能跨多行的錯誤),這個例子中仔細檢查可以發現在'小明'后面少了一個逗號。

1)Uncaught SyntaxError: Unexpected identifier
var person = { name: '小明' family: { name: '小明家' } }
語法解析錯誤,因為在對象結構中缺少一個逗號,除了通過在 VSCode 中查看外,也可以直接通過 Chrome Console 切換到 Source 頁面查看錯誤行,並檢查此行的上下文中是否存在語法錯誤
2)Uncaught SyntaxError: Unexpected end of input
function fn() { console.log('這是一個函數'); console.log(fn);
語法解析錯誤:未預期的結束,這個例子中缺少結尾的大括號 },在編寫代碼時盡可能的維持正確的鎖緊,將代碼排列整齊之后更容易找到錯誤。

3)Uncaught SyntaxError: Unexpected token '}'
if (name) console.log('立即執行函數') };

語法解析錯誤:未預期的符號 },代碼結尾多了一個 } 符號導致環境運行錯誤,這個錯誤的排查方法與上面相同,盡可能將代碼排整齊並維持首尾符號的一致。
4)Uncaught SyntaxError: Identifier 'a' has already been declared
let a;
let a;
語法解析錯誤:識別符號(在這里指的是變量)已經被聲明,應該避免重復生命同一個變量,在 ES6 都禁止用 let、const 對變量進行重復聲明,直接排除即可。
3、TypeError - 類型錯誤
運行時最常見的異常,表示變量或參數不是預期類型,比如 new 關鍵字后面必須為構造函數、()前必須為函數。

TypeError 是類型上的錯誤,同樣 IDE 也不會預先提示有錯誤,必須在執行時才會看到,這類型的錯誤通常是以下幾種:
- 試圖獲取 undefined、null 的屬性
- 嘗試調用非函式變量或表達式(例如:
'text'())
排查重點:在獲取變量前先確認其當前的數據類型及結構
1)Uncaught TypeError: Cannot read property 'a' of undefined
var a; console.log(a.a);
說明:在這個變量的值中無法找到其特定的屬性,例如在 undefined、null 的值上是找不到其它屬性的。
2)Uncaught TypeError: console.log(...) is not a function
console.log('a') (function() { console.log('立即執行函數') })()

說明:這代碼看起來是立即執行函數的錯誤,但是卻出現了 console.log(...) is not a function。這個錯誤主要是因為缺少了分號。當遇到這類錯誤時只要在兩者之間補上分號即可。
4、ReferenceError - 引用錯誤
引用一個不存在的變量時發生的錯誤,每當我們創建或定義一個變量時,變量名稱都會寫入一個變量存儲中心中。
這個變量存儲中心就像鍵值存儲一樣,每當我們引用變量時,它都去存儲中找到 Key 並提取並返回 Value,如果我們要找的變量不在存儲中,就會拋出 ReferenceError。
比如下面這個未定義的錯誤:

請注意,如果我們調用的是一個已經存在的變量的一個不存在的屬性,則不會拋出 ReferenceError,因為變量本身已經在存儲中了,調用它不存在的屬性只會是未賦值狀態,也就是 undefined:

ReferenceError 這類錯誤通常是指找不到引用,當出現這類錯誤時在 IDE 中不一定會提示現錯誤(除非安裝了 Linter),所以在代碼的運行階段才會看到這類錯誤。
排查重點:
- 通過 Chrome 的提示改正
- 在 JavaScript 開發環境中安裝 ESLint
1)ReferenceError: a is not defined
引用錯誤:由於變量 a 未定義,所以在使用這個變量時會出現未定義的提示,只要先定義好這個變量即可。
還有另一種很常見的情況,當引用外部包時出現 “包名 + is not defined”,這種情況通常是外部資源沒有被正確載入,應該確保該資源被正確的引入。
下面的例子就是因為 jQuery 沒有正確導入而導致的。
Uncaught ReferenceError: $ is not defined
5、RangeError - 邊界錯誤
表示超出有效范圍時發生的異常,主要的有以下幾種情況:
- 數組長度為負數或超長
- 數字類型的方法參數超出預定義范圍
- 函數堆棧調用超過最大值

這是創建了超過長度上限的數組或執行了無法退出的遞歸函數所造成的錯誤,遇到這類問題需要重新檢查代碼的邏輯,是否消耗了過多的資源(內存或CPU資源)。
排查重點:需要重新檢查邏輯,如果有必要可先刪除部分代碼,先找出錯誤的片段后再進行除錯。
1)Uncaught RangeError: Maximum call stack size exceeded
(function a() {
a();
})();
說明:在函數調用時會產生一個函數調用棧,如果在遞歸的過程中超過上限則會產生錯誤。
這類錯誤也很常見,卻不容易找到出錯的原因,其主要原因是在遞歸時超過了環境的限制(使用框架時也很常見),如果遇到這錯誤建議改寫當前調用函數的方式。
6、URIError - URL 錯誤
在調用 URI 相關的方法中 URL 無效時拋出的異常,主要包括 encodeURI、decodeURI()、encodeURIComponent()、decodeURIComponent()、escape()和unescape()幾個函數:

7、自定義異常
另外,為了滿足各種各樣的業務需求,除了 JavaScript 已經給定的異常類型,我們還可以自定義一些異常類型,比如我們要根據不同的異常類型給用戶不同的錯誤提示:
class UnAuthError extends Error { } class ParamError extends Error { } function controller() { throw new UnAuthError(); } try { controller(); } catch (error) { if (error instanceof UnAuthError) { return '無權限'; } if (error instanceof ParamError) { return '參數錯誤'; } }
