第17章 错误处理与调试

来源:互联网 发布:淘宝店家身份查询 编辑:程序博客网 时间:2024/05/17 04:16
第17章 错误处理与调试
17.1 浏览器报告的错误
如果脚本有错误,浏览器都有某种机制向用户报告错误。


17.1.1 IE
IE在默认情况下,发生脚本错误时会在在浏览器的左下角处显示一个叹号(可以通过设置-取消禁止调试来一发生就弹框)。
其他的浏览器可以按F12的console查看具体的脚本错误。


17.1.2 Firefox
通常会使用Firebug来调试脚本错误。


17.1.3 Safari
苹果的浏览器,不常用。


17.1.4 Opera
欧朋浏览器,现在已经被 360 和昆仑万维收购,不常用。


17.1.5 Chrome
谷歌浏览器,默认情况也会隐藏JavaScript错误。


17.2 错误处理
//错误处理在程序设计中的重要性毋容置疑,为此,作为开发人员,我们必须理解在处理JavaScript错误的时候,都有哪些手段和工具可以利用。


17.2.1 try-catch语句
try{
//可能出现错误的代码
}catch{
//出现错误时怎么处理
}
例如:(TryCatchExample01.htm)
try{
window.someNonexistenFunction();
}catch(error){
alert(error.message);
}
error对象的message属性是唯一一个能够保证所有的浏览器都支持的属性。其他浏览器都添加了对象的相关信息,所以在跨浏览器编程时,最好只使用error.message


1.finally子句(可选)
①try语句中全部代码正常执行,finally子句会执行
②try语句中发生异常,执行了catch语句,finally子句还是会执行
就是写了finally就会执行,即使try-catch里面有return


例如:
function testFinally(){
try{
return 2;
}catch(error){
return 1;
}finally{
return 0;
}
}
如果提供了finally字句,则catch字句就成了可选(catch或者finally有一个即可)。IE7及早期版本中有一个bug;除非有catch子句,否则finally的代码永远不会执行。


2.错误类型
ECMA-262定义了7种错误类型:
Error:基类型,其他错误类型都继承自该类型,Error类型的错误很少见,抛出的主要目的供开发人员抛出自定义错误。
EvalError:使用eval()函数发生异常被抛出,没有把eval()当成函数调用,就会抛出错误
RangeError:在数值超出范围会触发
ReferenceError:找不到对象(var obj=x)
SyntaxError:把语法错误传入eval()函数时会触发(eval("a++b"))
TypeError:执行特定于类型的操作时,变量的类型并不符合要求所导致
URIError:使用encodeURI或decodeURI(),URI格式不正确时


EvalError:
eval() 函数可计算某个字符串,并执行其中的的 JavaScript 代码。
例子:
new eval();//chrome-TypeError
eval=foo;//chrome-ReferenceError


RangeError:
例子:
var items1=new Array(-20);
var items2=new Array(Number.MAX_VALUE);


TypeError:
var o=new 10;
alert("name" in true);
最常发生的情况:传递给函数的参数没有检查,传了一个与预期不相符的函数


使用instanceof可以获取错误的类型
如:
try{
someFunction();
}catch(error){
if(error instanceof TypeError){
//处理类型错误
}else if(error instanceof ReferenceError){
//处理引用错误
}else{
//处理其他类型错误
}
}
利用不同的错误类型,可以获悉更多有关异常的信息,从而有助于对错误做出恰当的处理。


3.合理使用try-catch
当try-catch语句中发生错误时,浏览器会认为错误已经被处理掉,所以就不会报错和记录错误了。但是,如果明明白白的知道自己的代码会发生错误就不应该使用
try-catch语句了,而是应该处理这个错误才对。


17.2.2 抛出错误
与try-catch语句相匹配的还有一个throw操作符,用于随时抛出自定义错误。抛出错误时,必须要给throw指定一个值。如:
throw 12345;
throw "hello world";
throw true;
throw {name:"JavaScript"};


在遇到throw操作符时,代码会立即停止。仅当try-catch语句捕获到抛出的值时,才会继续执行。
以下是每种错误类型的构造函数例子:
throw new Error("Error");
throw new SyntaxError("SyntaxError");
throw new TypeError("TypeError");
throw new RangeError("RangeError");
throw new EvalError("EvalError");
throw new URIError("URIError");
throw new ReferenceError("ReferenceError");




可以利用原型链通过继承Error来创建自定义错误类型,需要为新创建的类型指定name和message属性,如:(ThrowingErrorsExample01.htm)
function CustomError(message){
this.name="CustomError";
this.message=message;
}
CustomError.prototype=new Error();
throw new CustomError("My message");


1.抛出错误的时机
想知道函数为什么执行失败,抛出自定义错误是一种很方便的方式。例子:
function process(values){
values.sort();

for(var i=0,len=values.length;i<len;i++){
if(values[i]>100){
return values[i];
}
}
return -1;
}
如果不传数组作为参数,肯定就会报错了。不同的浏览器的错误给出的信错误息都不同,假如面对的是成千上万行的js代码时,要查找错误就很难了,出于
可维护性的考虑,如:
function process(values){
if(!(values instanceof Array)){
throw new Error("process():参数必须是数组");
}
values.sort();

for(var i=0,len=values.length;i<len;i++){
if(values[i]>100){
return values[i];
}
}
return -1;
}
//建议在开发JavaScript代码的过程中,重点关注函数和可能导致函数执行失败的因素。良好的错误处理机制应该可以确保代码中只发生你自己抛出的错误。


2.抛出错误与使用try-catch
应该捕获那些你确切知道该如何处理的错误。捕获错误的目的在于避免浏览器以默认方式处理他们;而抛出错误的目的在于提供错误发生具体原因的消息。


17.2.3错误(error)事件
任何没有通过try-catch处理的错误都会触发window对象的error事件。在任何Web浏览器中,onerror事件处理程序都不会创建event对象,但他可以接受三个参数:
错误消息、错误所在url和行号。像下面这样在事件处理程序中返回false,可以阻止浏览器报告错误的默认行为。(OnErrorExample01.htm)//->
window.onerror = function(message, url, line){
alert(message);
return false;
};
throw new Error("Something bad happened.");
通过返回false,这个函数实际充当了整个文档中的try-catch语句,可以捕获所有无代码处理的运行时错误。图像也支持error事件。(OnErrorExample02.htm)


17.2.4 处理错误的策略
在web应用程序的JavaScript这一端,错误的处理策略很重要,作为开发者,必须知道代码何时可能出错,出什么错,还要有一套错误跟踪问题的系统。


17.2.5常见的错误类型
类型转换错误
数据类型错误
通信错误


1.类型转换错误
例子:
alert(5=="5");//true
alert(5==="5");//false
alert(1==true);//true
alert(1===true);//false
由于相等和不相等操作符会在比较之前进行一个转换,所以js会误以为这个比较是正确的而继续执行代码,然后导致错误,所以建议使用全等或不全等


例子:
function concat(str1,str2,str3){
var result=str1+str2;
if(str3){
result+=str3;
}
return result;
}
这个函数的用意是拼接两或三个字符串,然后返回结果,其中第三个参数可选,当函数存在第三个参数的时候,那么就应该执行if语句,但是当第三个参数传入0,
由于转为false,所以也不会执行,更好的写法:
function concat(str1,str2,str3){
var result=str1+str2;
if(typeof str3=="string"){
result+=str3;
}
return result;
}
//在流程控制语句中使用非布尔值为条件很容易导致类型转换错误。


2.数据类型错误
例子:
//不安全的函数,任何非字符串值都会导致错误
function getQuqeryString(url){
var pos=url.indexOf("?");
if(pos>-1){
return url.substring(pos+1);
}
return "";
}
这个例子中的两个函数只能操作字符串,所以要加个类型检测就会没那么容易出错
function getQuqeryString(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(values instanceof Array){
values.sort();
values.reverse();
}
}


3.通信错误
JavaScript与服务器之间的通信所产生的错误。
最常见的错误:将数据发送给服务器之前,没有使用encodeURIComponent()对数据进行编码,例如:
http://www.xxx.com/?param=abc
应该要对param=后面的字符串调用encodeURIComponent(),具体:
//url:服务器端地址,name:编码的字符名称,value:要编码的字符
function addQueryStringArg(url,name,value){
if(url.indexOf("?")==-1){
url+="?";
}else{
url+="&";
}
url+=encodeURIComponent(name)+"="+encodeURIComponent(value);
return url;
}
var url="http://www.baidu.com";
var newUrl=addQueryStringArg(url,"redir","http://www.baidu.com?a=b&c=d");
alert(newUrl);
注意:在使用Ajax通信的情况下,也可能发生通信错误,相关的问题和错误将在第21章讨论


17.2.6区分致命错误和非致命错误
非致命错误:
不影响用户的主要任务
只影响页面的一部分
可以恢复
重复相同错误可以消除错误


致命错误:
程序无法继续运行
明显影响到用户的主要操作
会导致其他连带错误


17.2.6把错误记录到服务器(很有用)
首先要在服务器创建一个页面,用于处理错误数据。
例子:
function logError(sev,msg){
var img=new Image();
img.src="log.aspx?sev="+encodeURIComponent(sev)+"&msg="+encodeURIComponent(msg);
}
参数1:表示严重程度的数值或字符串
参数2:Image对象,所有浏览器都支持这个对象,避免跨域限制
for(var i=0;len=mods.length;i<len;i++){
try{
mods[i].init();
}catch(ex){
logError("nonfatal","module init failed:"+ex.message);
}
}


17.3 调试技术
最常见的做法:alert(),但是很麻烦


17.3.1 将消息记录到控制台
需要用到console对象,有以下方法:
error(message):将错误消息记录到控制台
info(message):将信息性消息记录到控制台
log(message):将一般消息记录到控制台
warn(message):将警告消息记录到控制台


下面这个函数可以作为一个统一的接口(ConsoleLoggingExample01.htm)//->
function log(message){
if (typeof console == "object"){
console.log(message);
} else if (typeof opera == "object"){
opera.postError(message);
} else if (typeof java == "object" && typeof java.lang == "object"){
java.lang.System.out.println(message);
}
}
        
function sum(num1, num2){
log("Entering sum(), arguments are " + num1 + "," + num2);


log("Before calculation");
var result = num1 + num2;
log("After calculation");


log("Exiting sum()");
return result;
}


var result = sum(10, 23);


17.3.2将消息记录到当前页面
例子:(PageLoggingExample01.htm)//->
function log(message){                    
var console = document.getElementById("debuginfo");
if (console === null){
console = document.createElement("div");
console.id = "debuginfo";
console.style.background = "#dedede";
console.style.border = "1px solid silver";
console.style.padding = "5px";
console.style.width = "400px";
console.style.position = "absolute";
console.style.right = "0px";
console.style.top = "0px";
document.body.appendChild(console);
}
console.innerHTML += "<p>" + message + "</p>";
}


17.3.2 抛出错误
如前所述,抛出错误也是一种调试代码的好办法。
function divide(num1,num2){
return num1/num2;
}
如果其中一个参数不是数组,都会导致这个函数返回NaN,所以应该要检测一下:
function divide(num1,num2){
if(typeof num1 !="number"||typeof num2 !="number"){
throw new Error("两个参数都要为数值")
}
return num1/num2;
}
对于大型的应用程序来说,自定义的错误通常使用assert()函数抛出(AssertExample01.htm)//->
//condition:不抛出错误的条件,message:抛出的message
function assert(condition, message){
if (!condition){
throw new Error(message);
}
}
function divide(num1, num2){
assert(typeof num1 == "number" && typeof num2 == "number", 
  "divide(): Both arguments must be numbers.");
return num1 / num2;
}


17,4常见的IE错误(最后)//->
多年以来,IE一直都是最难于调试JavaScript错误的浏览器。


17.4.1操作终止(OperationAbortedExample01.htm)
在IE8之前,当页面尚未加载完毕就修改document.body,而且<script>元素还不是<body>的直接子元素。就会发生操作终止(在当前的div还没加载完就继续修改元素)


解决方案:(OperationAbortedExample02.htm,OperationAbortedExample03.htm)


17.4.2无效字符
就是在JavaScript文件中包含JavaScript语法未定义的字符。如("\u2013")很像减号,是在word文档中自动插入的,如果你的代码是用word编辑器复制进来就很
可能报错了。


17.4.3未找到成员(。。。)
如果对象被销毁之后,又对该对象赋值,就会导致未找到成员错误。
document.onclick=function(){
var event=window.event;
setTimeout(function(){
event.returnValue=false;//未找到成员错误
},1000);
}


17.4.4未知运行错误
当使用innerHTML或outerHTML以以下方式指定HTML时,就会发生未知运行错误
①把块元素插入到行内元素
②访问表格任意部分(<table>、<tbody>等)的任意属性时
如:
span.innerHTML="<div>Hi</div>";//其他浏览器会尝试纠正并隐藏错误,而IE在这一点反而很较真儿。


17.4.5 语法错误
通常就是少了一个分号或者花括号不对应,但是有一种情况很不明显。
引用外部JavaScript文件,而该文件没有返回JavaScript代码。


17.4.6 系统无法找到指定资源
在使用JavaScript请求某个资源URL,而该URL的长度超过了IE对URL最长不能超过2083个字符的限制时,就会发生这个错误。
例如:(LongURLErrorExample01.htm,貌似IE8没有报错)
<script type="text/javascript">
function createLongUrl(url){
var s = "?";
for (var i=0, len=2500; i < len; i++){
s += "a";
}

return url + s;
}
   
var x = new XMLHttpRequest();
x.open("get", createLongUrl("http://www.somedomain.com/"), true);
x.send(null);
</script>


17.5 小结
错误处理对于今天复杂的Web应用程序开发而言至关重要,在投入运行的产品代码中,不应该再有诸如此类的错误报告出现。
下面是几种避免浏览器响应JavaScript错误的方法:
①在可能发生错误的地方使用try-catch语句。
②使用window.error事件处理程序。


另外,任何Web应用程序都应该分析可能的错误来源,并制定处理错误的方案:
①明确致命和非致命错误
②判断最可能发生错误的代码,而JavaScript中发生错误的主要原因如下:
a.类型转换
b.为充分检测数据类型
c.发送给服务器或从服务器接收到的数据有误
借助浏览器进行脚本错误调试。









0 0