前面的話
錯誤處理對于web應用程序開發至關重要,不能提前預測到可能發生的錯誤,不能提前采取恢復策略,可能導致較差的用戶體驗。由于任何javascript錯誤都可能導致網頁無法使用,因此作為開發人員,必須要知道何時可能出錯,為什么會出錯,以及會出什么錯。本文將詳細介紹javascript中的錯誤處理機制
error對象
error對象是包含錯誤信息的對象,是javascript的原生對象。當代碼解析或運行時發生錯誤,javascript引擎就會自動產生并拋出一個error對象的實例,然后整個程序就中斷在發生錯誤的地方
console.log(t);//Uncaught ReferenceError: t is not defined
ECMA-262規定了error對象包括兩個屬性:message和name。message屬性保存著錯誤信息,而name屬性保存錯誤類型
瀏覽器還對error對象的屬性做了擴展,添加了其他相關信息。其中各瀏覽器廠商實現最多的是stack屬性,它表示棧跟蹤信息(safari不支持)
當然,可以使用error()構造函數來創建錯誤對象。如果指定message參數,則該error對象將把它用做它的message屬性;若不指定,它將使用一個預定義的默認字符串作為該屬性的值
當不使用new操作符,直接將Error()構造函數像一個函數一樣調用時,它的行為和帶new操作符調用時一樣
error對象有一個toString()方法,返回'Error:'+ error對象的message屬性
error類型
執行代碼期間可能會發生的錯誤有多種類型。每種錯誤都有對應的錯誤類型,而當錯誤發生時,就會拋出相應類型的錯誤對象。ECMA-262定義了下列7種錯誤類型:
其中,Error是基類型,其他錯誤類型都繼承自該類型。因此,所有錯誤類型共享了一組相同的屬性。Error類型的錯誤很少見,如果有也是瀏覽器拋出的;這個基類型的主要目的是供開發人員拋出自定義錯誤
【EvalError(eval錯誤)】
eval函數沒有被正確執行時,會拋出EvalError錯誤。該錯誤類型已經不再在ES5中出現了,只是為了保證與以前代碼兼容,才繼續保留
【RangeError(范圍錯誤)】
RangeError類型的錯誤會在一個值超出相應范圍時觸發,主要包括超出數組長度范圍以及超出數字取值范圍等
(1234).toExponential(21);//Uncaught RangeError: toExponential() argument must be between 0 and 20
(1234).toExponential(-1);////Uncaught RangeError: toExponential() argument must be between 0 and 20
引用一個不存在的變量或左值(lvalue)類型錯誤時,會觸發ReferenceError(引用錯誤)
a;//Uncaught ReferenceError: a is not defined
1++;//Uncaught ReferenceError: Invalid left-hand side expression in postfix operation
【SyntaxError(語法錯誤)】
當不符合語法規則時,會拋出SyntaxError(語法錯誤)
【TypeError(類型錯誤)】
在變量中保存著意外的類型時,或者在訪問不存在的方法時,都會導致TypeError類型錯誤。錯誤的原因雖然多種多樣,但歸根結底還是由于在執行特定類型的操作時,變量的類型并不符合要求所致
【URIError(URI錯誤)】
URIError是URI相關函數的參數不正確時拋出的錯誤,主要涉及encodeURI()、decodeURI()、encodeURIComponent()、decodeURIComponent()、escape()和unescape()這六個函數
decodeURI('%2');// URIError: URI malformed
error事件
任何沒有通過try-catch處理的錯誤都會觸發window對象的error事件
error事件可以接收三個參數:錯誤消息、錯誤所在的URL和行號。多數情況下,只有錯誤消息有用,因為URL只是給出了文檔的位置,而行號所指的代碼行既可能出自嵌入的JavaScript代碼,也可能出自外部的文件
要指定onerror事件處理程序,可以使用DOM0級技術,也可以使用DOM2級事件的標準格式
瀏覽器是否顯示標準的錯誤消息,取決于onerror的返回值。如果返回值為false,則在控制臺中顯示錯誤消息;如果返回值為true,則不顯示
這個事件處理程序是避免瀏覽器報告錯誤的最后一道防線。理想情況下,只要可能就不應該使用它。只要能夠適當地使用try-catch語句,就不會有錯誤交給瀏覽器,也就不會觸發error事件
圖像也支持error事件。只要圖像的src特性中的URL不能返回可以被識別的圖像格式,就會觸發error事件。此時的error事件遵循DOM格式,會返回一個以圖像為目標的event對象
加載圖像失敗時會顯示一個警告框。發生error事件時,圖像下載過程已經結束,也就是不能再重新下載了
throw語句與拋出錯誤
throw語句用于拋出錯誤。拋出錯誤時,必須要給throw語句指定一個值,這個值是什么類型,沒有要求
[注意]拋出錯誤的過程是阻塞的,后續代碼將不會執行
可以使用throw語句手動拋出一個Error對象
利用原型鏈還可以通過繼承Error來創建自定義錯誤類型(原型鏈在第6章中介紹)。此時,需要為新創建的錯誤類型指定name和message屬性
瀏覽器對待繼承自Error的自定義錯誤類型,就像對待其他錯誤類型一樣。如果要捕獲自己拋出的錯誤并且把它與瀏覽器錯誤區別對待的話,創建自定義錯誤是很有用的
在遇到throw語句時,代碼會立即停止執行。僅當有try-catch語句捕獲到被拋出的值時,代碼才會繼續執行
更詳細的解釋為:當拋出異常時,javascript解釋器會立即停止當前正在執行的邏輯,并跳轉到就近的異常處理程序。異常處理程序是用try-catch語句的catch從句編寫的。如果拋出異常的代碼塊沒有一條相關聯的catch從句,解釋器會檢查更高層的閉合代碼塊,看它是否有相關聯的異常處理程序。以此類推,直到找到一個異常處理程序為止。如果拋出異常的函數沒有處理它的try-catch語句,異常將向上傳播到調用該函數的代碼。這樣的話,異常就會沿著javascript方法的詞法結構和調用棧向上傳播。如果沒有找到任何異常處理程序,javascript將把異常當成程序錯誤來處理,并報告給用戶
try catch語句與捕獲錯誤
ECMA-262第3版引入了try-catch語句,作為JavaScript中處理異常的一種標準方式,用于捕獲和處理錯誤
其中,try從句定義了需要處理的異常所在的代碼塊。catch從句跟隨在try從句之后,當try塊內某處發生了異常時,調用catch內的代碼邏輯。catch從句后跟隨finally塊,后者中放置清理代碼,不管try塊中是否產生異常,finally塊內的邏輯總是會執行。盡管catch和finally都是可選的,但try從句需要至少二者之一與之組成完整的語句
try/catch/finally語句塊都需要使用花括號括起來,這里的花括號是必需的,即使從句中只有一條語句也不能省略花括號
try{
//通常來講,這里的代碼會從頭到尾而不會產生任何問題
//但有時會拋出一個異常,要么是由throw語句直接拋出,要么通過調用一個方法間接拋出
}catch(e){
//當且僅當try語句塊拋出了異常,才會執行這里的代碼
//這里可以通過局部變量e來獲得對Error對象或者拋出的其他值的引用
//這里的代碼塊可以基于某種原因處理這個異常,也可以忽略這個異常,還可以通過throw語句重新拋出異常
}finally{
//不管try語句是否拋出了異常,finally里的邏輯總是會執行,終止try語句塊的方式有:
//1、正常終止,執行完語句塊的最后一條語句
//2、通過break、continue或return語句終止
//3、拋出一個異常,異常被catch從句捕獲
//4、拋出一個異常,異常未被捕獲,繼續向上傳播
}
一般地,把所有可能會拋出錯誤的代碼都放在try語句塊中,而把那些用于錯誤處理的代碼放在catch塊中
如果try塊中的任何代碼發生了錯誤,就會立即退出代碼執行過程,然后接著執行catch塊。此時,catch塊會接收到一個錯誤信息的對象,這個對象中包含的實際信息會因瀏覽器而異,但共同的是有一個保存著錯誤消息的message屬性
[注意]一定要給error對象起個名字,置空會報語法錯誤
catch接受一個參數,表示try代碼塊拋出的值
catch代碼塊捕獲錯誤之后,程序不會中斷,會按照正常流程繼續執行下去
為了捕捉不同類型的錯誤,catch代碼塊之中可以加入判斷語句
雖然finally子句在try-catch語句中是可選的,但finally子句一經使用,其代碼無論如何都會執行。換句話說,try語句塊中的代碼全部正常執行,finally子句會執行;如果因為出錯而執行了catch語句塊,finally子句照樣還會執行。只要代碼中包含finally子句,則無論try或catch語句塊中包含什么代碼――甚至return語句,都不會阻止finally子句的執行
[注意]return語句的count的值,是在finally代碼塊運行之前,就獲取完成了
【tips】塊級作用域
try-catch語句的一個常見用途是創建塊級作用域,其中聲明的變量僅僅在catch內部有效
ES6引入了let關鍵字,為其聲明的變量創建塊級作用域。但是,在目前ES3和ES5的情況下,常常使用try-catch語句來實現類似的效果
由下面代碼可知,e僅存在于catch分句內部,當試圖從別處引用它時會拋出錯誤
常見錯誤
錯誤處理的核心是首先要知道代碼里會發生什么錯誤。由于javaScript是松散類型的,而且也不會驗證函數的參數,因此錯誤只會在代碼期間出現。一般來說,需要關注三種錯誤:類型轉換錯誤、數據類型錯誤、通信錯誤
【類型轉換錯誤】
類型轉換錯誤發生在使用某個操作符,或者使用其他可能自動轉換值的數據類型的語言結構時
容易發生類型轉換錯誤的地方是流控制語句。像if之類的語句在確定下一步操作之前,會自動把任何值轉換成布爾值。尤其是if語句,如果使用不當,最容易出錯
未使用過的命名變量會自動被賦予undefined值。而undefined值可以被轉換成布爾值false,因此下面這個函數中的if語句實際上只適用于提供了第三個參數的情況。問題在于,并不是只有undefined才會被轉換成false,也不是只有字符串值才可以轉換為true。例如,假設第三個參數是數值0,那么if語句的測試就會失敗,而對數值1的測試則會通過
在流控制語句中使用非布爾值,是極為常見的一個錯誤來源。為避免此類錯誤,就要做到在條件比較時切實傳入布爾值。實際上,執行某種形式的比較就可以達到這個目的
javascript是松散類型的,在使用變量和函數參數之前,不會對它們進行比較以確保它們的數據類型正確。為了保證不會發生數據類型錯誤,只能編寫適當的數據類型檢測代碼。在將預料之外的值傳遞繪函數的情況下,最容易發生數據類型錯誤
另一個常見的錯誤就是將參數與null值進行比較。與null進行比較只能確保相應的值不是null和undefined。要確保傳入的值有效,僅檢測null值是不夠的
如果傳入一個包含sort()方法的對象(而不是數組)會通過檢測,但調用reverse()函數時可能會出錯
在確切知道應該傳入什么類型的情況下,最好是使用instanceof來檢測其數據類型
【通信錯誤】
隨著Ajax編程的興起,Web應用程序在其生命周期內動態加載信息或功能,已經成為一件司空見慣的事。不過,javascript與服務器之間的任何一次通信,都有可能會產生錯誤
最常見的問題是在將數據發送給服務器之前,沒有使用encodeURIComponent()對數據進行編碼
新聞熱點
疑難解答