【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;
}
好好学习

推荐阅读更多精彩内容