一旦你发现一个错误,就可以清除它。不幸的是,发现它们的
出处并不总是很容易 - 你的大部分调试时间只是花在指出错 误的位置。 最可靠的方法之一是在你的代码中加入一些简单的语句打印出 正在发生什么。假如你在下面的两段程序中发现一个问题: function getName() { var first_name = prompt("what's your first name?",""); var last_name = prompt("what's your last name?",""); var the_name = first_name + " " + last_name; } function theGreeting() { var the_name = ""; the_name = getName(); if (the_name == "Dave Thau") { alert("Hello, oh greatest one!"); } else { alert("Ahoy palloi!"); } } 运行这段程序,看看你是否能发现出了什么问题(Netscape 3.x 的用户可能会遇到一些错误检查的问题,这是由于Netscape 3.x 本身的原因,以下的Javascript例子与此相同)。如果你在警告 对话框中随意输入一些名字,你会得到问候:“Ahoy palloi!”。 但是,如果你在第一个提示对话框中输入“Dave”,在第二个中 输入“Thau”,你应该得到“Hello,oh greatest one!”这条 信息。然而,你还是只得到“Ahoy palloi!”这条信息。很明显, 函数中出了错误。在这个简单的例子程序中,你或许只是查看 JavaScript代码就能发现错误。然而,当你的代码变得越来越复 杂时,只靠目测来发现错误会变得愈加困难。 如果JavaScript没能捕获你的错误,你也没有通过查看代码发现 错误,有时打印出变量会对你有所帮助。最简单的方法是象下面 这样使用一个alert(): // theGreeting gets a name using getName, then presents // one or two alert boxes depending on what the name is //function getName() { var first_name = prompt("what's your first name?",""); var last_name = prompt("what's your last name?",""); var the_name = first_name + " " + last_name; alert("in getName, the_name is: " + the_name); } // theGreeting gets a name using getName, then presents // one of two alert boxes depending on what the name is // function theGreeting() { var the_name = ""; the_name = getName(); alert("after getName, the_name = " + the_name); if (the_name == "Dave Thau") { alert("hello, oh greatest one!"); }else{ alert("ahoy palloi!"); } } 请注意我们已经在所有重要的地方加入警告语句。现在试着运行 这段程序。如果你输入名称“Dave”和“Thau”,你会注意到第 一个警告显示“in getName, the_name is: Dave Thau,”,但 是第二个警告显示“after getName, the_name = undefined,”, 这就告诉你在getName()的最后一行事情变得糟糕起来。不知何 故,the_name只在函数存在前正确,但是theGreeting没有给变 量the_name正确赋值。当你写的函数能正确运行,但返回值出现 问题时,你最先要做的就是检查你是否的确让其返回了一个值。 很明显,问题就出在这儿。getName()函数指出了名称,但没有 返回它。所以我们应把语句“return the_name;”加到函数末尾。 把一些警告对话框加入你的代码中是很有帮助的。不幸的是,每 隔一行就按一次“OK”也是一种痛苦。 不用警告对话框也能调试代码。一种选择是把调试信息写到窗体 的一个文本区内。另一种可能是把调试信息写在另一个窗口上。 这儿有一个把调试信息写在下面文本区的调试代码的例子。 使你的调试经历更舒适的第三个诀窍是这样的:创建不同的调试 等级,然后设置“调试”变量。下面就是在此页上运行的 JavaScript代码: // debug can be either none, alert, or textarea // depending on the kind of debugging I want to do // var debug = "none"; // function getName gets a first and last name, // concatenates them with a space in between, // and returns the name function getName() { var first_name = prompt("what's your first name?",""); var last_name = prompt("what's your last name?",""); var the_name = first_name + " " + last_name; var error_message = "in getName, the_name is: " + the_name; doError("in getName, the_name is: " + the_name); } // theGreeting gets a name using getName, then presents // one of two alert boxes depending on what the name is // function theGreeting() { var the_name = ""; the_name = getName(); doError("after getName, the_name = " + the_name); if (the_name == "Dave Thau") { alert("hello, oh greatest one!"); } else { alert("ahoy palloi!"); } } // doError is the error handling routine // depending on the type of debug message // it presents either no debug message, an alert // or puts the message in a textarea // function doError(the_message) { if (debug == "alert") { alert(the_message); } else if (debug == "textarea") { window.document.the_form.the_text.value += the_message + "<br>\n"; } } 请注意我已经定义了一个叫“debug”的变量,它可以是 “none”,“alert”或“textarea”。于是当我想产生一个错 误信息时,我把它送给函数doError(),此函数可能什么也不做, 或者显示一个消息对话框,或者把消息粘贴到一个文本区中,这 取决于我怎样设置调试变量。当你想同时看到多条错误信息时, 你可以设置调试变量为“textarea”。当你准备把你的代码显示 给全世界时,你只需要把调试变量设为“none”,于是错误信息 将不再出现,这样可以省去发现和清除所有调试语句的麻烦。 通常,程序员可以创建不同的调试等级,如“none”,“brief” 和“extreme”。“brief”将打印一些调试信息,“extreme” 将打印大量调试信息,“none”当然不会打印任何信息。 如果你按此方法建立你的调试系统,你可以在编码时把调试等级 设为“brief”,在准备公布你的JavaScript时把调试变量设为 “none”。如果有不可思议的事情发生,并且你不知道去哪儿发 现问题,你可以把调试等级设为“extreme”,然后流览所有的 调试信息直到发现可疑的地方。 好了,调试系统就讨论到这儿。现在让我们看看JavaScript编码 器产生的一般性错误。>>多数错误只是无聊的语法错误。记住关闭那些引号,大括号和
小括号会花费很长时间,不过幸运的是JavaScript自动错误检 测器能捕获大部分此类错误。虽然JavaScript错误检测器随着 日渐复杂的流览器而不断完善,但是一些错误仍会溜走。下面 是一些需要留意的常见错误: 混淆变量名或函数名 大写和复数变量和函数名产生的错误令人烦恼地经 常出现,有时JavaScript错误检测器不能捕获它们。 通过建立和坚持使用一种对变量和函数的命名协定, 会大大减少这些麻烦的数量。例如,我全部用小写 字母定义变量,并用下划线代替空格 (my_variable, the_data, an_example_variable),用内置符号表 示函数 (addThreeNumbers(), writeError()等)。 我避免使用任何复数,因为我总是忘记那些变量是 不是复数。 偶然地使用了保留字 一些字不能作为变量名,因为它们已经被JavaScript 使用。例如,不能定义一个叫“if”的变量,因为 它实际上是JavaScript的一部分 - 如果使用“if”, 你会遇到各种麻烦。当你因为使用命名为“if”的 变量而变得疯狂时,一个叫做“document”的变量 是很诱人的。不幸的是,“document”是一个 JavaScript对象。另一个经常遇到的问题是把变量 命名为“name”(窗体元素有“names”属性)。把 变量命名为“name”不会总出问题,只是有时 - 这会更使人迷惑 - 这就是避免使用“name”变量的 原因。 不幸的是,不同的流览器有不同的保留字,所以没 有办法知道该回避哪些字。最安全的办法是避免使 用已经成为JavaScript一部分的字和HTML使用的字。 如果你因为变量遇到问题,并且不能发现哪儿错了, 试着把变量改个名字。如果成功了,你或许就避开 了保留字。 记住在逻辑判断时应该用两个等号 一些流览器能捕获这种错误,有些却不能。这是一 种非常常见的错误,但是如果流览器不能替你指 出来,你就很难发现。下面是一个这种错误的例子: var the_name = prompt("what's your name?", ""); if (the_name = "the monkey") { alert("hello monkey!"); } else { alert("hello stranger."); } 这段代码将产生“hello monkey!”警告对话框 - 不管你在提示里敲的是什么 - 这不是我们希望的。 原因是在if-then语句中只有一个等号,这句话告 诉JavaScript你想让一件事等于另一件。假设你在 提示中敲的是“robbie the robot”。最开始,变 量the_name的值是“robbie the robot”,但是随 后if语句告诉JavaScript你想把the_name设为 “the monkey.”。于是JavaScript很高兴地执行你 的命令,送一个“true”消息给if-then语句,结果 警告对话框每次都出现“hello monkey!”。 这种阴险的错误会使你发疯,所以注意使用两个 等号。 偶然给变量加上了引号,或忘了给字符串加引号 我不时遇到这个问题。JavaScript区分变量和字符 串的唯一方法是:字符串有引号,变量没有。下面 有一个明显的错误: var the_name = 'koko the gorilla'; alert("the_name is very happy"); 虽然the_name是一个变量,但是程序还会产生一个 提示“the_name is very happy,”的警告对话框。 这是因为一旦JavaScript看见引号包围着某些东西 就不再考虑它,所以当你把the_name放在引号里, 你就阻止了JavaScript从内存中查找它。 下面是一个不太明显的此类错误的扩展(我们已经 在第三天的课程里见过): function wakeMeIn3() { var the_message = "Wake up! Hey! Hey! WAKE UP!!!!"; setTimeout("alert(the_message);", 3000); } 这里的问题是你告诉JavaScript三秒后执行alert (the_message)。但是,三秒后the_message将不再 存在,因为你已经退出了函数。这个问题可以这样 解决: function wakeMeIn3() { var the_message = "Wake up!"; setTimeout("alert('" + the_message+ "');", 3000); } 把the_message放在引号外面,命令“alert('Wake up!');”由setTimeout预定好,就可以得到你想 要的。 这只是一些可能在你的代码中作祟的很难调试的 错误。一旦发现了它们,就有不同的或好或差的方 法来改正错误。你很幸运,因为你能从我的经验和 错误中获益 第页:修正错误 找到错误,有时侯虽然很难,却只是第一步。然后你必须清除 错误。下面是一些在清除错误时应该做的一些事: 首先拷贝你的程序 有些错误很难清除。实际上,有时在根除错误时, 你会破坏整个程序 - 一个小错误使你疯狂。在开始 调试前保存你的程序是确保错误不会利用你的最好 方法。 一次修正一个错误 如果你知道有好几个错误,应该修正一个,检验其 结果,再开始下一个。一次修正许多错误而不检验 你的工作只会招致更多的错误。 警惕迷惑性错误 有时你知道存在一个错误,但不真正知道为什么。 假设有一个变量“index”,由于某种原因“index” 总比你期望的小1。你可以做下面两件事中的一件: 在那儿坐一会儿,解决它为什么变小了,或只是耸 耸肩;在使用“index”之前加1,然后继续进行。后 一种方法称为迷惑编程。当你开始思考“究竟是怎 么了 - 为什么index是2而不是3呢?好吧...我现在 先让它正常工作,以后再修改错误。”时,你正在 把一块护创膏布贴到一处潜在的硬伤上。 迷惑编程可能在短期内有用,但是你可以看到长期 的厄运 - 如果你没有完全理解你的代码到可以真正 清除错误的程度,那个错误将会回来困扰你。它或 者以另一种你不能解决的怪异错误的方式回来,或 者当下一个可怜的被诅咒的灵魂读你的代码时,他 会发现你的代码非常难以理解。 寻找小错误 有时侯,对程序员来说,剪切和粘贴代码的能力是 一种很坏的事。通常,你会在一个函数中写一些 JavaScript代码,然后把它们剪切和粘贴到另一个 函数中。如果第一个函数有问题,那么现在两个函 数都有问题。我并不是说你不应该剪切和粘贴代码。 但是错误会以某种方式繁殖,如果你发现了一个 错误,你就应该寻找与其相似的其它错误。(或者 在制作它的若干版本之前确切知道会发生什么。) 变量名拼写错误在一段JavaScript代码中会突然多 次出现 - 在一个地方把the_name错拼成teh_name, 你就有机会在其它地方发现这个错误。 如果所有其它的方法都失败了 如果你正坐在那儿盯着一个错误,并且不能指出是 怎么回事(或者根本没有发现错误,但是因为程序 不能正确运行,你知道存在错误),你最好从计算 机前走开。去读一本书,在角落散散步,或者拿一 杯可口的饮料 - 做些事,任何事,但不要去想程序 或问题。这种技术在某种情况下叫做“酝酿”,效 果非常好。在你稍做休息和放松后,再试着找出 错误。你会得到一幅比较清晰的景象。“酝酿”起 作用是因为它使你从思维混乱中解脱出来。如果沿 着一条错路走太远,你有时会发现无法转身。这种 情况下最好开辟一条新路。我知道这会令人发火, 但确实有效。真的! 如果上面的方法还不成功... 请求别人的帮助。有时你的思想会形成定式,只有 换一种眼光才能洞察问题之所在。在结构化编程环 境中,程序员们定期地互相复查别人的代码。这可 以适当地叫做“代码复查”,不仅可以帮助消除 错误,还可以得到更好的代码。不要怕把你的 JavaScript代码给别人看,它会使你成为更好的 JavaScript程序员。 但是消除错误的绝对最好的办法是... 一开始就创建没有错误的代码。>>编好程序的关键是程序是写给人的,不是写给计算机的。如
果你能明白其他人或许会阅读你的JavaScript,你就会写更 清晰的代码。代码越清晰,你就越不容易犯错误。机灵的代 码是可爱的,但就是这种机灵的代码会产生错误。最好的经 验法则是KISS,即Keep It Simple,Sweetie(保持简单,可爱)。 另一个有帮助的技术是在写代码之前作注释。这迫使你在动 手之前先想好。一旦写好了注释,你就可以在其下面写代码。 下面是一个用这种方法写函数的例子: 第一步:写注释 //function beSassy() // beSassy asks for a user's name, chooses a random // insult and returns an alert box with the user's name and the // insult. function beSassy() { // first write a list of insults // // next get the user's name // // then choose a random insult // // finally, return the personalized sass // } 第二步:填充代码 //function beSassy() // beSassy asks for a user's name, chooses a random // insult and returns an alert box with the user's name and the // insult. function beSassy() { // first write a list of insults // var the_insult_list = new Array; the_insult_list[0] = "your shoe lace is untied"; the_insult_list[1] = "your mama!"; the_insult_list[2] = "it's hard to be insulting"; // next get the user's name // var the_name = prompt("What's your name?", ""); // then choose a random insult // var the_number = Math.random() * 5; var insult_number = parseInt(the_number); var the_insult = the_insult_list[insult_number]; // finally, return the personalized sass // alert("Hey " + the_name + " " + the_insult); } 这种先写注释的策略不仅迫使你在写代码前思考,而且 使编码的过程看起来容易些 - 通过把任务分成小的, 易于编码的各个部分,你的问题看起来就不太象珠穆朗 玛峰,而象一群令人愉悦的起伏的小山。 最后... 总以分号结束你的每一条语句。 虽然并不是严格必需,你应该养成以分号结束每一条语 句的习惯,这样可以避免这行后面再有代码。忘了加 分号,下一行好的代码会突然产生错误。 把变量初始化为“var”,除非你有更好的理由不这样做。 用“var”把变量局域化可以减少一个函数与另一个不相 关函数相混淆的机会。 好了,既然你已经知道了如何编码,下面就让我们学习怎样使 你的JavaScript快速运行。>>一旦你的JavaScript能运行,你就会想到使其运行得更快。
在讲解加速代码的方法之前,让我先讲讲“80/20规则”: 百分之八十的优化是由最初百分之二十的工作所完成的。竭 力实现剩余百分之二十的速度优化是一种巨大的痛苦,而且 经常导致完全不能读和难以管理的代码。简言之,如果你的 JavaScript运行得很慢,你可以用很多简单的方法来加速它, 但是除非你的代码确实运行得很慢,我不会对它进行再优化。 下面是一些使你的代码轻松运行的方法。 限制循环内的工作量 程序运行慢的最常见原因是循环内的重复工作。如果一 条命令只需要执行一次,就没有必要把它放在循环内。 例如: var index = 0; while (index <10) { var the_date = new Date(); var the_day = the_date.getDay(); var the_name = prompt("what's the kid's name? " ,""); alert("On " + the_day + " " + the_name + " is a very special person."); index++; } 此程序循环执行10次。每次得到当天的日期,询问小孩 的名字,然后打印出“On Monday,so-and-so is a very special person.”。 但是日期是不会改变的,总是今天。所以没有必要把前 两行放在循环中。把它们从循环中拿出来,让其只执行 一次而不是10次,这样会节省时间: var index = 0; var the_date = new Date(); var the_day = the_date.getDay(); while (index <10) { var the_name = prompt("what's the kid's name? " ,""); alert("On " + the_day + " " + the_name + " is a very special person."); index++; } 定制if-then-else语句,按最可能到最不可能的顺序 因为if-then-else语句在遇到条件为真时结束,你可以 通过把最有可能的条件放到最开始来减少需要判断的语 句的数量。例如: var pet = prompt("what kind of pet do you have?", ""); if (pet == "cat") { doCatStuff(); } else if (pet == "dog") { doDogStuff(); } else if (pet == "bird") { doBirdStuff(); } else if (pet == "lizard") { doLizardStuff(); } 一般来说,程序中的if子句比从lizard到dog需要执行的 逻辑判断要少。 最小化重复执行的表达式 如果你发现需要重复计算一个特定的表达式,如 var pi=22/7,只计算一次并把它放在一个全局变量中或 许是个好主意。例如,不象下面程序这样: function theArea(radius) { var pi = 22/7; var area = pi * radius * radius; return area; } function theCircumference(radius) { var pi = 22/7; var circumference = 2 * pi * radius; return circumference; } 而是这样做: var pi = 22/7; function theArea(radius) { var area = pi * radius * radius; return area; } function theCircumference(radius) { var circumference = 2 * pi * radius; return circumference; } 我知道我在用一个全局变量,我也说过这不是一个好主意。 然而,一些数字,如pi,其值在程序中永远不会改变,是 此规则的特例。通过只计算pi一次,可以省去额外的计算。 或许时间上的一些小的节省,累加起来会很管用。 如果你发现代码运行很慢,你只要注意一些事情。这些都 很明显,但是当你发现你经常忽略象这样简单的优化技巧 时,你会很吃惊。 还有,我的朋友,让我们结束今天的课程,这也是整个 JavaScript高级教程的结束。如果你已经进行到这儿, 并且你至少读过过去五天课程中的一半,那么你已经看 过很多JavaScript代码了。实际上,如果你能理解跨越 第一部分和第二部分的10课的大部分内容,你就可以很 安全地把自己称为“JavaScript助手”。通往神秘真知 的路就在你的脚下