17.错误处理与调试(2)

3.调试技术:在不那么容易找到JavaScript 调试程序的年代,开发人员不得不发挥自己的创造力,通过各种方法来调试自己的代码。结果,就出现了以这样或那样的方式置入代码,从而输出调试信息的做法。其中,最常见的做法就是在要调试的代码中随处插入alert()函数。但这种做法一方面比较麻烦(调试之后还需要清理),另一方面还可能引入新问题(想象一下把某个alert()函数遗留在产品代码中的结果)。如今,已经有了很多更好的调试工具,因此我们也不再建议在调试中使用alert()了。

  • 将消息记录到控制台:IE8、Firefox、Opera、Chrome 和Safari 都有JavaScript 控制台,可以用来查看JavaScript 错误。而且,在这些浏览器中,都可以通过代码向控制台输出消息。对Firefox 而言,需要安装Firebug(www.getfirebug.com),因为Firefox 要使用Firebug 的控制台。对IE8、Firefox、Chrome 和Safari 来说,则可以通过console 对象向JavaScript 控制台中写入消息,这个对象具有下列方法。
  1. error(message):将错误消息记录到控制台
  2. info(message):将信息性消息记录到控制台
  3. log(message):将一般消息记录到控制台
  4. warn(message):将警告消息记录到控制台

在IE8、Firebug、Chrome 和Safari 中,用来记录消息的方法不同,控制台中显示的错误消息也不一样。错误消息带有红色图标,而警告消息带有黄色图标。以下函数展示了使用控制台输出消息的一个示例。

function sum(num1, num2){
    console.log("Entering sum(), arguments are " + num1 + "," + num2);
    console.log("Before calculation");
    var result = num1 + num2;
    console.log("After calculation");
    console.log("Exiting sum()");
    return result;
}

在调用这个sum()函数时,控制台中会出现一些消息,可以用来辅助调试。在Safari 中,通过“Develop”(开发)菜单可以打开其JavaScript 控制台(前面讨论过);在Chrome 中,单击“Control thispage”(控制当前页)按钮并选择“Developer”(开发人员)和“JavaScript console”(JavaScript 控制台)即可;而在Firefox 中,要打开控制台需要单击Firefox 状态栏右下角的图标。IE8 的控制台是其DeveloperTools(开发人员工具)扩展的一部分,通过“Tools”(工具)菜单可以找到,其控制台在“Script”(脚本)选项卡中。

Opera 10.5 之前的版本中,JavaScript 控制台可以通过opera.postError()方法来访问。这个方法接受一个参数,即要写入到控制台中的参数,其用法如下。

function sum(num1, num2){
	opera.postError("Entering sum(), arguments are " + num1 + "," + num2);
	opera.postError("Before calculation");
	var result = num1 + num2;
	opera.postError("After calculation");
	opera.postError("Exiting sum()");
	return result;
}

别看opera.postError()方法的名字好像是只能输出错误,但实际上能通过它向JavaScript 控制台中写入任何信息。

还有一种方案是使用LiveConnect,也就是在JavaScript 中运行Java 代码。Firefox、Safari 和Opera都支持LiveConnect,因此可以操作Java 控制台。例如,通过下列代码就可以在JavaScript 中把消息写入到Java 控制台。

java.lang.System.out.println("Your message");

可以用这行代码替代console.log()或opera.postError(),如下所示。

function sum(num1, num2){
    java.lang.System.out.println("Entering sum(), arguments are " + num1 + "," + num2);
    java.lang.System.out.println("Before calculation");
    var result = num1 + num2;
    java.lang.System.out.println("After calculation");
    java.lang.System.out.println("Exiting sum()");
    return result;
}

如果系统设置恰当,可以在调用LiveConnect 时就立即显示Java 控制台。在Firefox 中,通过“Tools”(工具)菜单可以打开Java 控制台;在Opera 中,要打开Java 控制台,可以选择菜单“Tools”(工具)及“Advanced”(高级)。Safari 没有内置对Java 控制台的支持,必须单独运行。

不存在一种跨浏览器向JavaScript 控制台写入消息的机制,但下面的函数倒可以作为统一的接口。

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);
	}
}

这个log()函数检测了哪个JavaScript 控制台接口可用,然后使用相应的接口。可以在任何浏览器中安全地使用这个函数,不会导致任何错误,例如:

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;
}

向JavaScript 控制台中写入消息可以辅助调试代码,但在发布应用程序时,还必须要移除所有消息。在部署应用程序时,可以通过手工或通过特定的代码处理步骤来自动完成清理工作。

记录消息要比使用alert()函数更可取,因为警告框会阻断程序的执行,而在测定异步处理对时间的影响时,使用警告框会影响结果。

  • 将消息记录到当前页面:另一种输出调试消息的方式,就是在页面中开辟一小块区域,用以显示消息。这个区域通常是一个元素,而该元素可以总是出现在页面中,但仅用于调试目的;也可以是一个根据需要动态创建的元素。例如,可以将log()函数修改为如下所示:
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>";
}

这个修改后的log()函数首先检测是否已经存在调试元素,如果没有则会新创建一个<div>元素,并为该元素应用一些样式,以便与页面中的其他元素区别开。然后,又使用innerHTML 将消息写入到这个<div>元素中。结果就是页面中会有一小块区域显示错误消息。这种技术在不支持JavaScript 控制台的IE7 及更早版本或其他浏览器中十分有用。

与把错误消息记录到控制台相似,把错误消息输出到页面的代码也要在发布前删除。

  • 抛出错误:如前所述,抛出错误也是一种调试代码的好办法。如果错误消息很具体,基本上就可以把它当作确定错误来源的依据。但这种错误消息必须能够明确给出导致错误的原因,才能省去其他调试操作。来看下面的函数:
function divide(num1, num2){
    return num1 / num2;
}

这个简单的函数计算两个数的除法,但如果有一个参数不是数值,它会返回NaN。类似这样简单的计算如果返回NaN,就会在Web 应用程序中导致问题。对此,可以在计算之前,先检测每个参数是否都是数值。例如:

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()函数代替某些函数中需要调试的if 语句,以便输出错误消息。下面是使用这个函数的例子。

function divide(num1, num2){
	assert(typeof num1 == "number" && typeof num2 == "number", 
"divide(): Both arguments must be numbers.");
	return num1 / num2;
}

可见,使用assert()函数可以减少抛出错误所需的代码量,而且也比前面的代码更容易看懂。

4.常见的IE 错误:多年以来,IE 一直都是最难于调试JavaScript 错误的浏览器。IE 给出的错误消息一般很短又语焉不详,而且上下文信息也很少,有时甚至一点都没有。但作为用户最多的浏览器,如何看懂IE 给出的错误也是最受关注的。下面几小节将分别探讨一些在IE 中难于调试的JavaScript 错误。

  • 操作终止:在IE8 之前的版本中,存在一个相对于其他浏览器而言,最令人迷惑、讨厌,也最难于调试的错误:操作终止(operation aborted)。在修改尚未加载完成的页面时,就会发生操作终止错误。发生错误时,会出现一个模态对话框,告诉你“操作终止。”单击确定(OK)按钮,则卸载整个页面,继而显示一张空白屏幕;此时要进行调试非常困难。下面的示例将会导致操作终止错误。
<!DOCTYPE html>
<html>
<head>
	<title>Operation Aborted Example</title>
</head>
<body>
	<p>The following code should cause an Operation Aborted error in IE versions prior to 8.</p>
	<div>
		<script type="text/javascript">
			document.body.appendChild(document.createElement("div"));
		</script>
	</div>
</body>
</html>

这个例子中存在的问题是:JavaScript 代码在页面尚未加载完毕时就要修改document.body,而且<script>元素还不是<body>元素的直接子元素。准确一点说,当<script>节点被包含在某个元素中,而且JavaScript 代码又要使用appendChild()、innerHTML 或其他DOM 方法修改该元素的父元素或祖先元素时,将会发生操作终止错误(因为只能修改已经加载完毕的元素)。

要避免这个问题,可以等到目标元素加载完毕后再对它进行操作,或者使用其他操作方法。例如,为document.body 添加一个绝对定位在页面上的覆盖层,就是一种非常常见的操作。通常,开发人员都是使用appendChild()方法来添加这个元素的,但换成使用insertBefore()方法也很容易。因此,只要修改前面例子中的一行代码,就可以避免操作终止错误。

<!DOCTYPE html>
<html>
<head>
	<title>Operation Aborted Example</title>
</head>
<body>
	<p>The following code should not cause an Operation Aborted error in IE versions
 prior to 8.</p>
	<div>
		<script type="text/javascript">
			document.body.insertBefore(document.createElement("div"),
			document.body.firstChild);
		</script>
	</div>
</body>
</html>

在这个例子中,新的<div>元素被添加到document.body 的开头部分而不是末尾。因为完成这一操作所需的所有信息在脚本运行时都是已知的,所以这不会引发错误。

除了改变方法之外,还可以把<script>元素从包含元素中移出来,直接作为<body>的子元素。例如:

<!DOCTYPE html>
<html>
<head>
<title>Operation Aborted Example</title>
</head>
<body>
	<p>The following code should not cause an Operation Aborted error in IE versions 
prior to 8.</p>
	<div>
	</div>
	<script type="text/javascript">
		document.body.appendChild(document.createElement("div"));
	</script>
</body>
</html>

这一次也不会发生错误,因为脚本修改的是它的直接父元素,而不再是间接的祖先元素。

在同样的情况下,IE8 不再抛出操作终止错误,而是抛出常规的JavaScript 错误,带有如下错误消息:HTML Parsing Error: Unable to modify the parent container element before the childelement is closed (KB927917).

不过,虽然浏览器抛出的错误不同,但解决方案仍然是一样的。

  • 无效字符:根据语法,JavaScript 文件必须只包含特定的字符。在JavaScript 文件中存在无效字符时,IE 会抛出无效字符(invalid character)错误。所谓无效字符,就是JavaScript 语法中未定义的字符。例如,有一个很像减号但却由Unicode 值8211 表示的字符(\u2013),就不能用作常规的减号(ASCII 编码为45),因为JavaScript 语法中没有定义该字符。这个字符通常是在Word 文档中自动插入的。如果你的代码是从Word 文档中复制到文本编辑器中,然后又在IE 中运行的,那么就可能会遇到无效字符错误。其他浏览器对无效字符做出的反应与IE 类似,Firefox 会抛出非法字符(illegal character)错误,Safari 会报告发生了语法错误,而Opera 则会报告发生了ReferenceError(引用错误),因为它会将无效字符解释为未定义的标识符。
  • 未找到成员:如前所述,IE 中的所有DOM 对象都是以COM 对象,而非原生JavaScript 对象的形式实现的。这会导致一些与垃圾收集相关的非常奇怪的行为。IE 中的未找到成员(Member not found)错误,就是由于垃圾收集例程配合错误所直接导致的。

具体来说,如果在对象被销毁之后,又给该对象赋值,就会导致未找到成员错误。而导致这个错误的,一定是COM 对象。发生这个错误的最常见情形是使用event 对象的时候。IE 中的event 对象是window 的属性,该对象在事件发生时创建,在最后一个事件处理程序执行完毕后销毁。假设你在一个闭包中使用了event 对象,而该闭包不会立即执行,那么在将来调用它并给event 的属性赋值时,就会导致未找到成员错误,如下面的例子所示。

document.onclick = function(){
    var event = window.event;
    setTimeout(function(){
        event.returnValue = false; //未找到成员错误
    }, 1000);
};

在这段代码中,我们将一个单击事件处理程序指定给了文档。在事件处理程序中,window.event被保存在event 变量中。然后,传入setTimeout()中的闭包里又包含了event 变量。当单击事件处理程序执行完毕后,event 对象就会被销毁,因而闭包中引用对象的成员就成了不存在的了。换句话说,由于不能在COM 对象被销毁之后再给其成员赋值,在闭包中给returnValue 赋值就会导致未找到成员错误。

  • 未知运行时错误:当使用innerHTML 或outerHTML 以下列方式指定HTML 时,就会发生未知运行时错误(Unknown runtime error):一是把块元素插入到行内元素时,二是访问表格任意部分(<table>、<tbody>等)的任意属性时。例如,从技术角度说,<span>标签不能包含<div>之类的块级元素,因此下面的代码就会导致未知运行时错误:
span.innerHTML = "<div>Hi</div>"; //这里,span 包含了<div>元素

在遇到把块级元素插入到不恰当位置的情况时,其他浏览器会尝试纠正并隐藏错误,而IE 在这一点上反倒很较真儿。

  • 语法错误:通常,只要IE 一报告发生了语法错误(syntax error),都可以很快找到错误的原因。这时候,原因可能是代码中少了一个分号,或者花括号前后不对应。然而,还有一种原因不十分明显的情况需要格外注意。

如果你引用了外部的JavaScript 文件,而该文件最终并没有返回JavaScript 代码,IE 也会抛出语法错误。例如,<script>元素的src 特性指向了一个HTML 文件,就会导致语法错误。报告语法错误的位置时,通常都会说该错误位于脚本第一行的第一个字符处。Opera 和Safari 也会报告语法错误,但它们会给出导致问题的外部文件的信息;IE 就不会给出这个信息,因此就需要我们自己重复检查一遍引用的外部JavaScript 文件。但Firefox 会忽略那些被当作JavaScript 内容嵌入到文档中的非JavaScript 文件中的解析错误。

在服务器端组件动态生成JavaScript 的情况下,比较容易出现这种错误。很多服务器端语言都会在发生运行时错误时,向输出中插入HTML 代码,而这种包含HTML 的输出很容易就会违反JavaScript语法。如果在追查语法错误时遇到了麻烦,我们建议你再仔细检查一遍引用的外部文件,确保这些文件中没有包含服务器因错误而插入到其中的HTML。

  • 系统无法找到指定资源:系统无法找到指定资源(The system cannot locate the resource specified)这种说法,恐怕要算是IE给出的最有价值的错误消息了。在使用JavaScript 请求某个资源URL,而该URL 的长度超过了IE 对URL最长不能超过2083 个字符的限制时,就会发生这个错误。IE 不仅限制JavaScript 中使用的URL 的长度,而且也限制用户在浏览器自身中使用的URL 长度(其他浏览器对URL 的限制没有这么严格)。IE 对URL路径还有一个不能超过2048 个字符的限制。下面的代码将会导致错误。
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);

在这个例子中,XMLHttpRequest 对象试图向一个超出最大长度限制的URL 发送请求。在调用open()方法时,就会发生错误。避免这个问题的办法,无非就是通过给查询字符串参数起更短的名字,或者减少不必要的数据,来缩短查询字符串的长度。另外,还可以把请求方法改为POST,通过请求体而不是查询字符串来发送数据。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值