17.3 调试技术
在不那么容易找到JavaScript 调试程序的年代,开发人员不得不发挥自己的创造力,通过各种方法来调试自己的代码。结果,就出现了以这样或那样的方式置入代码,从而输出调试信息的做法。其中,最常见的做法就是在要调试的代码中随处插入alert()函数。但这种做法一方面比较麻烦(调试之后还需要清理),另一方面还可能引入新问题(想象一下把某个alert()函数遗留在产品代码中的结果)。如今,已经有了很多更好的调试工具,因此我们也不再建议在调试中使用alert()了。
17.3.1 将消息记录到控制台
IE8、Firefox、Opera、Chrome 和Safari 都有JavaScript 控制台,可以用来查看JavaScript 错误。而且,在这些浏览器中,都可以通过代码向控制台输出消息。对Firefox 而言,需要安装Firebug(www.getfirebug.com),因为Firefox 要使用Firebug 的控制台。对IE8、Firefox、Chrome 和Safari 来说,则可以通过console 对象向JavaScript 控制台中写入消息,这个对象具有下列方法。
- error(message):将错误消息记录到控制台
- info(message):将信息性消息记录到控制台
- log(message):将一般消息记录到控制台
- 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()函数更可取,因为警告框会阻断程序的执行,而在测定异步处理对时间的影响时,使用警告框会影响结果。
17.3.2 将消息记录到当前页面
另一种输出调试消息的方式,就是在页面中开辟一小块区域,用以显示消息。这个区域通常是一个元素,而该元素可以总是出现在页面中,但仅用于调试目的;也可以是一个根据需要动态创建的元素。
例如,可以将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 及更早版本或其他浏览器中十分有用。
与把错误消息记录到控制台相似,把错误消息输出到页面的代码也要在发布前删除。
17.3.3 抛出错误
如前所述,抛出错误也是一种调试代码的好办法。如果错误消息很具体,基本上就可以把它当作确定错误来源的依据。但这种错误消息必须能够明确给出导致错误的原因,才能省去其他调试操作。来看下面的函数:
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()函数可以减少抛出错误所需的代码量,而且也比前面的代码更容易看懂。