0%

JavaScript学习笔记(17)

《JavaScript高级程序设计》第十七章,错误处理与调试。
这里好像没什么内容,差不多就是trycatchthrow,还有几种错误类型。
至于调试的话,Chrome的高级调试工具我不太会用,快去学习 一个!


以前没有固定的开发工具和错误的上下文信息,导致调试非常困难。后来有了语言特性和工具支持,如引入了try-catchthrow语句和一些错误类型、Web浏览器中出现了一些JavaScript调试工具等,这下错误处理就简单些了。

错误处理

捕获错误try-catch

1
2
3
4
5
6
7
try {
// 可能会导致错误的代码
} catch(error) {
// 在错误发生时怎么处理
} finally {
// 可选的finally子句
}

这里的error对象有表示错误信息的message属性、表示错误类型的name属性。finally子句可选,但只要代码中包含finally子句,无论try或catch语句块中包含什么代码(甚至是return语句),都不会阻止finally子句的执行。譬如下面这个例子,调用函数只会返回0

1
2
3
4
5
6
7
8
9
function testFinally() {
try {
return 2;
} catch(error) {
return 1;
} finally {
return 0;
}
}

可能出现的错误有多种类型,ECMA-262定义了7中错误类型:

  • Error
    是基类型,其它错误类型都继承自此类型。

  • EvalError
    在使用eval()发生异常时会导致此类型错误。

    1
    2
    new eval(); // 抛出EvalError
    eval = foo; // 抛出EvalError
  • RangeError
    在数值超过相应范围时导致。

    1
    2
    const items1 = new Array(-20);              // 抛出RangeError
    const items2 = new Array(Number.MAX_RANGE); // 抛出RangeError
  • ReferenceError
    找不到对象或访问不存在的变量时导致。

    1
    let obj = x; // 在x并未声明情况下,抛出ReferenceError
  • SyntaxError
    当把语法错误的JavaScript字符串传入eval()时导致。

    1
    eval("a ++ b"); // 抛出SyntaxError
  • TypeError
    当在执行特定于类型的操作时,变量的类型不符合要求情况下导致。譬如变量中保存以外的类型、访问不存在的方法、传参错误等。

    1
    2
    3
    const o = new 10;                         // 抛出TypeError
    alert("name" in true); // 抛出TypeError
    Function.prototype.toString.call("name"); // 抛出TypeError
  • URIError
    使用encodeURL()decodeURL()而URL格式不正确时导致。

利用不同的错误类型,可以获取更多有关异常的信息,从而对错误做出恰当的处理。可以用instanceof来判断错误的类型。

1
2
3
4
5
6
7
8
9
10
11
try {
someFunction();
} catch(error) {
if(error instanceof TypeError) {
// 处理类型错误
} else if(error instanceof ReferenceError) {
// 处理引用错误
} else {
// 处理其它错误
}
}

* try-catch适用于处理那些我们无法控制的错误,譬如在使用一些库函数时,该函数可能会抛出一些错误,而我们没有办法修改库的源码,因此可以把函数调用包裹在try-catch里;而当我们明确知道自己的代码会发生错误时,就不应该用try-catch了,譬如要传递个函数的参数可能会类型错误,那么就应该先检查该参数的类型,再决定去怎么做。

抛出错误throw

与try-catch匹配的有一个throw操作符,用来随时抛出自定义错误。当遇到throw操作符时,代码会立即停止执行,仅当有try-catch语句捕获到被抛出的值时代码才会继续执行。使用throw时需要指定一个值,不过对该值的类型没有限制。

1
2
3
4
throw 12345;
throw "Hello world";
throw true;
throw { name: "JavaScript" };

可以用内置的错误类型来更加真实地模拟浏览器错误,这样,浏览器会像处理自己生成地错误一样,来处理这行代码抛出的错误。

1
2
3
4
throw new Error("Something bad happened");
throw new SyntaxError("I don't like your syntax");
throw new TypeError("What type of variable do you take me for?");
...

另外,利用原型链(在第6章)还可以通过继承Error来创建自定义错误类型,如下,此时需要为新创建的错误类型指定namemessage。有时候,在特定的情况下使用自定义错误,会让错误上报更加具体。

1
2
3
4
5
6
function CustomError(message) {
this.name = "CustomError";
this.message = message;
}
CustomError.prototype = new Error();
throw new CustomError("my messsage");

那么,何时该使用try-catch来捕获错误,何时该使用throw来抛出错误呢?一般会在应用程序架构的较低层次中抛出错误,在较高层次去捕获并适当地处理错误。捕获错误的目的在于避免浏览器以默认方式处理它们,抛出错误的目的在于提供错误发生具体原因的消息。

* 良好的错误处理机制应该可以确保代码中只发生你自己抛出的错误。

错误事件

任何没有通过try-catch处理的错误都会触发window对象的error事件。在onerror事件处理程序中,不会创建event对象,但它可以接收3个参数:错误消息、错误所在URL、错误所在行号。

1
2
3
4
window.onerror = function(message, url, line) {
alert(message);
return false;
};

这个例子里的返回false,相当于充当了整个文档的try-catch语句,捕获所有无代码处理的运行时错误,可以阻止浏览器报告错误的默认行为。

错误上报

集中保存错误日志可以为查找重要错误的原因以及分析数据提供帮助。这样一个错误记录系统,首先需要在服务器上创建一个页面(感觉一个接口其实就够了),让它从query string取得数据,再将数据写入错误日志,类似下面的例子。

1
2
3
4
5
6
7
8
9
10
11
// 参数sev表示严重程度、msg表示错误信息
function logError(ser, msg) {
const img = new Image();
img.src = `log.php?sev=${encodeURIComponent(sev)}&msg=${encodeURIComponent(msg)}`;
}

try {
doSomething();
} catch(ex) {
logError("nonfatal", `Module init failed: ${ex.message}`);
}

这里用Image的好处有:

  • 支持度高。所有浏览器都支持Image对象,包括不支持XMLHttpRequest对象的浏览器。
  • 可以避免跨域限制。通常一台服务器负责处理多台服务器的错误,这种情况下用XMLHttpRequest是不行的。
  • 在记录错误的过程中出问题的概率较低。大多数Ajax通信都是由JavaScript库提供的包装函数来处理的,如果库本身有问题,而我们还在依赖库来记录错误,那错误肯定得不到记录。

调试技术

这个针对的也是开发人员,有下面几种常见的调试技术。

  • 将消息记录到控制台
    通过console对象向JavaScript控制台写入消息,有几种方法:error(message)info(message)log(message)warn(message)
  • 将消息记录到当前页面
    也可以开辟一小块区域,用来显示消息。
  • 抛出错误
    这种调试方法一般就可以得到非常具体的错误信息了。

* 下面还有一段是介绍常见的IE错误,不喜欢IE,不看啦。