1、錯誤處理
- 任何有影響力的Web應用程序都需要一套完善的錯誤處理機制,良好的錯誤處理機制可以讓用戶及時得到提醒。
1.1 try-catch語句
try{
// 可能會導致錯誤的代碼
} catch(error){
// 在錯誤發生時怎么處理
}
(1)finally 子句
- 雖然在try-catch 語句中是可選的,但finally子句一經使用,其代碼無論如何都會執行。
- 只要代碼中包含finally子句,則無論try或catch語句塊中包含什么代碼——甚至return 語句,都不會阻止finally 子句的執行
function testFinally(){
try {
return 2;
} catch (error){
return 1;
} finally {
return 0;
}
}
/**調用這個函數只能返回0**/
(2)錯誤類型
-
執行代碼期間可能會發生的錯誤有多種類型。每種錯誤都有對應的錯誤類型,而當錯誤發生時,就會拋出相應類型的錯誤對象。
- Error
- EvalError
- RangeError
- ReferenceError
- SyntaxError
- TypeError
- URIError
EvalError 類型的錯誤會在使用eval()函數而發生異常時被拋出。
new eval(); //拋出EvalError
eval = foo; //拋出EvalError
- RangeError 類型的錯誤會在數值超出相應范圍時觸發。
var items1 = new Array(-20); //拋出RangeError
var items2 = new Array(Number.MAX_VALUE); //拋出RangeError
- 在找不到對象的情況下,會發生ReferenceError。
var obj = x; //在x 并未聲明的情況下拋出 ReferenceError
- 當我們把語法錯誤的JavaScript字符串傳入eval()函數時,就會導致此SyntaxError。
eval("a ++ b"); //拋出SyntaxError
- 在執行特定于類型的操作時,變量的類型不符合要求,會導致TypeError。
var o = new 10; //拋出TypeError
alert("name" in true); //拋出TypeError
Function.prototype.toString.call("name"); //拋出TypeError
在使用encodeURI()或decodeURI(),而URI 格式不正確時,就會導致URIError 錯誤。
利用不同的錯誤類型,可以獲悉更多有關異常的信息,從而有助于對錯誤作出恰當的處理。
try {
someFunction();
} catch (error){
if (error instanceof TypeError){
//處理類型錯誤
} else if (error instanceof ReferenceError){
//處理引用錯誤
} else {
//處理其他類型的錯誤
}
}
(3)合理使用try-catch
- 使用try-catch 最適合處理那些我們無法控制的錯誤。假設你在使用一個大型JavaScript 庫中的函數,該函數可能會有意無意地拋出一些錯誤。由于我們不能修改這個庫的源代碼,所以大可將對該函數的調用放在try-catch語句當中,萬一有什么錯誤發生,也好恰當地處理它們。
- 在明明白白地知道自己的代碼會發生錯誤時,再使用try-catch 語句就不太合適了。
1.2 拋出錯誤
與try-catch 語句相配的還有一個throw 操作符,用于隨時拋出自定義錯誤。
拋出錯誤時,必須要給throw 操作符指定一個值,這個值是什么類型,沒有要求。
在遇到throw 操作符時,代碼會立即停止執行。僅當有try-catch語句捕獲到被拋出的值時,代碼才會繼續執行。
利用原型鏈還可以通過繼承Error 來創建自定義錯誤類型。
function CustomError(message){
this.name = "CustomError";
this.message = message;
}
CustomError.prototype = new Error();
throw new CustomError("My message");
(1)拋出錯誤的時機
- 要針對函數為什么會執行失敗給出更多信息,拋出自定義錯誤是一種很方便的方式。
function process(values){
if (!(values instanceof Array)){
throw new Error("process(): Argument must be an array.");
}
values.sort();
for (var i=0, len=values.length; i < len; i++){
if (values[i] > 100){
return values[i];
}
}
return -1;
}
(2) 拋出錯誤與使用try-catch
如果你打算編寫一個要在很多應用程序中使用的JavaScript庫,甚至只編寫一個可能會在應用程序內部多個地方使用的輔助函數,我都強烈建議你在拋出錯誤時提供詳盡的信息。然后,即可在應用程序中捕獲并適當地處理這些錯誤。
捕獲那些確切地知道該如何處理的錯誤。捕獲錯誤的目的在于避免瀏覽器以默認方式處理它們;而拋出錯誤的目的在于提供錯誤發生具體原因的消息。
1.3 錯誤事件
- 在任何Web 瀏覽器中,onerror事件處理程序都不會創建event對象,但它可以接收三個參數:錯誤消息、錯誤所在的URL 和行號。
- 只要發生錯誤,無論是不是瀏覽器生成的,都會觸發error事件,并執行這個事件處理程序。
window.onerror = function(message, url, line){
alert(message);
//阻止瀏覽器報告錯誤的默認行為。
return false;
};
1.4 常見的錯誤類型
- 由于JavaScript 是松散類型的,而且也不會驗證函數的參數,因此錯誤只會在代碼運行期間出現。一般來說,需要關注三種錯誤:
- 類型轉換錯誤
- 數據類型錯誤
- 通信錯誤
(1)類型轉換錯誤
- 建議使用全等(===)和不全等(!==)操作符,以避免類型轉換。
- 容易發生類型轉換錯誤的另一個地方,就是流控制語句。像if之類的語句在確定下一步操作之前,會自動把任何值轉換成布爾值。尤其是if語句,如果使用不當,最容易出錯。
function concat(str1, str2, str3){
var result = str1 + str2;
if (str3){ //絕對不要這樣!!!
result += str3;
}
return result;
}
function concat(str1, str2, str3){
var result = str1 + str2;
if (typeof str3 == "string"){ //恰當的比較
result += str3;
}
return result;
}
(2) 數據類型錯誤
- 為了保證不會發生數據類型錯誤,只能依靠開發人員編寫適當的數據類型檢測代碼。在將預料之外的值傳遞給函數的情況下,最容易發生數據類型錯誤。
/**例子一**/
//不安全的函數,任何非字符串值都會導致錯誤
function getQueryString(url){
var pos = url.indexOf("?");
if (pos > -1){
return url.substring(pos +1);
}
return "";
}
function getQueryString(url){
if (typeof url == "string"){ //通過檢查類型確保安全
var pos = url.indexOf("?");
if (pos > -1){
return url.substring(pos +1);
}
}
return "";
}
/**例子二**/
//不安全的函數,任何非數組值都會導致錯誤
function reverseSort(values){
if (values){ //絕對不要這樣!!!
values.sort();
values.reverse();
}
}
//不安全的函數,任何非數組值都會導致錯誤
function reverseSort(values){
if (values != null){ //絕對不要這樣!!!
values.sort();
values.reverse();
}
}
//還是不安全,任何非數組值都會導致錯誤
function reverseSort(values){
if (typeof values.sort == "function"){ //絕對不要這樣!!!
values.sort();
values.reverse();
}
}
//安全,非數組值將被忽略
function reverseSort(values){
if (values instanceof Array){ //問題解決了
values.sort();
values.reverse();
}
}
(3)通信錯誤
- 第一種通信錯誤與格式不正確的URL或發送的數據有關。最常見的問題是在將數據發送給服務器之前,沒有使用encodeURIComponent()對數據進行編碼。
- 對于查詢字符串,應該記住必須要使用encodeURIComponent()方法。為了確保這一點,有時候可以定義一個處理查詢字符串的函數。
function addQueryStringArg(url, name, value){
if (url.indexOf("?") == -1){
url += "?";
} else {
url += "&";
}
url += encodeURIComponent(name) + "=" + encodeURIComponent(value);
return url;
}
1.5 區分致命錯誤和非致命錯誤
- 對于非致命錯誤,可以根據下列一或多個條件來確定:
- 不影響用戶的主要任務;
- 只影響頁面的一部分;
- 可以恢復;
- 重復相同操作可以消除錯誤。
- 致命錯誤,可以通過以下一或多個條件來確定:
- 應用程序根本無法繼續運行;
- 錯誤明顯影響到了用戶的主要操作;
- 會導致其他連帶錯誤。
for (var i=0, len=mods.length; i < len; i++){
mods[i].init(); //可能會導致致命錯誤
}
for (var i=0, len=mods.length; i < len; i++){
try {
mods[i].init();
} catch (ex) {
//在這里處理錯誤
}
}
1.6 把錯誤記錄到服務器
- 開發Web 應用程序過程中的一種常見的做法,就是集中保存錯誤日志,以便查找重要錯誤的原因。
- 要建立這樣一種JavaScript錯誤記錄系統,首先需要在服務器上創建一個頁面用于處理錯誤數據。這個頁面的作用無非就是從查詢字符串中取得數據,然后再將數據寫入錯
誤日志中。 - 這個頁面可能會使用如下所示的函數:
function logError(sev, msg){
var img = new Image();
img.src = "log.php?sev=" + encodeURIComponent(sev) + "&msg=" +
encodeURIComponent(msg);
}
- 使用了Image 對象來發送請求,這樣做非常靈活,主要表現如下幾方面。
(1)所有瀏覽器都支持Image 對象,包括那些不支持XMLHttpRequest 對象的瀏覽器。
(2)可以避免跨域限制。通常都是一臺服務器要負責處理多臺服務器的錯誤,而這種情況下使用XMLHttpRequest 是不行的。
(3)在記錄錯誤的過程中出問題的概率比較低。
for (var i=0, len=mods.length; i < len; i++){
try {
mods[i].init();
} catch (ex){
logError("nonfatal", "Module init failed: " + ex.message);
}
}
2、調試技術
(1)將消息記錄到控制臺
- 可以通過console 對象向JavaScript 控制臺中寫入消息。
- error(message):將錯誤消息記錄到控制臺
- info(message):將信息性消息記錄到控制臺
- log(message):將一般消息記錄到控制臺
- warn(message):將警告消息記錄到控制臺
(2)將消息記錄到當前頁面
- 在頁面中開辟一小塊區域,用以顯示消息
(3)拋出錯誤
function divide(num1, num2){
if (typeof num1 != "number" || typeof num2 != "number"){
throw new Error("divide(): Both arguments must be numbers.");
}
return num1 / num2;
}
- 對于大型應用程序來說,自定義的錯誤通常都使用assert()函數拋出。這個函數接受兩個參數,一個是求值結果應該為true的條件,另一個是條件為false時要拋出的錯誤。
//基本的assert()函數
function assert(condition, message){
if (!condition){
throw new Error(message);
}
}
- 使用assert()函數可以減少拋出錯誤所需的代碼量.
function divide(num1, num2){
assert(typeof num1 == "number" && typeof num2 == "number",
"divide(): Both arguments must be numbers.");
return num1 / num2;
}
好好學習