【javascript】錯誤處理和調試

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;
}
好好學習
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容