JavaScript-测试入门指南-全-

JavaScript 测试入门指南(全)

原文:zh.annas-archive.org/md5/BA61B4541373C00E412BDA63B9F692F1

译者:飞龙

协议:CC BY-NC-SA 4.0

序言

在今天 Web 2.0 的世界中,JavaScript 是网络开发的重要部分。尽管市场上有很多 JavaScript 框架,但学会在没有框架帮助的情况下编写、测试和调试 JavaScript,会使你成为一个更好的 JavaScript 开发者。然而,测试和调试可能既耗时又繁琐,令人痛苦。这本书将通过提供各种测试策略、建议和工具指南,让你的测试变得顺畅和简单,从而减轻你的烦恼。

本书采用易于跟随的、逐步的教学风格,以便最大化你的学习。你将首先学习到你作为 JavaScript 开发者最常遇到的不同的错误类型。你还将通过我们易于跟随的示例,学习到 JavaScript 的最重要特性。

随着学习,你将通过验证学会如何编写更好的 JavaScript 代码;仅仅学习如何编写验证过的代码就会极大地提高你作为 JavaScript 开发者的能力,最重要的是,帮助你编写运行得更好、更快、且错误更少的 JavaScript 代码。

随着我们的 JavaScript 程序变得更大,我们需要更好的方法来测试我们的 JavaScript 代码。你将学习到各种测试概念以及如何在你的测试计划中使用它们。之后,你将学习如何为你的代码实施测试计划。为了适应更复杂的 JavaScript 代码,你将了解更多关于 JavaScript 的内置特性,以便识别和捕捉不同类型的 JavaScript 错误;这些信息有助于找到问题的根源,从而采取行动。

最后,你将学习如何利用内置浏览器工具和其他外部工具来自动化你的测试过程。

本书内容涵盖

第一章,什么是 JavaScript 测试?,涵盖了 JavaScript 在网络开发中的作用以及 HTML 和 CSS 等基本构建模块。它还涵盖了您最常见到的错误类型。

第二章,JavaScript 中的即兴测试和调试,介绍了我们为何要为我们的 JavaScript 程序进行即兴测试,并通过编写一个简单的程序,介绍 JavaScript 最常用的特性。这个程序将作为一个例子,用于执行即兴测试。

第三章,语法验证,介绍了如何编写验证过的 JavaScript。完成这一章后,您将提高作为 JavaScript 开发者的技能,同时理解更多关于验证在测试 JavaScript 代码中的作用。

第四章,规划测试,介绍了制定测试计划的重要性,以及我们在执行测试时可以使用的策略和概念。本章还涵盖了各种测试策略和概念,我们将通过一个简单的测试计划来看看制定测试计划意味着什么。

第五章,将测试计划付诸行动,紧接着第四章,我们应用我们已经制定的简单测试计划。最重要的是,我们将通过揭示错误、记录并应用我们在第四章中学到的理论来修复错误。

第六章,测试更复杂的代码,介绍了测试我们代码的高级方法。测试代码的一种方式是使用 JavaScript 提供的内置错误对象。本章还介绍了如何使用控制台日志,如何编写自己的消息,以及如何捕获错误。

第七章,调试工具,讨论了我们的代码变得太大而复杂,无法使用手动方法进行测试的情况。我们现在可以利用市场上流行浏览器提供的调试工具,包括 Internet Explorer 8、FireFox 3.6、Chrome 5.0、Safari 4.0 和 Opera 10.

第八章,测试工具,介绍了你可以使用免费、跨浏览器和跨平台的测试工具来自动化你的测试。它还涵盖了如何测试你的界面,自动化测试,以及进行断言和基准测试。

本书所需准备

像 Notepad++这样的基本文本编辑器。

浏览器,如 Internet Explorer 8、Google Chrome 4.0、Safari 4.0 和更新版本、FireFox 3.6。

JavaScript 版本 1.7 或更高版本。

其他涉及到的软件还包括 Sahi、JSLitmus 和 QUnit。

本书面向人群

本书适合初学者 JavaScript 程序员,或者可能只有少量使用 JavaScript 经验的初学者程序员,以及希望学习 HTML 和 CSS 的人。

约定

在本书中,你会发现几个频繁出现的标题。

为了清楚地说明如何完成一个程序或任务,我们使用:

动手时间—标题

  1. 行动 1

  2. 行动 2

  3. 行动 3

说明往往需要一些额外的解释,以便它们有意义,所以它们后面跟着:

刚才发生了什么?

这个标题解释了你刚刚完成的工作或指令的工作原理。

你还会发现书中有一些其他的学习辅助工具,包括:

互动测验—标题

这些是旨在帮助你测试自己理解程度的简短多项选择题。

试一试英雄—标题

这些部分设定了实际挑战,并给你提供了一些实验你学到的内容的点子。

你还会发现有一些文本样式,用于区分不同类型的信息。以下是一些这些样式的示例及其含义解释。

文本中的代码词汇如下所示展示:"我们可以通过使用include指令包含其他上下文。"

一段代码如下所示:

<input type="submit" value="Submit"
onclick="amountOfMoneySaved(moneyForm.money.value)" />
</form>
</body>
</html>

当我们要引您注意代码块中的某个特定部分时,相关的行或项目会被加粗:

function changeElementUsingName(a){
var n = document.getElementsByName(a); 
for(var i = 0; i< n.length; i++){
n[i].setAttribute("style","color:#ffffff");
}
}

新术语重要词汇以粗体显示。例如,您在屏幕上、菜单或对话框中看到的词汇,在文本中会这样显示:"点击下一页按钮将您带到下一页"。

注意

警告或重要说明会以这样的盒子形式出现。

注意

技巧和窍门会这样展示。

读者反馈

我们总是欢迎读者的反馈。告诉我们您对这本书的看法——您喜欢或可能不喜欢的地方。读者的反馈对我们来说非常重要,以便我们开发出您真正能从中获得最大收益的标题。

要给我们发送一般性反馈,只需发送一封电子邮件到<feedback@packtpub.com>,并在您消息的主题中提到书名。

如果您需要一本书,并希望我们出版,请通过www.packtpub.com上的建议一个标题表单给我们发个消息,或者发送电子邮件到<suggest@packtpub.com>

如果您在某个主题上有专业知识,并且您有兴趣撰写或为书籍做出贡献,请查看我们作者指南中的www.packtpub.com/authors

客户支持

现在您已经成为 Packt 书籍的骄傲拥有者,我们有很多事情可以帮助您充分利用您的购买。

注意

下载本书的示例代码

您可以从您在www.PacktPub.com账户上下载您购买的所有 Packt 书籍的示例代码文件。如果您在其他地方购买了这本书,您可以访问www.PacktPub.com/support并注册,以便将文件直接通过电子邮件发送给您。

错误

虽然我们已经尽一切努力确保我们内容的准确性,但是错误仍然会发生。如果您在我们的某本书中发现了一个错误——可能是文本或代码中的错误——如果您能向我们报告,我们将非常感激。这样做,您可以节省其他读者不必要的挫折,并帮助我们改进本书的后续版本。如果您发现任何错误,请通过访问www.packtpub.com/support,选择您的书,点击让我们知道链接,并输入您错误的详情。一旦您的错误得到验证,您的提交将被接受,错误将被上传到我们的网站,或添加到该标题的错误部分现有的错误列表中。您可以通过从www.packtpub.com/support选择您的标题来查看现有的错误。

盗版

互联网上侵犯版权材料的问题持续存在,遍布所有媒体。在 Packt,我们对保护我们的版权和许可非常重视。如果您在互联网上以任何形式发现我们作品的非法副本,请立即提供给我们位置地址或网站名称,这样我们可以采取补救措施。

请通过<copyright@packtpub.com>联系我们,并提供疑似侵权材料的链接。

我们感谢您在保护我们的作者,以及我们为您提供有价值内容的能力方面所提供的帮助。

问题

如果您在阅读本书的任何方面遇到问题,可以通过<questions@packtpub.com>联系我们,我们会尽力解决。

第一章:什么是 JavaScript 测试?

首先,请允许我欢迎你拿起这本书。如果你拿起这本书,我会假设你对 JavaScript 测试感兴趣。你很可能会经历 JavaScript,希望通过学习如何测试你的 JavaScript 程序来提高你的技能。

JavaScript 通常与网络浏览器关联,是创建网页上交互元素的关键工具之一。然而,与 PHP、Python 等服务器端语言不同,JavaScript 通常默默失败(尽管像 IE 这样的浏览器有时会提供警告信息);没有错误信息告诉你发生了错误。这使得调试变得困难。

通常,我们将学习关于 JavaScript 测试的基本构建块。这包括HTML(超文本标记语言)CSS(层叠样式表)和 JavaScript 的基础知识。之后,你将学习各种技术使 HTML、CSS 和 JavaScript 协同工作;这些技术是你在其他章节中要学习的内容的基础。

更具体地说,我们将在本章中学习以下内容:

  • HTML、CSS 和 JavaScript 的基础

  • HTML、CSS 和 JavaScript 的语法

  • 如何使用 CSS 和 JavaScript 选择 HTML 元素?

  • 网页为什么需要在没有 JavaScript 的情况下工作?

  • 测试是什么,为什么你需要测试?

  • 什么是错误?

  • JavaScript 错误的类型

本章中示例很简单——它们旨在让你看到主要语法和正在使用的内置方法或函数。在本章中,代码将最少;你会被要求输入代码。之后,我们将简要回顾代码示例,看看发生了什么。

带着这个想法,我们将立即开始。

JavaScript 在网页中占有什么位置?

每个网页都由以下属性组成——内容、外观和行为。这些属性分别由超文本标记语言(HTML)、层叠样式表(CSS)和 JavaScript 控制。

HTML 内容

HTML 代表超文本标记语言。它是网页的主导标记语言。通常,它控制网页的内容。HTML 通过<head><body><form><p>等语义标记来定义网页(或 HTML 文档),以控制标题、文档主体、表单、段落等。你可以把 HTML 看作是一种描述网页应该看起来怎样的方式。

HTML 使用标记标签,这些标签通常成对出现。HTML 的语法如下:

<name-of-html-tag> 你的内容可以在这里括起来 </name-of-html-tag>

请注意,HTML标签由尖括号括起来;HTML标签对以<name-of-html-tag>开头,以</name-of-html-tag>结尾。这个第二个HTML标签被称为闭合标签,它们在HTML标签前有一个斜杠。

以下是一些常见的 HTML 元素:

  • <head> </head>

  • <body> </body>

  • <title> </title>

  • <p> </p>

  • <h1> </h1>

  • <a> </a>

要查看完整的 HTML 元素列表,请访问www.w3schools.com/tags/default.asp

行动时间—构建一个 HTML 文档

我们将通过使用上面看到的 HTML 标签和语法来创建一个 HTML 文档。(你在这里看到的示例可以在Chapter 1的源代码文件夹中找到,文档名为chapter1-common-html.html

  1. 首先,打开你最喜欢的文本编辑器或工具,比如微软记事本,然后创建一个新文档。

  2. 将以下代码输入到你的新文档中并保存。

    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
    <html >
    <head>
    <title>This is a sample title</title>
    </head>
    <body>
    <h1>This is header 1</h1>
    <h2>This is header 2</h2>
    <h3>This is header 3</h3>
    <p>This is a paragraph. It can be styled by CSS</p>
    <hr>
    <div style="position:absolute; background-color:black; color:#ffffff;top:10px;right:10px;border:solid 3px yellow; height:200px; width:200px;">Your content here</div>
    <div>
    <div>I am enclosed within a <i>div</i> tag. And it can be styled on a document level.
    <ol>
    <li>This is an ordered list and it is centered</li>
    <li>apple</li>
    <li>orange</li>
    <li>banana</li>
    </ol>
    <ul>
    <li>This is an unordered list. And it can be styled by CSS.</li>
    <li>apple</li>
    <li>orange</li>
    <li>banana</li>
    </ul>
    </div>
    <div>I am enclosed within a <i>div</i> tag. And it can be styled by CSS.
    <ol>
    <li>This is an ordered list and it is centered</li>
    <li>apple</li>
    <li>orange</li>
    <li>banana</li>
    </ol>
    <ul>
    <li>This is an unordered list. And it can be styled by CSS</li>
    <li>apple</li>
    <li>orange</li>
    <li>banana</li>
    </ul>
    <a href="#">This is a link. And it can be styled by CSS </a>
    </div>
    </div>
    </body>
    </html>
    
    
  3. 最后,打开浏览器中的文档,你会看到类似以下屏幕截图的示例:

行动时间—构建一个 HTML 文档

  • 请注意右上角的黑色盒子。这是 CSS 工作的一个简单例子。这将在后面解释。

刚才发生了什么?

你刚刚使用更常见的HTML元素和HTML语法创建了一个 HTML 文档。

每个HTML标签都有特定的用途,正如你在浏览器中看到的结果一样。例如,你肯定注意到了<h1>This is header 1</h1>产生了最大的文本,<h2>This is header 2</h2>产生了第二大文本,依此类推。

<ol> </ol>代表有序列表,而<ul> </ul>代表无序列表(带有子弹点的列表)。

你应该注意到了<div> </div>的使用。这用于在 HTML 文档中定义一个部分。然而,<div> </div>的效果和力量只能在本章的下一部分看到。

但是等等,似乎我还没有对 HTML 做一个完整的介绍。没错。我没有介绍 HTML 元素的各个属性。所以让我们快速概述一下。

使用其属性样式化 HTML 元素

通常,HTML 元素的 core 属性包括class, id, styletitle属性。你可以以以下方式使用这些属性:

<div id="menu" class="shaded" style="..." title="Nice menu"> Your
content here </div>

注意,这四个属性可以同时使用。另外,属性的顺序无关紧要。

但我们还没有进行任何样式设计。样式只发生在style属性中。为了看到一个例子,请在之前的代码中<body></body>标签之间输入以下代码。

<div style= "position:absolute; background-color:black;color:#ffffff;
top:10px;right:10px;border:solid 3px yellow; height:200px;
width:200px;">Your content here
</div>

你应该能看到一个 200px 乘 200px 的黑色盒子,带有黄色边框,位于浏览器窗口的右上角(如之前的屏幕截图所示)。以下是只显示黑色盒子的屏幕截图:

使用其属性样式化 HTML 元素

通常,你指定的内联样式会操作style属性的样式属性,以使其看起来是你想要的样子。

只有style属性允许你设置 HTML 元素的样式。但这种方法只用于为元素指定内联样式。

如果你想知道<title>标签的作用,它实际上是一个指定元素额外信息的属性。这通常用在<head>标签内。如果你打开任何包含<title>标签的 HTML 文档,你会在浏览器的标签页或浏览器窗口的标题中找到这个标签的内容。

那么id属性和class属性是什么呢?我们将在下一节简要介绍这些内容。

为 HTML 元素指定 id 和 class 名称

通常,id属性和class属性允许 HTML 元素通过 CSS(我们将在本章后面介绍的层叠样式表)来设置样式。你可以把id属性和class属性看作是一个“名字”,或者是一种识别对应 HTML 元素的方法,这样如果这个“名字”被 CSS 引用,元素就会按照特定的 CSS 样式进行设置。此外,id属性和class属性经常被 JavaScript 引用以操纵一些 DOM(文档对象模型)属性等。

在本书的这一点上,你必须理解一个重要的概念:每个 HTML 元素的id属性在 HTML 文件中必须是唯一的,而class属性则不是。

层叠样式表

CSS 代表层叠样式表。CSS 用于控制网页的布局、外观和格式。CSS 是你指定 HTML 元素风格化外观的方法。通过 CSS,你可以定义字体、颜色、大小,甚至是 HTML 元素的布局。

如果你注意到了,我们还没有向我们的 HTML 文档添加任何形式的 CSS 样式;在前面的截图中,你所看到的是我们浏览器的默认 CSS(除了右上角的黑色盒子),大多数浏览器如果没有定义特定的 CSS,都会有相同的默认 CSS。

CSS 可以是内部的或外部的;内部 CSS 通过<style>标签嵌入 HTML 文档中,而外部 CSS 则通过<link>标签链接,例如:

<link rel="stylesheet" type="text/css" href="style.css">.

通常,使用内部 CSS 被认为是一种不好的实践,应该避免。与内部 CSS 相比,外部 CSS 更受欢迎,因为它可以节省我们的时间和精力,我们只需更改.css文件即可更改网站的设计,而不需要对每个 HTML 文档进行单独的更改。它还有助于提高性能,因为浏览器只需下载一个 CSS 并将其缓存在内存中。

本节最重要的点是 CSS 选择器的使用和 CSS 的语法。

CSS 选择器的工作方式如下:选择 ID 时,ID 的名称前面有一个井号字符。对于类选择器,它前面有一个点号。在你稍后看到的代码中,你会看到同时使用了 ID 和类选择器(在源代码中也进行了注释)。以下是选择器的一个快速预览:

/* this is a id selector */
#nameOfID {
/* properties here*/
}
/* this is a class selector */
.nameOfClass {
/* properties here*/
}

CSS 的语法如下:选择器 { 声明 } . 声明由分号分隔的一组名称或值属性对组成,其中冒号将名称与值分隔开。

记得我们在上一节提到了id属性 和 class属性吗?现在你将看到 CSS 是如何使用id属性和class属性的。

动手时间—使用 CSS 样式化你的 HTML 文档

现在我们将继续样式化我们在上一节创建的 HTML 文档,使用 CSS。为了简单起见,我们将使用内部 CSS。在本节中,你将看到 CSS 语法在实际工作中的应用,以及它是如何通过利用相应 HTML 元素的id属性和class属性来样式化每个 HTML 元素的。注意这个例子中同时使用了idclass选择器。

注意

这个例子完成版本可以在Chapter 1的源代码文件夹中找到,文件名为:chapter1-css-appearance.html

  1. 接着上一个例子,打开你的文本编辑器,在</title>标签后插入以下代码:

    <style type="text/css">
    body{
    background-color:#cccccc;
    }
    /* Here we create a CSS selector for IDs by a name preceded by a hash character */
    #container{
    width:750px; /* this makes the width of the div element with the id 'container' to have a width of 750px */
    height:430px;
    border:1px solid black;solid 1px black;
    }
    /* #[nameOfElement] */
    #boxed1{
    background-color:#ff6600;
    border:2px solid black;
    height:360px;
    width:300px;
    padding:20px;
    float:left;
    margin:10px;
    }
    #boxed2{
    HTML documentstyling, CSS usedbackground-color:#ff6600;
    border:2px solid black;
    height:360px;
    width:300px;
    padding:20px;
    float:left;
    margin:10px;
    }
    #ordered1{
    font-size:20px;
    color:#ce0000;
    text-align:center;
    }
    #unordered1{
    font-size:12px;
    color:#000f00;
    }
    #ordered2{
    font-size:20px;
    color:#ce0000;
    text-align:center;
    }
    #unordered2{
    font-size:12px;
    color:#000f00;
    }
    #unordered2.nice{
    font-size:16px;
    }
    .intro{
    color:black;
    font-weight:bold;
    }
    a:link {color:#FF0000;} /* unvisited link */
    a:visited {color:#00FF00;} /* visited link */
    a:hover {color:#FF00FF;} /* mouse over link */
    a:active {color:#0000FF;} /* selected link */
    </style>
    
    
  2. 在添加上面的 CSS 代码后,你需要为你 的 HTML 元素添加classid属性。你需要添加的内容如下:

    <! - Some code omitted above -- >
    <body>
    <! - Some code omitted -- >
    <p class="intro">This is a paragraph. I am styled by a class called "intro"</p>
    <hr>
    <div id="container">
    <div id="boxed1">I am enclosed within a <i>div</i> tag. And I can be styled on a document level.
    <ol id="ordered1">
    <li>This is an ordered list and it is centered</li>
    <li>apple</li>
    <li>orange</li>
    <li>banana</li>
    </ol>
    <ul id="unordered1">
    <li>This is an unordered list.</li>
    <li>apple</li>
    <li>orange</li>
    <li>banana</li>
    </ul>
    <a class="link" href="#">I am a link that is styled by a class</a>
    </div>
    <div id="boxed2">I am enclosed within a <i>div</i> tag. And I am styled on a local level.
    <ol id="ordered2">
    <li>This is an ordered list and it is centered</li>
    <li>apple</li>
    <li>orange</li>
    <li>banana</li>
    </ol>
    <ul class="nice" id="unordered2">
    <li>This is an unordered list and I have a class defined</li>
    <li>apple</li>
    <li>orange</li>
    <li>banana</li>
    </ul>
    <a class="link" href="#">I am a link that is styled by a class</a>
    </div>
    </div>
    </body>
    </html>
    
    

    需要添加的classid属性在上面的代码片段中突出显示。如果你不确定自己是否做得正确,打开chapter1-css-appearance.html看看。

  3. 现在保存文件,并在浏览器中打开它。你应该看到你的 HTML 文档现在看起来与使用 CSS 样式化之前不同。你的输出应该与下面示例中的截图类似:

动手时间—使用 CSS 样式化你的 HTML 文档

刚才发生了什么?

你刚刚在你上一节创建的 HTML 文档中应用了 CSS。注意你同时使用了id选择器和class选择器语法。在每个选择器内部,你也应该看到一些样式属性。

这个例子中的 HTML 元素与上一个例子相似,不同之处在于现在的 HTML 元素具有idclass名称。

在下面的子部分中,我将继续解释引用各种 HTML 元素的技术,以及我们如何通过使用它们的样式属性来设置元素样式。

通过它的 id 或 class 名称引用 HTML 元素并对其进行样式设置

我们引用了各种 HTML 元素的idclass名称。考虑上面例子中的以下代码片段:

<! some code omitted above-->
<p class="intro">This is a paragraph. I am styled by a class called "intro"</p>
<! some code omitted -->
<div id="boxed">This is enclosed within a <i>div</i> tag. And it is styled on a local level.
<ol id="ordered1">
<li>This is an ordered list and it is centered</li>
<li>apple</li>
<li>orange</li>
<li>banana</li>
</ol>
<ul class="nice" id="unordered1">
<li>This is an unordered list and has a class defined</li>
<li>apple</li>
<li>orange</li>
<li>banana</li>
</ul>
<a class="link" href="#">This is a link that is styled by a class</a>
</div>

高亮的代码指的是使用idclass名称属性的 HTML 元素。注意有些 HTML 元素同时具有idclass名称属性,而有些则没有。

现在考虑这个 CSS 代码段,它出现在示例中:

#boxed1{
background-color:#ff6600;
border:2px solid black;
height:360px;
width:300px;
padding:20px;
float:left;
margin:10px;
}

#boxed1选择器指的是 HTML 文档中的<div>元素,其 id 为#boxed1。注意,具有 id 为#boxed1<div>元素是根据声明中的名称和值属性对进行样式的。如果你更改值属性并刷新你的浏览器,你将注意到#boxed1元素也发生了变化。

现在,考虑以下 CSS 代码段:

.intro{
color:black;
font-weight:bold;
}

以及:

a:link {color:#FF0000;} /* unvisited link */
a:visited {color:#00FF00;} /* visited link */
a:hover {color:#FF00FF;} /* mouse over link */
a:active {color:#0000FF;} /* selected link */

前两个代码段是我们所说的class选择器,它们的语法与id选择器略有不同。例如,.intro类选择器选择类名称为“intro”的<p>元素,而a:linka:visiteda:hovera:active选择器指的是锚点的四种状态。

到目前为止,我们已经介绍了 CSS 选择器如何选择 HTML 文档中的 HTML 元素。但我们还没有涉及到 HTML 元素同时具有idclass属性的情况,现在我们来解释一下。

类选择器和 id 选择器之间的区别

尽管id选择器和class选择器看起来一样,但它们之间有一些细微的差别。例如,id选择器用于指定一个 HTML 元素,而class选择器用于指定几个 HTML 元素。

例如,你可以尝试将锚元素<a class="link" href="#">更改为<a class="intro" href="#">,你会注意到链接现在变成了粗体。

注意

如果一个 HTML 元素有一个由idclass选择器控制的样式属性,那么class选择器中的样式属性将优先于id选择器中的样式属性。

类选择器和 id 选择器的其他用途

在下面的章节中,你将了解到 HTML 元素的idclass名称在网页上提供交互性方面起着重要作用。这是通过使用 JavaScript 实现的,JavaScript 通过其idclass名称来引用 HTML 元素,之后对引用的 HTML 元素执行各种操作,如 DOM 操作。

CSS 属性的完整列表

这里给出的例子不完整。要获取 CSS 的完整参考,你可以访问www.w3schools.com/css/css_reference.asp

JavaScript 为网页提供行为

在本节中,我们将介绍 JavaScript 的一些关键方面。总的来说,如果 HTML 为 HTML 文档提供内容,而 CSS 为 HTML 文档设置样式,那么 JavaScript 通过为网页提供行为来赋予 HTML 文档生命。

行为可以包括动态改变 HTML 文档的背景颜色,或者改变文本的字体大小等等。JavaScript 甚至可以用来创建如动画幻灯片、淡入淡出效果等效果。

通常,行为是基于事件的,通过实时操作 DOM 来实现(至少从用户的角度来看)。

如果你对 JavaScript 还不太熟悉,JavaScript 是一种具有面向对象能力的解释型编程语言。它是一种松散类型的语言,这意味着你在声明变量或函数时不需要定义数据类型。

在我看来,理解 JavaScript 语言特性最好的方式是通过一个例子。现在,是时候动手了。

动手时间—给你的 HTML 文档添加行为

我们将把 JavaScript 应用到一个带有 CSS 样式的 HTML 文档上。与之前的例子相比,HTML 元素和 CSS 一般来说没有太大变化,除了你会在 HTML 文档中看到 HTML 按钮。

这个例子中应用到 HTML 文档上的 JavaScript 被称为内联 JavaScript,因为它存在于 HTML 文档中。

我们在这里要展示的是如何声明变量、函数,如何操作 HTML 元素的 DOM,以及如何通过它们的idclass来引用 HTML 元素。你还将学习到数组的一些常用内置方法,以及如何引用这些元素,从而使你的任务变得更简单。

这个例子并不复杂,但你将学习到一些最重要的、用于引用 HTML 元素并操作 DOM 的常用技术。

注意

(这个例子完整的代码可以在源代码文件夹Chapter 1中找到,文件名为:chapter1-javascript-behavior.html):

  1. 继上一个例子之后,在</style>标签后输入以下 JavaScript 代码:
<script type="text/javascript">
function changeProperties(d){
var e = document.getElementById(d);
e.style.position = "absolute";
e.style.fontFamily = "sans-serif";
e.style.backgroundColor = "#000000";
e.style.border = "solid 2px black";
e.style.left = "200px";
e.style.color = "#ffffff";
}
function arrangeList(f) {
// This is the element whose children we are going to sort
if (typeof f == "string"){ // check to see if the element is "string"
f = document.getElementById(f);
}
// Transfer the element (but not text node) children of e to a real array
var listElements = [];
for(var x = f.firstChild; x != null; x = x.nextSibling)
if (x.nodeType == 1){
listElements.push(x);
}
listElements.sort(function(n, m) { // .sort is a built in method of arrays
var s = n.firstChild.data;
var t = m.firstChild.data;
if (s < t){
return -1;
}
else if (s > t){
return 1;
}
else{
return 0;
}
});
for(var i = 0; i < listElements.length; i++){
f.appendChild(listElements[i]);
}
}
function insertContent(a){
var elementToBeInserted = document.getElementById(a);
elementToBeInserted.innerHTML = "<h1>This is a dynamic content</h1><br><p>great to be here</p>";
}
function changeElementUsingName(a){
var n = document.getElementsByName(a);
for(var i = 0; i< n.length; i++){
n[i].setAttribute("style","color:#ffffff");
}
}
function hideElement(a){
var header = document.getElementById(a);
header.style.visibility = "hidden";
}
function hideElementUsingTagName(a){
var n = document.getElementsByTagName(a);
for(var i = 0; i< n.length; i++){
n[i].setAttribute("style","visibility:hidden");
}
}
</script>

  • 现在保存你的文档并在浏览器中加载它,你会看到一个与下一张截图类似的示例:

动手时间—给你的 HTML 文档添加行为

刚才发生了什么?

你已经创建了一个带有 CSS 样式的 HTML 文档,并向其应用了 JavaScript。与之前的例子相比,HTML 元素和 CSS 一般来说没有太大变化,但你将会看到<button>元素。

通过点击 HTML 按钮,你可以看到 JavaScript 的强大作用。你会发现如果你点击了改变属性按钮,你将看到右侧的 HTML 盒子向左移动了 200 像素,其背景颜色也发生了变化。你还可以点击其他按钮来测试它们对 HTML 文档的影响。

当你点击每个 HTML 按钮时,你正在调用一个 JavaScript 函数,通过 DOM 操纵文档中的相关 HTML 元素。你应该看到诸如隐藏内容、创建动态内容、重新排列项目列表等效果。

在以下部分中,我首先简要介绍了 JavaScript 语法,然后将事件附加到 HTML 元素上,最后使用 JavaScript 的内置方法来查找 HTML 元素并操作它们。

JavaScript 语法

我们将从学习 JavaScript 的基本语法开始。考虑一下打开的<script>标签:

<script type="text/javascript">
// code omitted
</script>

上述<script>标签的作用是确定 JavaScript 的开始和结束位置。在type属性内,我们写text/javascript以表示这是一个 JavaScript 代码。

现在,让我们考虑一下以下的代码片段:

function arrangeList(f) {
if (typeof f == "string"){ // check to see if the element is "string"
f = document.getElementById(f);
}
var listElements = [];//declaring a variable
for(var x = f.firstChild; x != null; x = x.nextSibling)
if (x.nodeType == 1){
listElements.push(x);
}
listElements.sort(function(n, m) { // .sort is a built in method of arrays
var s = n.firstChild.data;
var t = m.firstChild.data;
if (s < t){
return -1;
}
else if (s > t){
return 1;
}
else{
return 0;
}
});
for(var i = 0; i < listElements.length; i++){
f.appendChild(listElements[i]);
}
}

上面的代码片段显示了一个名为arrangeList的函数。我们通过使用保留关键字function后跟函数名称来定义一个函数。参数在( )内传递,在这个代码片段中,f是传递给函数的参数。函数从a开始{并在a}结束。

简而言之,函数语法可以定义如下:

function functionname(parameter1, parameter2, ... parameterX){
Body of the function
}

第二个高亮行通过使用if语句展示了 JavaScript 中的决策制定。这个语法与 C 编程语言中的if语句相似。JavaScript 的if语句的语法如下:

if (condition){
code to be executed if condition is true.
}

if语句的一个变体是if-else

if (condition){
code to be executed if condition is true.
}
else{
code to be executed if condition is not true.
}

我们使用关键字var后跟一个变量名。在上面的例子中,var listElements = [];意味着定义了一个名为listElements的变量,并给它一个表示空列表的[]值。通常,由于 JavaScript 是松散类型的,变量可以被赋予任意值。

继续上面的内容,你应该看到for循环在起作用。它的语法也与 C 语言的for循环相似。

如果你是 JavaScript 的新手,document.getElementById()listElements.push(x)之类的语句可能会让你感到困惑。这两行代码中发生的事情是我们使用了 JavaScript 的一些内置方法来引用具有相应 ID 的 HTML 元素。现在,document.getElementById()对你来说将更重要;这将在你学习如何在 HTML 文档中查找元素的部分进行介绍。

javascript 事件

首先,让我们来看一下你 JavaScript 中找到的以下代码片段:

<button onclick="changeProperties('boxed1')">change properties</button>
<button onclick="insertContent('empty')">Create dynamic content</button>
<button onclick="arrangeList('ordered1')">Rearrange list</button>
<button onclick="arrangeList('unordered1')">Rearrange unordered list</button>
<button onclick="hideElement('header1')">hide header 1</button>
<button onclick="changeElementUsingName('lost')">Change hyperlink colors</button>
<button onclick="hideElementUsingTagName('h2')">Hide header 2 (using tag name)
</button>

上面的代码片段显示了通过onclick附加到 HTML 按钮上的事件。当按钮被点击时,相应的 JavaScript 函数将被调用。

例如,<button onclick="changeProperties('boxed1')">change properties</button>意味着当这个按钮被点击时,changeProperties()函数将被调用,参数boxed1是一个具有ID boxed1div元素。

在文档中查找元素

记住,我们已经看到了 JavaScript 的一些内置方法。JavaScript 可以通过使用一些内置方法或属性在 HTML 文档中查找元素。在找到 HTML 元素后,你可以操作它的属性。JavaScript 具有Document对象(DOM 树的根)的三个属性,可以让你找到所需的 HTML 元素。这里提到的技术是 JavaScript 测试的骨架。理解这一部分对理解本书的其余部分至关重要。所以,确保你理解了这一章节的内容。

首先,document.getElementById()。这个属性允许你通过特定的 ID 选择一个 HTML 元素。document.getElementById()只能返回一个元素,因为每个id属性的值都是(应该是)唯一的。以下是来自示例的代码片段:

function changeProperties(d){
var e = document.getElementById(d); 
e.style.position = "absolute";
e.style.fontFamily = "sans-serif";
e.style.backgroundColor = "#000000";
e.style.border = "2px solid black";
e.style.left = "200px";
e.style.color = "#ffffff";
}

考虑上面代码片段中突出显示的行,var e = document.getElementById(d)。这里发生的是 HTML 元素'd'被引用,而'd'碰巧是changeProperties()函数的参数。如果你查看这个示例的源代码,你会看到一个 HTML 按钮,其内容为:<button onclick="changeProperties('boxed1')">改变属性</button>。注意'boxed1'正在被引用,这意味着参数'f'取值为'boxed1'的 HTML 元素 id。因此,var e = document.getElementById(d)意味着通过document.getElementById()方法将 ID 为'boxed1'的 HTML div 分配给变量e

其次,注意document.getElementsByName()语句。这个方法和document.getElementById()类似,但它查看的是name属性而不是id属性。它返回一个元素的数组而不是一个单一的元素。考虑以下代码片段:

function changeElementUsingName(a){
var n = document.getElementsByName(a); 
for(var i = 0; i< n.length; i++){
n[i].setAttribute("style","color:#ffffff");
}
}

这里发生的是通过名称'a'(碰巧是函数的参数)引用了 HTML 元素,并且因为返回的是元素数组,我们使用一个for循环遍历元素,并使用.setAttribute方法将文本颜色改为白色。name属性仅适用于<form><a>标签。

最后,看看document.getElementsByTagName()。这个方法通过 HTML 标签名称来查找 HTML 元素。例如,以下代码:

function hideElementUsingTagName(a){
var n = document.getElementsByTagName(a); 
for(var i = 0; i< n.length; i++){
n[i].setAttribute("style","visibility:hidden");
}
}

通过标签名称查找 HTML 元素,并使其隐藏。在我们这个例子中,使用h2作为参数,因此当你点击相关按钮时,所有包含在<h2>标签中的文本都会消失。

现在,如果你将参数改为div,那么你会注意到所有的方框都会消失。

把所有内容放在一起

现在我将简要描述 JavaScript 如何与 HTML 元素交互。在本小节中,你会了解到:当 HTML 按钮被点击(一个事件)后,它调用一个 JavaScript 函数。然后,JavaScript 函数接收一个参数并执行该函数。考虑下面的代码片段。

以下是为带有事件的 HTML 按钮编写的代码:

<button onclick="insertContent('empty')">Create dynamic content</button>code

接下来,以下是为 HTML div 元素编写的代码:

<div id="empty"></div>

最后,以下是要调用的 JavaScript 函数的代码:

function insertContent(a){
var elementToBeInserted = document.getElementById(a);
elementToBeInserted.innerHTML = "<h1>This is a dynamic content</h1><br><p>great to be here</p>";
}

现在,让我解释我们要在这里做什么;点击 HTML 按钮后,调用 JavaScript 函数insertContent()。参数'empty'被传递给insertContent()。'empty'指的是 ID 为'empty'的div元素。

在调用insertContent()后,参数'empty'被传递给变量var elementToBeInserted,通过使用document.getElementById()。然后,利用 HTML 元素节点的内置方法innerHTML()(因为 HTML 元素节点传递给了elementToBeInserted变量),我们动态地插入文本"<h1>This is a dynamic content

great to be here

`"。

然后请在你的网页浏览器中打开文件,并点击 HTML 按钮。你会注意到新的一段文本被动态地插入到 HTML 文档中。

注意

HTML 元素节点的内置方法innerHTML()允许我们操纵(或者在这个例子中,动态插入)HTML 内容到使用innerHTML()方法的 HTML 节点中。例如,在我们的例子中,我们将"<h1>This is a dynamic content</h1><br><p>great to be here</p>"插入到<div id="empty"></div>中。技术上讲,插入后,最终结果将是:<div id="empty"><h1>This is a dynamic content</h1><br><p>great to be here</p></div>

JavaScript 与服务器端语言的区别

总的来说,JavaScript 与服务器端语言的主要区别在于它们的用途和执行位置。在现代应用中,JavaScript 在客户端(用户的网页浏览器)运行,而服务器端语言在服务器上运行,因此经常用来读取、创建、删除和更新 MySQL 等数据库。

这意味着 JavaScript 在网页浏览器上进行处理,而服务器端语言在网页服务器上执行。

服务器端语言包括 ASP.NET、PHP、Python、Perl 等。

在现代网络开发技术背景下,你可能已经听说过 Web 2.0 应用程序。一个重要的技术是 JavaScript 经常被广泛使用,以提供交互性并执行异步数据检索(有时是数据操作),这也被称作 AJAX(它是异步 JavaScript 和 XML 的缩写)。

JavaScript 不能用来与数据库交互,而像 PHP、Python 和 JSP 这样的服务器端语言可以。

JavaScript 也被称为前端,而服务器端则是后端技术。

注意

JavaScript 也可以用在服务器端,尽管它最常与客户端技术相关联。尽管 JavaScript 通常不与与数据库交互关联,但未来这种情况可能会改变。考虑像 Google Chrome 这样的新浏览器,它为 JavaScript 提供了与浏览器内建数据库交互的数据库 API。

为什么页面需要在没有 JavaScript 的情况下工作

虽然关于我们应该让网页在没有或具有 JavaScript 的情况下工作的争论有很多,但我个人认为,这取决于网站或应用程序的使用方式。不过无论如何,我将从一些页面需要在没有 JavaScript 的情况下工作的常见原因开始。

首先,并非所有用户都在网页浏览器中启用了 JavaScript。这意味着如果您的应用程序(或功能)需要 JavaScript,那么没有启用 JavaScript 的用户将无法使用您的应用程序。

其次,如果您打算支持移动设备上的用户,那么您需要确保您的网站或应用程序在没有 JavaScript 的情况下也能工作。主要原因是移动设备对 JavaScript 的支持往往不够满意;如果您使用 JavaScript,您的网站或应用程序可能不如预期工作(或者更糟,根本无法工作)。

另一个角度来看,这基于您对用户群体的理解。例如,大概唯一可以忽略那些禁用 JavaScript 的用户的情况是,当您可以保证或事先知道您的用户群体已启用 JavaScript 时。这种情况可能出现在您为内部使用开发应用程序时,您事先知道您的所有用户都已启用 JavaScript。

如果您在想如何创建在没有 JavaScript 的情况下也能工作的页面,您可以了解一下优雅降级(graceful degradation)的概念。想象一下,您有一个应用程序,该应用程序的核心功能是基于 AJAX 的。这意味着为了使用您的应用程序,您的用户需要启用 JavaScript。在这种情况下,您可能需要考虑让您的页面在没有 JavaScript 的情况下也能工作,以确保所有用户都能使用您的应用程序。

测试是什么?

一般来说,程序员在编写程序时会有一些目标。除了创建一个为解决特定问题或满足特定需求而编写的程序之外,其他常见目标还包括确保程序至少是正确的、高效的,并且可以容易地扩展。

在上文提到的目标中,正确性至少在这本书中是最重要的目标。我们所说的正确,是指对于任何给定的输入,我们需要确保输入是我们想要的或需要的,相应的输出也是正确的。这一点的隐含意义是指程序逻辑是正确的:它按照我们的意图工作,没有语法错误,引用的变量、对象和参数是正确的并且是我们需要的。

例如,一个用 JavaScript 编写的退休计划计算器。我们可能会期望用户输入诸如他们当前的年龄、退休年龄和每月储蓄等值。想象一下,如果用户输入错误的数据,比如字符串或字符。JavaScript 退休计划计算器将无法工作,因为输入数据是错误的。更糟糕的是,如果用户输入了正确的数据,而我们计算为退休设置 aside 的金额的算法是错误的,这将导致输出是错误的。

上述错误可以通过测试来避免,这是本书的主题。在本章剩余的部分,我们将讨论你作为 JavaScript 程序员可能遇到的一些错误类型。但在我们进入那个话题之前,我将简要讨论为什么我们需要测试。

为什么你需要测试?

首先且最重要的是,人类容易犯错误。作为一名程序员,你很可能在你编程生涯中犯过编码错误。即使地球上最优秀的程序员也犯过错误。更糟糕的是,我们可能直到测试程序时才发现错误。

第二,也许更重要的是,JavaScript 通常会默默失败;没有错误信息告诉你发生了什么错误,或者错误发生在哪里,假设你没有使用任何测试单元或工具来测试你的 JavaScript。因此,如果你 JavaScript 程序有错误,很难或根本没有办法知道发生了什么。

注意

在微软的 Internet Explorer 中,你实际上可以看到是否有任何 JavaScript 错误。你需要打开脚本调试,这在工具 | Internet 选项 | 高级 | 脚本调试中找到。开启脚本调试后,如果你有任何 JavaScript 错误,你将在 IE7 或 IE8 的左下角看到一个黄色的'yield'图标。点击这个图标,你会得到一个窗口,你可以在其中点击显示详细信息来获取有关错误的更多信息。

第三,即使有方法可以通知你 JavaScript 错误,比如启用脚本调试,如上所述,但仍有某些错误是无法通过这些方法检测到的。例如,你的程序语法可能是一百分之一百的正确,但你的算法或程序逻辑可能是有误的。这意味着即使你的 JavaScript 可以执行,你的输出可能是错误的。

最后,测试 JavaScript 可以帮助你识别跨浏览器兼容性问题。因为大约有五种主要类型的浏览器(不计算不同版本)需要支持——即微软的 Internet Explorer、Mozilla 的 Firefox、谷歌的 Chrome、苹果的 Safari 和 Opera 网络浏览器——你肯定需要测试以确保你的网站或应用程序在所有浏览器上都能工作,因为不同的浏览器有不同的 DOM 兼容性。

确保程序正确意味着确认并检查输入是正确的,然后输出是我们期望的结果。

错误类型

在我开始介绍 JavaScript 错误之前,我们需要了解 JavaScript 和网页浏览器的工作原理。一般来说,用户从服务器请求一个网页文档,这个文档被加载到用户的网页浏览器中。假设这个网页文档中嵌入了 JavaScript(无论是通过外部 JavaScript 文件还是通过内联 JavaScript),JavaScript 将与网页文档一起被加载(从上到下)。当网页浏览器加载网页文档时,网页浏览器的 JavaScript 引擎将开始解释网页文档中嵌套的 JavaScript。这个过程将继续,直到 JavaScript(和网页文档)完全加载到用户的网页浏览器中,为交互做好准备。然后,用户可能开始通过点击可能附有 JavaScript 事件链接或按钮来与网页文档进行交互。

现在,带着上面的过程在心中,我们将开始介绍不同类型的 JavaScript 错误,通过使用简单的例子。

加载错误

我们首先讨论的错误类型是加载错误。加载错误是在文档加载过程中由网页浏览器的 JavaScript 引擎捕获的错误。

换句话说,加载错误发生在 JavaScript 有机会运行之前。这些错误通常在代码有机会执行之前被 JavaScript 引擎发现。

带着前面提到的事情在心中,现在让我们经历一下加载错误是如何发生的。

行动时间——加载错误的具体表现

现在我们将看到加载错误的具体表现。我们实际上并没有看到它,但你将学习到一些加载错误的最常见原因。

注意

这个例子的完整代码可以在源代码文件夹第一章中找到,文件名为chapter1-loading-errors.html

  1. 打开你的文本编辑器并创建一个新文档。

  2. 将以下代码输入到你的文档中:

    <html>
    <head><title>JavaScript Errors - Loading Errors</title></head>
    <body>
    <script type="text/javascript">/*
    1\. Loading Errors
    */
    /*
    // Example 1 - syntax errors
    var tests = "This is a test"; // note two s
    document.write(test); // note one s
    */
    /*
    // Example 2 - syntax errors as the keyword "var" is not used
    Var Messsage = "This is a test"; // note three s's
    document.write(Message); // note two s's
    */
    /*
    // Example 3 - error caused by using a key word
    var for = "this is a test";
    document.write(in);
    */
    </script>
    </body>
    </html>
    
    
  3. 现在,取消注释掉例子 1 周围的/**/,保存文档并在浏览器中加载它。你应该在你的网页浏览器中看到一个空白页面。

  4. 重复上述步骤,对例子 2 和例子 3 也这样做。你应该看到例子 2 和例子 3 都是一个空白页面。

刚才发生了什么?

你刚刚创建了一个带有错误 JavaScript 代码的 HTML 文档。从代码中的注释,你应该意识到错误主要是由于语法错误引起的。当这种错误发生时,网页浏览器中的 JavaScript 没有任何响应。

常见的语法错误示例包括缺少括号、缺少分号和错误的变量名。

通常情况下,只要你的代码在语法上是正确的,那么你应该能够避免加载错误。

现在,你可能会问,如果 JavaScript 代码的某些部分是错误的会发生什么?这将取决于错误发生的地点。

部分正确的 JavaScript

通常情况下,JavaScript 是从上到下执行或加载的。这意味着首先加载第一行代码,然后是下一行,直到最后加载最后一行代码。这对部分正确的 JavaScript 有重要的影响。

行动时间——加载错误在行动中

现在我们将看到部分正确的 JavaScript 代码在行动中及其影响。

注意

这个示例的完整源代码可以在源代码文件夹中找到,文件名为Chapter1-loading-errors-modified.html

  1. 打开你的文本编辑器,创建一个新文档,将以下代码输入到你的文档中:

    <html>
    <head><title>JavaScript Errors - Loading Errors</title></head>
    <body>
    <script type="text/javascript">/*
    1\. Loading Errors - modified
    */
    // this is correct code
    var tests = "This is a CORRECT test";
    document.write(tests);
    // this is incorrect code. The variable name referred is incorrect
    var Messsage = "This is a FIRSTtest";
    document.write(Message);
    // this is correct code
    var testing = "this is a SECOND test";
    document.write(testing);
    </script>
    </body>
    </html>
    
    
  2. 现在保存你的文档并在你的网页浏览器中加载你的文档。你应该在浏览器中看到文字这是一个测试在

刚才发生了什么?

如果你追踪代码,你应该看到 JavaScript 是从上到下执行的。当它遇到一个错误时,它会在document.write()中引用一个错误的变量名而停止执行。因为它在遇到错误时停止执行,所以剩下的 JavaScript 代码将不会被执行。

如果你的 JavaScript 代码是按照函数来组织的,那么情况会有所不同。在这种情况下,语法错误的函数将无法执行,而语法正确的函数将继续工作,无论其在代码中的顺序如何。

到现在为止,你应该对加载错误以及如何通过确保你的代码在语法上是正确的来防止它们有一个大致的了解。

现在让我们继续讨论下一类错误——运行时错误。

运行时错误

你还记得 JavaScript 是如何与网页文档一起加载到浏览器中的吗?在网页文档完全加载到网页浏览器后,它准备好响应各种事件,这也导致了 JavaScript 代码的执行。

运行时错误发生在执行过程中;例如,考虑一个带有 JavaScript 事件的 HTML 按钮。假设一个 JavaScript 函数被分配给了一个事件,那么如果 JavaScript 函数有错误,当用户点击 HTML 按钮时,该函数将不会被执行。

其他形式的运行时错误发生在你对对象、变量或方法误用,或者你引用了尚不存在的对象或变量时。

行动时间——运行时错误在行动中

现在我们将看到运行时错误的三个常见原因在行动。

注意

代码示例保存在第一章的源代码文件夹中,名为:chapter1-runtime-errors.html

  1. 打开你的文本编辑器,在新的文档中输入以下代码:

    <html>
    <head><title>JavaScript Errors</title></head>
    <script type="text/javascript">/*
    2\. Runtime Errors
    */
    alert (window.innerHTML);
    var Test = "a variable that is defined";
    alert(Test); // if variables is wrongly typed, than nothing wil happen
    // nothing happens when the user clicks on the HTML button, which invokes the following function
    function incorrectFunction(){
    alert(noSuchVariable);
    }
    </script>
    <body>
    <input type="button" value="click me" onclick="incorrectFunction()" />
    </body>
    </html>
    
    
  2. 保存文档,并将其加载到你的网页浏览器中。

  3. 在将文档加载到浏览器后,你将看到两个警告框:第一个框显示未定义,第二个警告框显示已定义的变量。然后你会看到一个写着点击我的 HTML 按钮。

  4. 点击按钮,你会发现什么也没有发生。

刚才发生了什么?

你所看到的第一个警告框显示了一个由方法误用引起的错误。window.innerHTML不存在,因为.innerHTML是应用于 HTML 元素,而不是window。第二个警告框显示一个在alert()引用它之前已定义的变量。最后,当你点击 HTML 按钮时,什么也不会发生,因为应该被调用的函数引用了未定义的变量。因此,在onclick()事件中没有执行。

在这个例子中,你应该意识到代码的逻辑非常重要,你需要在使用它们之前定义你的变量或对象。还要确保应用的方法或属性是正确的。否则,你最终会得到一个运行时错误。

现在,我们将进入 JavaScript 错误的最后一种形式——逻辑错误。

逻辑错误

逻辑错误很难解释。但一般来说,你可以将逻辑错误视为代码运作不符合你预期的方式时产生的错误。通过亲身体验逻辑错误,你更容易理解它们是什么。所以,让我们采取一些行动。

行动时间——逻辑错误在行动

在这个最后一个例子中,你会看到逻辑错误。

  1. 打开你的文本编辑器,在新的文档中输入以下代码:

    <html>
    <head><title>JavaScript Errors</title>
    <script type="text/javascript">
    /* Logic Errors */
    //saving some input in wrong variables
    function amountOfMoneySaved(amount){
    var amountSpent, amountSaved;
    amountSpent = amount; // where you really meant amountSaved
    var currentAmount = 100;
    var totalAmountSaved = currentAmount - amountSpent;
    alert("The total amount of money you have now is " +
    totalAmountSaved );
    }
    function checkInput(amount){
    if(amount>0 && amount<99)
    alert("is number");
    else
    alert("NOT number");
    }
    </script>
    </head>
    <body>
    <!-- this shows an infinite loop, an obvious logic error-->
    <script>
    // an infinite loop
    for(var i = 0; i<10; i--){
    document.write(i + "<br>");
    }
    </script>
    <form id="moneyForm">
    You currently have 100 dollars.
    The amount of money you have saved is: <input type="text" id="money" name="money" /><br />
    <input type="submit" value="Submit"
    onclick="amountOfMoneySaved(moneyForm.money.value)" />
    </form>
    </body>
    </html>
    
    
  2. 现在,保存代码并在浏览器中打开文档。

  3. 你会看到两个简单的表单。第一个表单包含文本:您目前有 100 美元。您所拥有的金额是 " ",后面是一个输入框。第二个表单包含文本:检查你是否输入了一个数字,后面是一个输入框。

  4. 现在尝试输入一个大于 99 的数字(比如,999)。

    你可能注意到了,在输入你的输入后,总金额似乎减少了。这是一个逻辑错误的例子,你应该将输入加起来,但函数却减去了输入。程序为什么没有按照预期的方式工作?

刚才发生了什么?

你刚刚见证了一个简单的逻辑错误行动例子。逻辑错误可以有多种形式。你可能注意到了上面例子中被注释掉的一段代码。

<script type="text/javascript">// example 1: infinite loop
for(var i = 0; i<10; i--){
document.write(i + "<br>");
}
</script>

这是一个无限 for 循环的例子。在这个循环中,你可能会注意到语句 document.write(i+<br>"); 应该执行 10 次(从 var i = 0i = 9)。然而,在 for 语句内的初始化器中的第三个表达式是递减的(i--)。

因此,变量 i 永远不可能达到 i>10 的条件。如果你取消注释代码,你会注意到语句 document.write(i"<br>"); 将会继续执行,直到网页浏览器挂起;如果你在 Windows 机器上使用 Firefox,网页浏览器将会挂起,你将不得不使用任务管理器退出浏览器。

一些编写无错误 JavaScript 的建议

到现在为止,你应该对 JavaScript 错误类型有一个大致的了解。虽然我们通常无法避免错误,但我们在编写代码时应该尽量减少错误。在本节中,我将简要讨论一些作为初学 JavaScript 程序员可以采取的策略,以最小化可能发生的错误量。

总是检查对象、变量和函数的正确名称

正如上面错误形式所看到的,你总是应该确保你正确地使用了对象、变量和函数的名称。因为这样的错误不会在你的网页浏览器中显示,当你编写代码时,总是检查名称的正确使用是一个好主意

这还包括为不同的变量、对象和函数使用独特的名称。记住,JavaScript 是大小写敏感的;因此一定要记得检查你是否正确地使用了变量、对象和函数的大小写。

检查语法是否正确

因为你在使用 JavaScript,至少在这本书中,你应该在运行你的程序之前检查你是否使用了正确的语法。在此之前,我们讨论了语言语法的一些关键特性,例如,每个语句都以分号结束,使用正确和匹配的括号,使用正确或独特的函数名称等。

编码前规划

在实际编码过程之前的规划有助于减少逻辑错误的可能性。这有助于你仔细思考你的程序,并在代码中找出明显的逻辑错误。规划还可以帮助你检查盲点,例如缺失的功能或函数。

编写代码时检查正确性

在你编写程序的过程中,总是检查你在完成代码的某些部分时是否有错误是一个好主意。例如,如果你的程序由六个函数组成,总是明智(且减少错误)地检查每个函数的正确性。在移动到下一个函数之前,确保你编写的每个函数都是正确的是一个好习惯,这可以在你编写大型程序时节省你很多麻烦。

通过选择合适的文本编辑器来预防错误

我个人认为,一个合适的文本编辑器(或 IDE)是减少编码错误的关键步骤。请注意,我没有说你需要一个“好”的文本编辑器,而是需要一个“合适”的文本编辑器。这是因为不同的编程语言有不同的特性和不同的功能。

例如,如果您已经使用过 Python 编程,您会注意到您不需要具备检查匹配括号的能力,因为 Python 基于代码块(制表或空格来表示代码块)。然而,在 JavaScript 的情况下,您肯定需要您的文本编辑器帮助您检查匹配(或缺失)的括号。可以实现上述功能的代码编辑器包括 Dreamweaver(商业的)和 Eclipse(免费的)。

除了匹配括号检查之外,以下是一些在您使用 JavaScript 编码时将为您提供帮助的其他功能:

  1. 自动制表或关键字后的空格或匹配括号:这将帮助您 visually inspect 代码结构,并将减少编码错误。

  2. 自动完成或自动建议功能:这意味着当你输入代码时,编辑器足够智能,可以建议你程序中使用的一些单词(或代码),这样你就可以在编写代码时快速引用它们。这对于检查用户定义的变量、对象和函数特别有用。

  3. 语法高亮:这将帮助您识别是否误用了任何关键字。还记得运行时错误吗?运行时错误可能由关键字的误用引起。如果您正在使用任何用户定义的变量、对象或函数的关键字,语法高亮将帮助您识别这一点。

总结

哇,我们在这一章中涵盖了好多内容。本章涵盖的大部分内容构成了我们后续章节需要使用的构建块。具体来说,我们介绍了以下主题:

  • 我们在网页中学到了 HTML、CSS 和 JavaScript。总的来说,HTML 提供内容,CSS 为网络文档设置样式,JavaScript 为网页提供行为和交互性。

  • 我们已经学习了 HTML、CSS 和 JavaScript 的语法。

  • 我们已经学习了如何使用 ID 和类选择器的关键技术,以便 CSS 能够引用各种 HTML 元素,并对引用的 HTML 元素执行样式操作。

  • 对于 JavaScript,我们学习了三种重要的技术,以便 JavaScript 能够引用 HTML 元素。这三种技术(或者说内置方法)是:document.getElementById()document.getElementsByName()document.ElementsByTagName()

  • 接下来,我们学习了测试以及为什么我们需要进行测试。总的来说,测试是为了确保程序正确运行——也就是说,对于给定的输入,我们得到正确的输出。此外,测试有助于发现语法错误,并确认程序以我们预期的方式运行。

  • 我们讨论了 JavaScript 错误的类型,具体包括加载错误、运行时错误和逻辑错误。我们还讨论了每种错误类型的一些简单示例以及它们常见的原因。

  • 我们讨论了一些编写无错误代码的重要技巧和建议。

现在我们已经介绍了 JavaScript 测试的基本构建块,你将看到我们如何利用它们来执行即兴测试,这将在下一章中介绍。你会注意到本章中使用的一些函数和内置方法将在下一章中使用。

第二章:JavaScript 中的随兴测试和调试

在本章中,我们将正式进入测试我们实际创建的 JavaScript 程序。但在我开始之前,我想向你简要介绍一下你可以期待在本章中看到的内容。在本章中,你将学习到两个主要概念,第一个概念是不同的浏览器如何影响 JavaScript 测试,第二个主要概念是你如何通过使用 alert()来测试你的 JavaScript 程序。你还将学习如何访问表单上的值,操作这些值,并最终以有意义的方式输出这些值。

你还将看到前一章中介绍的许多技术被广泛使用。

更具体地说,我们将学习以下主题:

  • 随兴测试的目的

  • 当浏览器遇到 JavaScript 错误时会发生什么

  • 浏览器差异及需要在多个浏览器中测试的需求

  • 常见的浏览器消息及其含义

  • 如何找出你的代码是否得到了正确的输出,以及是否在代码中把正确的值放在了正确的位置

  • 如何访问表单上的值以及如何访问网页的其他部分

  • 当你的 JavaScript 程序没有给你期望的结果时该怎么办的技巧

  • 脚本如果不运行该怎么办

  • 如何进行视觉检查

  • 如何使用alert()测试你的 JavaScript 程序

  • 为了简化测试,注释掉代码的某些部分

  • 为什么随兴测试并不总是足够

所以在进入本章的主要内容之前,我会简要提到在继续本章其余内容之前你应该理解的两个基本概念。

随兴测试的目的——让脚本运行

第一个基本概念涉及随兴测试的目的。随兴测试的主要目的是快速让你的代码运行起来,然后看看你的代码是否有任何错误。如前所述,JavaScript 的三种不同错误类型包括加载、运行时和逻辑错误。

随兴测试的主要优点是它允许你测试你的 JavaScript 程序,而不会让你感到困惑。它适用于那些想要节省时间的人,尤其是测试小段代码时。

当浏览器遇到 JavaScript 错误时会发生什么

现在是第二个基本概念的时候了。在前一章中,我已经简要描述了一个网页是如何被加载到浏览器中,然后在网页浏览器中渲染,等待与用户交互。我还提到,通常来说,JavaScript 是默默失败的;它不会明确告诉你或显示发生了什么错误(如果有的话)。这是因为你的浏览器没有开启任何形式的调试。

然而,现代网络浏览器具有内置的方式,让浏览器告诉用户网页上发生了某种错误。当你明确打开或安装网络浏览器的调试工具时,就会发生这种情况。对于某些浏览器,您还需要明确打开错误控制台,才能找出发生了什么错误。

如果您想知道如何利用这些内置功能,以下是一些简单的指导说明,帮助您开始使用:

  1. 对于 Firefox——打开你的网络浏览器,前往工具。点击错误控制台

  2. 对于 Internet Explorer——你需要前往工具 | 互联网选项 | 高级。滚动到底部浏览,并检查显示关于每个脚本错误的通知

现在你已经理解了为什么我们要进行临时测试的基本概念。接下来,我们将进入一个更复杂的话题——浏览器差异如何影响你的 JavaScript 程序。

浏览器差异及在多个浏览器中进行测试的需要

一般来说,浏览器具有不同的功能。对我们来说最重要的区别,至少在这本书中,是不同浏览器使用的 JavaScript 引擎。不同的 JavaScript 引擎以不同的方式处理 JavaScript。这对我们有很大的影响。一个网络浏览器支持的某些 JavaScript 函数或方法可能在另一个浏览器上不受支持。

JavaScript 的主要本质是它通过 DOM 操作提供网页的行为;不同的浏览器对 DOM 的支持有不同的级别。

我们不会尝试深入讨论各种浏览器支持和不支持的内容。相反,我们会指向这个网站:www.quirksmode.org/compatibility.html

这个链接提供了不同选择器下各种网络浏览器不兼容性的总结。对于我们这里的目的,我们应该更关注 DOM 选择器,因为我们关心的是 JavaScript。可以随意浏览该网站以获取详细信息。但现在,你需要理解的主要观点是,浏览器差异导致了不兼容性,因此我们需要测试浏览器兼容性。

大多数初学者 JavaScript 程序员经常会想知道他们如何可以找出访问者使用的浏览器。毕竟,如果你能找出你的访问者使用什么浏览器,你就能创建出兼容的 JavaScript 代码。这在很大程度上是正确的;所以现在我们首先要学习如何检查访问者的浏览器。

是时候行动了——检查功能和嗅探浏览器

在本节中,我们想向您介绍 navigator 对象。navigator 对象是一个内置对象,为您提供有关访问者浏览器的信息。我们试图做的是向您展示 navigator 对象是如何工作的,以及您可以如何根据浏览器信息进行编程决策。

注意

此示例的源代码可以在源代码文件夹第二章中找到,文件名为browser-testing-sample-1.htmlbrowser-testing-sample-2.html

  1. 如果您还没有这样做,请启动您的文本编辑器,然后在您的文本编辑器中输入以下代码:

    <html>
    <head><title>Testing for Browser - Example 1</title></head>
    <body>
    <script type="text/javascript">// Sample 1
    var browserType ="Your Browser Information Is As Follows:\n";
    for( var propertyName in navigator){
    browserType += propertyName + ": " + navigator[propertyName] + "\n";
    }
    alert(browserType);
    </script>
    </body>
    </html>
    
    

    下面是之前代码中发生的事情:我们定义了一个变量browserType。之后我们使用了一个for循环并定义了另一个变量propertyName

  2. 所说的for( var propertyName in navigator )意味着我们正在尝试获取navigator对象中的所有属性。

  3. 这样做之后,我们将propertyName和信息添加到browserType变量中。最后,我们在一个警告框中输出这些信息。

  4. 现在,将文件加载到您的网页浏览器中,您应该会看到一个包含有关您网页浏览器信息的弹出窗口。

    注意,警告框包含了有关您网页浏览器各种类型的信息。您还可以访问浏览器的特定属性以供您自己使用。我们接下来要做的就是这件事。

    您已经学会了如何使用 navigator 对象,现在该看看我们如何利用这些信息来执行编程决策了:

  5. 创建另一个新文档,并在其中输入以下代码:

    <html>
    <head><title>Testing for Browser - Example 2</title></head>
    <body>
    <script type="text/javascript">// Sample 2
    var typeOfBrowser = navigator.appName;
    document.write(typeOfBrowser);
    if(typeOfBrowser == "Netscape"){
    alert("do code for Netscape browsers");
    }
    else{
    alert("do something else");
    }
    </script>
    </body>
    </html>
    
    

在上一个示例代码中,我们已经定义了变量typeOfBrowser,用于决定执行哪个操作。一个简单的方法是使用if else语句根据浏览器名称选择执行的代码。

刚才发生了什么?

在前面的示例中,您已经看到了如何使用 navigator 对象执行“浏览器嗅探”,并根据给定信息执行适当的操作。

除了使用 navigator 对象,您还可以基于浏览器的能力测试浏览器之间的差异。这意味着您可以测试用户浏览器是否具有某些功能。这种技术也被称为功能测试。现在,我们将简要看看您如何执行功能测试。

通过功能测试测试浏览器差异

功能测试是应对浏览器不兼容的重要且强大的方法。例如,您可能想使用某个可能在不同浏览器上不受支持的函数。您可以包含一个测试,以查看此功能是否受支持。然后,根据这些信息,您可以为您的访问者执行适当的代码。

行动时间——针对不同浏览器的功能测试

在本节中,我们将简要介绍一个简单易用的方法,可以帮助你快速测试某个特性。我们要使用的方法是.hasFeature()方法。现在,让我们深入了解并看看它在实际中的应用。

注意

这个示例的源代码可以在source code文件夹中的第二章找到,文件名为browser-testing-by-feature-2.htmlbrowser-testing-by-feature.html

  1. 启动你的文本编辑器,然后在文本编辑器中输入以下代码:

    <html>
    <head><title>Testing browser capabilities using .hasFeature()</title></head>
    <body>
    <script type="javascript/text">
    var hasCore = document.implementation.hasFeature("Core","2.0");
    document.write("Availability of Core is "+ hasCore + "<br>");
    var hasHTML = document.implementation.hasFeature("HTML","2.0");
    document.write("Availability of HTML is "+ hasHTML + "<br>");
    var hasXML = document.implementation.hasFeature("XML","2.0");
    document.write("Availability of XML is "+ hasXML + "<br>");
    var hasStyleSheets = document.implementation.hasFeature("StyleSheets","2.0");
    document.write("Availability of StyleSheets is "+ hasStyleSheets + "<br>" );
    var hasCSS = document.implementation.hasFeature("CSS","2.0");
    document.write("Availability of CSS is "+ hasCSS + "<br>" );
    var hasCSS2 = document.implementation.hasFeature("CSS2","2.0");
    document.write("Availability of CSS2 is "+ hasCSS2 + "<br>");
    </script>
    </body>
    </html>
    
    

    为了使事情更清晰,我为每个特性和版本号定义了变量。一般来说,.hasFeature()的使用如下所示:

    .hasFeature(feature, version);
    // feature refers to the name of the feature to test in string
    // version refers to the DOM version to test
    
    
  2. 现在将文件加载到你的网页浏览器中,你应该会看到屏幕上动态创建各种类型的文本。

    同样,你可以使用从用户浏览器中得到的信息以与之前示例中看到的方式类似地执行各种决策。

    因此,为了简化和解释的目的,这里是你可以如何使用.hasFeature()进行程序决策的示例。

  3. 创建另一个新文档,并将以下代码输入其中:

    <html>
    <head><title>Testing browser capabilities using .hasFeature() - Example 2</title></head>
    <body>
    <script type="text/javascript">
    var hasCore = document.implementation.hasFeature("Core","2.0");
    if(hasCore){
    document.write("Core is supported, perform code based on the feature<br>");
    }
    else{
    document.write("Feature is not supported, do alternative code to enable your program<br>");
    }
    </script>
    </body>
    </html>
    
    

上面的示例代码是自解释的,因为它与browser-testing-sample-2.html中的示例类似。

刚才发生了什么?

之前的示例是你测试浏览器差异可以做到的事情的一个简单扩展。它与第一个示例类似,后者明确“嗅探”浏览器信息,而使用.hasFeature()的方法是基于功能能力的。

测试浏览器差异没有对错之分。然而,一个普遍的做法是使用.hasFeature()来测试程序功能。也就是说,我们经常使用.hasFeature()以确保我们的 JavaScript 功能在不同浏览器中可用。

之前的示例展示了你可以通过.hasFeature()测试的一些特性。以下是使用.hasFeature()可以测试的其他特性列表:

  • 事件

  • 用户界面事件

  • 鼠标事件

  • 网页事件

  • 变异事件

  • 范围

  • 遍历

  • 视图

既然你已经对如何测试浏览器差异有了一些了解,是时候讨论下一个话题了——得到输出并将值放在正确的地方。

你得到正确的输出并将值放在正确的地方了吗?

在本节中,我们将学习如何确保我们得到输出并将正确的值放在正确的地方。这意味着我们需要了解如何使用 JavaScript 与 HTML 表单配合。

访问表单中的值

一般来说,“获取”值通常意味着用户会在 HTML 文档中输入一些值到表单中,然后我们的程序从网络表单中“获取”输入。此外,这些值可能被其他函数处理,也可能不被处理;初始用户输入可能作为参数传递给其他函数,然后被处理。

这可以通过使用 JavaScript 的内置工具来实现;JavaScript 为您提供了几种访问表单值的方式,这样您就可以稍后使用这些值。通常,JavaScript 会在"获取"表单的 onsubmit 事件中。

行动时间——从表单中获取值

在以下示例中,我们将从简单的 HTML 表单开始。你将学习到访问不同表单元素的各种技术。这里发生的是,我们首先通过使用onsubmit事件提交表单。onsubmit事件允许我们将表单通过一个 JavaScript 函数发送出去,该函数帮助我们从各种表单元素类型中提取值。所以在这个示例中,我需要你放松并理解前面提到的技术。

注意

本例的源代码可在source code文件夹的Chapter 2中找到,文件名为accessing-values-from-form.html

  1. 再次,将以下代码输入到您在新建文档中最喜欢的编辑器中:

    <html>
    <head><title>Getting Values from a HTML form</title>
    <script type="text/javascript">/*
    In this example, we'll access form values using
    the following syntax:
    document.NameOfForm.NameOfElement
    where:
    NameOfForm is the name of corresponding form
    NameOfElement is the name of the element ( within the corresponding form)
    */
    function checkValues(){
    var userInput = document.testingForm.enterText.value;
    alert(userInput);
    var userInputTextArea = document.testingForm.enterTextArea.value;
    alert(userInputTextArea);
    var userCheckBox = document.testingForm.clickCheckBox.value;
    // this is for checkbox
    if(document.testingForm.clickCheckBox.checked){
    userCheckBox = true;
    }
    else{
    userCheckBox = false;
    }
    alert(userCheckBox);
    var userSelectBox = document.testingForm.userSelectBox.value;
    alert(userSelectBox);
    // here's another way you can "loop" through your form elements
    alert(document.testingForm.radioType.length);
    for(var counter = 0; counter<document.testingForm.radioType.length;counter++){
    if(document.testingForm.radioType[counter].checked){
    var userRadioButton = document.testingForm.radioType[counter].value;
    alert(userRadioButton);
    }
    }
    }
    </script>
    </head>
    <body>
    <h1>A simple form showing how values are accessed by JavaScript</h1>
    <form name="testingForm" onsubmit="return checkValues()">
    <p>Enter something in text field:<input type="text" name="enterText" /></p>
    <p>Enter something in textarea:<textarea rows="2" cols="20" name="enterTextArea"></textarea></p>
    <p>Check on the checkbox:<input type="checkbox" name="clickCheckBox" /></p>
    <p>Select an option:
    <select name="userSelectBox">
    <option value="EMPTY">--NIL--</option>
    <option value="option1">option1</option>
    <option value="option2">option2</option>
    <option value="option3">option3</option>
    <option value="option4">option4</option>
    </select>
    </p>
    <p>Select a radio buttons:<br />
    <input type="radio" name="radioType" value="python" /> Python
    <br />
    <input type="radio" name="radioType" value="javascript" /> JavaScript
    <br />
    <input type="radio" name="radioType" value="java" /> Java
    <br />
    <input type="radio" name="radioType" value="php" /> PHP
    <br />
    <input type="radio" name="radioType" value="actionscript" /> ActionScript 3.0
    </p>
    <input type="submit" value="Submit form" />
    </form>
    </body>
    </html>
    
    

    你应该注意到有各种输入类型,比如texttextareacheckboxselectradio

  2. 保存表单,然后将其加载到网页浏览器中。你应该在屏幕上看到一个简单的 HTML 表单。

  3. 继续输入字段的值,然后点击提交表单。你应该看到一系列的警告窗口,重复你输入的值。

刚才发生了什么?

在之前提到的简单示例中,你通过一个 JavaScript 事件onsubmit提交了一个表单。onsubmit事件调用了一个名为checkValues()的 JavaScript 函数,该函数帮助我们访问不同表单元素中的值。

通常,访问表单元素的语法如下:

document.formName.elementName.value 

其中formName是指表单的名称,elementName指的是元素的名称。

正如之前的示例中,表单名是 testingForm,正如在<form name="testingForm" onsubmit="return checkValues()">中所看到的,输入文本元素的名字是enterText,正如在<input type="text" name="enterText" />中所看到的。因此,基于这段代码片段,我们将通过以下方式访问表单值:

document.testingForm.enterText.value

我们可以将这个值赋给一个可以稍后保存的变量,如代码示例所示。

之前的示例应该很容易理解。但在这个简短的示例中,我还介绍了一些其他有用的方法。考虑以下代码片段,它可以在示例中找到:

for(var counter = 0; counter<document.testingForm.radioType.length;counter++){ 
if(document.testingForm.radioType[counter].checked){
var userRadioButton = document.testingForm.radioType[counter].value;
alert(userRadioButton);
}
}

请注意,在突出显示的行中,我使用了length属性;document.testingForm.radioType.length意味着我在名为testingForm的表单中计算了名为radioType的元素的数量。这个属性返回一个整数,然后可以在这个整数上使用循环,如之前代码片段中的for循环。然后你可以遍历表单元素,并使用前面提到的方法检查它们的值。

另一个重要的技术可以在下面的代码片段中找到

if(document.testingForm.clickCheckBox.checked){ 
userCheckBox = true;
}

突出显示的行中发生的事情是document.testingForm.clickCheckBox.checked返回一个truefalse。你可以使用这个技术来检查你引用的表单元素是否有输入。然后你可以利用这个信息来执行决策。

访问表单值的另一种技术

正如你可能已经注意到的,我们通过使用name属性来访问表单元素。我们很可能会(很可能)使用name属性来访问表单元素,因为它更容易引用这些元素。但无论如何,这里有一个你可以快速查看的替代方法:

而不是写

document.formName.elementName.value 

你可以写这个:

document.forms[integer].elementName.value 

这里你正在使用forms对象,elementName指的是输入的名称。

上述代码样例的一个例子可能是:

document.forms[0].enterText.value

注意forms对象后面跟了[0]。这意味着forms对象被当作数组一样处理;forms[0]指的是网页上的第一个表单,依此类推。

既然你已经理解了访问表单值的基础知识,你在下一节将学习如何确保你在正确的地方获取正确的值。

访问网页的其他部分

在本节中,你将学习如何访问网页的其他部分。通常,你已经学习了通过使用getElementByIdgetElementsByTaggetElementsByTagName来访问网页不同部分的构建块。现在你将进一步使用这些方法,以及新学到的从表单中访问值的技术。

行动时间—在正确的地方获取正确的值

在这个例子中,你将看到到目前为止你所学习技术的综合应用。你将学习如何访问表单值、操纵它们、对它们执行操作,最后,把新的输出放置在网页的其他部分。为了帮助你更好地可视化我要描述的内容,以下是完成例子的截图:

行动时间—在正确的地方获取正确的值

接下来要使用的例子是一个简单的 JavaScript 程序,用于检查你是否能在你想要退休的年龄退休。它会要求你提供一些基本信息。根据提供的信息,它将确定你能否在那个时间退休,基于你退休时想要拥有的金额

你将构建一个表单(实际上有两个表单,泛泛而言),用户将被要求在第一个表单(在左边)输入基本信息,在输入每个字段所需的 information 后,将在字段右边动态出现另一个输入字段(在网页的中间),如果输入正确

当你输入信息时,一个 JavaScript 事件将触发一个 JavaScript 函数来检查输入的正确性。如果输入正确,将在刚刚接受输入的字段右侧创建一个新的字段,并且左侧的字段将被禁用。

在左侧字段正确填写后,你会注意到一个完整的表单正在页面中间被填写。点击提交后,代码将进行计算,并根据你指定的年龄和你需要的金额,确定你是否可以退休。

这个示例的基本要求如下:

  • 必须输入正确的值。例如,如果字段要求你输入你的年龄,该字段只能接受整数,不允许字符。

  • 如果字段需要文本输入,例如你的名字,将不允许整数。

注意

这个示例的完整源代码可以在第二章的源代码文件夹中找到,文件名为getting-values-in-right-places.html

那么现在,让我们开始这个示例:

  1. 让我们先从构建这个示例的基本界面开始。所以,请将以下代码(HTML 和样式)输入到你的文本编辑器中。

    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
    <html >
    <head><title>Getting the right values</title>
    <style>
    input{
    padding:5px;
    margin:5px;
    font-size:10px;
    }
    .shown{
    display:none;
    }
    .response{
    padding:5px;
    margin:5px;
    width:inherit;
    color:red;
    font-size:16px;
    float:left;
    }
    #container{
    position:absolute;
    width:800px;
    padding:5px;
    border: 2px solid black;
    height:430px;
    }
    #left{
    height:inherit;
    width:370px;
    border-right:2px solid black;
    float:left;
    padding:5px;
    }
    #right{
    height:inherit;
    width:300px;
    float:left;
    padding:5px;
    }
    #bottom{
    float:left;
    bottom:5px;
    padding:5px;
    }
    #finalResponse{
    float:left;
    width:780px;
    height:250px;
    border:3px solid blue;
    padding:5px;
    }
    /* this is for debugging messages */
    #debugging{
    float:left;
    margin-left:820px;
    height:95%;
    width:350px;
    border:solid 3px red;
    padding:5px;
    color:red;
    font-size:10px;
    }
    </style>
    <script type=”javascript/text”>
    // some Javascript stuff in here
    var globalCounter = 0;
    </script>
    <body>
    <div id="container">
    <div id="left">
    <h3>Enter your information here</h3>
    <form name="testForm" >
    <input type="text" name="enterText" id="nameOfPerson" onblur="submitValues(this)" size="50" value="Enter your name"/><br>
    <input type="text" name="enterText" id="birth" onblur="submitValues(this)" size="50" value="Enter your place of birth"/><br>
    <input type="text" name="enterNumber" id="age" onblur="submitValues(this)" size="50" maxlength="2" value="Enter your age"/><br>
    <input type="text" name="enterNumber" id="spending" onblur="submitValues(this)" size="50" value="Enter your spending per month"/><br>
    <input type="text" name="enterNumber" id="salary" onblur="submitValues(this)" size="50" value="Enter your salary per month"/><br>
    <input type="text" name="enterNumber" id="retire" onblur="submitValues(this)" size="50" maxlength="3" value="Enter your age you wish to retire at" /><br>
    <input type="text" name="enterNumber" id="retirementMoney" onblur="submitValues(this)" size="50"
    value="Enter the amount of money you wish to have for retirement"/><br>
    </form>
    </div>
    <div id="right">
    <h3>Response</h3>
    <form name="testFormResponse" id="formSubmit" onsubmit="checkForm(this);return false">
    </form>
    </div>
    <div id="finalResponse"><h3>Final response: </h3></div>
    </div>
    </body>
    </html>
    
    
  2. 你可能想保存这个文件并在浏览器中加载它,看看你是否得到了与之前看到的前屏幕截图相同的输出。

    请注意,在上面的 HTML 表单中,有一个 JavaScript 事件onbluronblur是一个发生在一个元素失去焦点时的 JavaScript 事件。所以你应该看到所有输入元素都有一个onblur,它触发了submitValues()函数。

    你也应该注意到submitValues()有一个this作为参数。this是 JavaScript 中最强大的关键词之一,指的是它所指向的相应元素。一个例子是<input type="text" name="enterText" id="nameOfPerson" onblur="submitValues(this)" size="50" value="Enter your name"/>。在这段代码中,submitValues(this)将通过名称enterText提交 HTML 表单元素对象。

    现在,是时候进行 JavaScript 编程了。根据之前的解释,当发生 JavaScript 事件onblur时,它将提交 HTML 表单元素对象到submitValues()函数。因此,我们首先从这个函数开始。

  3. 现在,请将以下代码插入<script type="javascript/text">标签之间:

    function submitValues(elementObj){
    // using regular expressions here to check for digits
    var digits = /^\d+$/.test(elementObj.value);
    // using regular expressions
    // here to check for characters which
    // includes spaces as well
    var letters = /^[a-zA-Z\s]*$/.test(elementObj.value);
    // check to see if the input is empty
    if(elementObj.value==""){
    alert("input is empty");
    return false;
    }
    // input is not relevant; we need a digit for input elements with name "enterNumber"
    else if(elementObj.name == "enterNumber" && digits == false){
    alert("the input must be a digit!");
    return false;
    }
    // input is not relevant; we need a digit for input elements with name "enterNumber"
    else if(elementObj.name == "enterText" && letters == false){
    alert("the input must be characters only!");
    return false;
    }
    // theinput seems to have no problem, so we'll process the input
    else{
    elementObj.disabled = true;
    addResponseElement(elementObj.value,elementObj.id);
    return true;
    }
    }
    
    

    我对代码正在做什么进行了注释,但我将重点介绍之前函数中使用的一些技术。

    我们在这里要做的就是检查输入的正确性。对于这个示例,我们只接受纯数字或纯字符(包括空格)。以下代码片段就是这样做:

    var digits = /^\d+$/.test(elementObj.value);
    var characters = /^[a-zA-Z\s]*$/.test(elementObj.value);
    
    

    在这里,我们利用正则表达式来检查输入的正确性。/^\d+$//^[a-zA-Z\s]*$/都是正则表达式,它们都附加上test方法。test方法测试 HTML 表单对象的值。例如,如果var digits = /^\d+$/.test(elementObj.value)的确是数字,它将返回true,否则返回false。同样,var characters = /^[a-zA-Z\s]*$/.test(elementObj.value)如果是字符(包括空格)将返回true,否则返回false

    如果您想了解更多关于使用正则表达式的信息,可以参考www.w3schools.com/jsref/jsref_obj_regexp.asp并了解其工作原理。

    这些信息将在if-else语句的决策过程中使用。if-else语句检查 HTML 对象的名称;enterNumber期望一个整数输入。如果不是enterNumber,它期望一个字符输入。

    您应该注意到,如果输入没有问题,我们将禁用输入元素,并将 HTML 表单对象的valueid传递给一个函数addResponseElement(),之后我们将return true,表示代码成功执行并提交了表单值。

    现在,我们将进入addResponseElement()函数:

  4. 继续当前文档,在submitValues()函数下方添加以下代码:

    function addResponseElement(messageValue, idName){
    globalCounter++;
    var totalInputElements = document.testForm.length;
    var container = document.getElementById('formSubmit');
    container.innerHTML += "<input type=\"text\" value=\"" +messageValue+ "\"name=\""+idName+"\" /><br>";
    if(globalCounter == totalInputElements){
    container.innerHTML += "<input type=\"submit\" value=\"Submit\" />";
    }
    }
    
    

    addResponseElement()所做的就是尝试动态地将输入元素添加到表单的原始输入表单右侧。在这里,您应该发现var container = document.getElementById('formSubmit')很熟悉。它寻找一个 ID 为 formSubmit 的 HTML 元素。之后,我们通过innerHTML方法向这个表单添加 HTML。container.innerHTML += "<input type=\"text\" value=\"" +messageValue+ "\"name=\""+idName+"\" /><br>";尝试将输入添加到<form>标签之间。

    您还应该注意到var totalInputElements = document.testForm.length;。这段代码通过使用length属性确定testForm中的输入元素总数。我们利用这个信息来确定是否处于表单的最后一个输入字段,以便在另一个表单上添加一个提交按钮。

    接下来,我们将创建一个函数,它在第二个名为testFormResponse的表单提交后调用。

  5. 继续在当前文档中,在 addResponseElement()函数下方添加以下代码:

    function checkForm(formObj){
    var totalInputElements = document.testFormResponse.length;
    var nameOfPerson = document.testFormResponse.nameOfPerson.value;
    var birth = document.testFormResponse.birth.value;
    var age = document.testFormResponse.age.value;
    var spending = document.testFormResponse.spending.value;
    var salary = document.testFormResponse.salary.value;
    var retire = document.testFormResponse.retire.value;
    var retirementMoney = document.testFormResponse.retirementMoney.value;
    var confirmedSavingsByRetirement;
    var ageDifference = retire - age; // how much more time can the user have to prepare for retirement
    var salaryPerYear = salary * 12; // salary per year
    var spendingPerYear = spending * 12; // salary per year
    // income per year, can be negative
    // if negative means cannot retire
    // need to either increase spending
    // or decrease spending
    var incomeDifference = salaryPerYear - spendingPerYear;
    if(incomeDifference <= 0){
    buildFinalResponse(nameOfPerson,-1,-1,-1,incomeDifference);
    return true;
    }
    else{
    // income is positive, and there is chance of retirement
    confirmedSavingsByRetirement = incomeDifference * ageDifference;
    if(confirmedSavingsByRetirement <= retirementMoney){
    var shortChange = retirementMoney - confirmedSavingsByRetirement;
    var yearsNeeded = shortChange/12;
    buildFinalResponse(nameOfPerson,false,yearsNeeded,retire, shortChange);
    return true;
    }
    else{
    var excessMoney = confirmedSavingsByRetirement - retirementMoney;
    buildFinalResponse(name,true,-1,retire,excessMoney);
    return true;
    }
    }
    }
    
    

    这个函数中发生的事情相当直接。各种表单值被分配给各种变量。然后我们开始进行一些简单的计算,以查看用户是否有足够的钱退休。您可以通过查看函数中的注释来了解计算的逻辑。

    通常,我们会调用函数buildFinalResponse(),无论用户能否按时退休,以及是否有足够的钱。所以这是buildFinalResponse()

    继续当前文档,在checkForm ()函数下方添加以下代码:

    function buildFinalResponse(name,retiring,yearsNeeded,retire, shortChange){
    var element = document.getElementById("finalResponse");
    if(retiring == false){
    element.innerHTML += "<p>Hi <b>" + name + "</b>,<p>";
    element.innerHTML += "<p>We've processed your information and we have noticed a problem.<p>";
    element.innerHTML += "<p>Base on your current spending habits, you will not be able to retire by <b>" + retire + " </b> years old.</p>";
    element.innerHTML += "<p>You need to make another <b>" + shortChange + "</b> dollars before you retire inorder to acheive our goal</p>";
    element.innerHTML += "<p>You either have to increase your income or decrease your spending.<p>";
    }
    /*
    else if(retiring == -1){
    element.innerHTML += "<p>Hi <b>" + name + "</b>,<p>";
    element.innerHTML += "<p>We've processed your information and we have noticed HUGE problem.<p>";
    element.innerHTML += "<p>Base on your current spending habits, you will not be able to retire by <b>" + retire + " </b> years old.</p>";
    element.innerHTML += "<p>This is because you spend more money than you make. You spend <b>" + shortChange + "</b> in excess of what you make</p>";
    element.innerHTML += "<p>You either have to increase your income or decrease your spending.<p>";
    }
    */
    else{
    // able to retire but....
    element.innerHTML += "<p>Hi <b>" + name + "</b>,<p>";
    element.innerHTML += "<p>We've processed your information and are pleased to announce that you will be able to retire on time.<p>";
    element.innerHTML += "<p>Base on your current spending habits, you will be able to retire by <b>" + retire + "</b> years old.</p>";
    element.innerHTML += "<p>Also, you'll have ' <b>" + shortChange + "</b> amount of excess cash when you retire.</p>";
    element.innerHTML += "<p>Congrats!<p>";
    }
    }
    
    

函数buildFinalResponse()addResponseElement()函数类似。它只是寻找所需的 HTML 元素,并将所需的 HTML 添加到该元素中。

在这里,你可以清楚地看到在这本书中学到的 JavaScript 函数、方法和技巧

保存文件。你可以尝试玩弄这个例子,看看它对你来说是如何工作的

刚才发生了什么?

在前一个例子中,你看到了如何访问表单的值,对输入进行操作,然后将输出放置在网页的不同部分。你可能会注意到我们广泛使用了getElementById。我们还使用了form对象和value方法来访问表单中各个元素的值。然后,通过使用getElementById,我们寻找所需的 HTML 元素,并将输出添加到 HTML 元素中。

但是,在这个时候,你可能会想知道如果你在程序中犯错误,你应该做什么。这就是我们下一节将重点关注的内容。

脚本给出了预期的结果吗?

我的观点是,在我们开始任何有意义的讨论之前,我们必须理解“预期结果”的含义。

“预期结果(s)”可以有几种含义,至少对于这本书的目的来说是这样。例如,如前所述,每个输入的输出都应该是正确的;这里指的是最终的输出。还有一种输出,它以“视觉输出”的形式出现。例如,对于每个用户交互或事件,我们的网络应用程序通常会提供一种视觉提示,让用户知道正在发生的事情。在这种情况下,我们的视觉提示按照我们的意图行事,将被认为是“预期结果**”。

一个简单的提示,为了检查脚本是否给你预期的结果,就是使用简单的输入并进行自己的计算。确保你的计算是正确的,并测试你的程序。

在本章的后半部分,我们将详细讨论两种相关技术。但首先,让我们看看如果我们的脚本没有运行,我们可以采取哪些行动。

脚本不运行时怎么办

如果脚本运行不了,很可能是因为加载或运行时出现了错误,这取决于你的程序是如何编写的。例如,在你刚刚创建的前一个程序中,如果你在输入第一个输入字段后没有回应,并且焦点不再在第一个输入字段上,那么你就知道程序没有在运行。

在这个例子中,有几种可能性(这些都归结于前面章节中提到的 JavaScript 错误的三个基本形式)。首先,可能在 JavaScript 事件的输入字段中存在语法错误,或者在由 JavaScript 事件调用的函数中存在严重的错误。如果不是,可能是逻辑错误。

无论错误可能是什么,通常很难猜测错误是什么以及在哪里。因此,如果您的代码没有运行,我将介绍三种重要的测试代码的技术。

visually inspecting the code(视觉检查代码)

视觉检查代码意味着你将扮演一个人类编译器,并 visually check for errors in your code(视觉检查代码中的错误)。我的看法是,对于视觉检查有一些预设条件和小贴士:

  • 必须有良好的代码块结构。这意味着代码应该适当地隔开并缩进,以提高视觉清晰度。一眼看上去,你应该能够看出哪段代码嵌套在哪个if-else语句下,或者它属于哪个函数。

  • 你使用的代码编辑器有很大的不同。一个常见的错误是括号或反引号的不匹配。因此,一个允许高亮显示匹配括号的代码编辑器将帮助你发现这类错误。

  • 检查每个语句(statement)后面的分号。

  • 检查变量是否已初始化。如果变量在程序的后部分使用但未初始化,将会造成严重的错误。

之前的操作是我如果脚本没有运行或者没有按照我预期的运行方式来运行时会做的一些事情。然而,尽管我们有意愿,但对代码的视觉检查只能对小于 30 到 50 行代码的小程序有用。如果程序再大一些,或者如果它们包含在事件中调用的各种函数,使用 alert 函数来检查我们的代码可能更好(也更有效率)。

使用 alert[] 来查看正在运行的代码

alert 方法可以用来检查正在运行的代码是否被适当地使用。我们还没有正式介绍 alert 方法。但是以防万一,您可以在 JavaScript 程序中的几乎任何地方使用 alert 函数来创建弹出窗口。语法如下:

alert(message)

其中 message 可以接受几乎任意数量的值(或者如果它已经被定义或初始化,可以是变量)。由于 alert 方法的灵活性,它也可以用来显示值、字符串和对象类型。

使用 alert 的问题源于在代码中放置 alert 的位置。这将在下一个动手示例中展示。

使用 alert() 来查看正在使用哪些值

正如前面提到的,alert 方法可以用来显示几乎任何类型的值。因此,常见的用法是将一个变量传递给 alert 方法,看看值是否是我们需要的或预期的。

同样,我们需要知道在哪里应用alert方法,以确保我们的代码检查是正确的。

在这一点上,示例将是查看我们如何使用alert方法检查代码错误的最合适方式。那么,让我们来看看这个是如何工作的。

行动时间——使用 alert 检查你的代码

这个示例与你之前所做的类似。在这个示例中,你需要将alert插入到适当的位置,以检查哪部分代码正在运行。在某些情况下,你需要向alert方法传递值,并看看这个值是否是你想要的。

坦白说,告诉你一步一步应该放置alert方法会很繁琐,尤其是因为本例中大部分代码与上一个类似。然而,为了让你更容易跟随,我们将从整个程序开始,然后向你解释alert方法的位置和传递给alert方法的价值背后的原因。

注意

以下示例的源代码可以在源代码文件夹的第二章中找到,文件名为getting-values-in-right-places-using-alert.html

  1. 这个示例与上一个类似,不同之处在于 JavaScript 代码略有改动。用以下代码替换上一个示例中的 JavaScript 代码:

    var globalCounter = 0;
    function submitValues(elementObj){
    alert("submitValues");
    alert(elementObj.name);
    var totalInputElements = document.testForm.length;
    alert("total elements: " + totalInputElements);
    var digits = /^\d+$/.test(elementObj.value);
    var characters = /^[a-zA-Z\s]*$/.test(elementObj.value);
    alert (characters);
    if(elementObj.value==""){
    alert("input is empty");
    return false;
    }
    else if(elementObj.name == "enterNumber" && digits == false){
    alert("the input must be a digit!");
    return false;
    }
    else if(elementObj.name == "enterText" && characters == false){
    alert("the input must be characters only!");
    return false;
    }
    else{
    alert("you've entered : " + elementObj.value);
    elementObj.disabled = true;
    alert(elementObj.value);
    addResponseElement(elementObj.value,elementObj.id);
    return true;
    }
    }
    function addResponseElement(messageValue, idName){
    alert("addResponseElement");
    globalCounter++;
    var totalInputElements = document.testForm.length;
    alert("totalInputElements");
    var container = document.getElementById('formSubmit');
    container.innerHTML += "<input type=\"text\" value=\"" +messageValue+ "\"name=\""+idName+"\" /><br>";
    if(globalCounter == totalInputElements){
    container.innerHTML += "<input type=\"submit\" value=\"Submit\" />";
    }
    }
    function checkForm(formObj){
    alert("checkForm");
    var totalInputElements = document.testFormResponse.length;
    alert(totalInputElements);
    var nameOfPerson = document.testFormResponse.nameOfPerson.value;
    alert(nameOfPerson);
    var birth = document.testFormResponse.birth.value;
    alert(birth);
    var age = document.testFormResponse.age.value;
    alert(age);
    var spending = document.testFormResponse.spending.value;
    alert(spending);
    var salary = document.testFormResponse.salary.value;
    alert(salary);
    var retire = document.testFormResponse.retire.value;
    alert(retire);
    var retirementMoney = document.testFormResponse.retirementMoney.value;
    alert(retirementMoney);
    var confirmedSavingsByRetirement;
    var ageDifference = retire - age; // how much more time can the user have to prepare for retirement
    alert(ageDifference);
    var salaryPerYear = salary * 12; // salary per year
    alert(salaryPerYear);
    var spendingPerYear = spending * 12; // salary per year
    alert(spendingPerYear);
    var incomeDifference = salaryPerYear - spendingPerYear;
    alert(incomeDifference);
    if(incomeDifference <= 0){
    buildFinalResponse(nameOfPerson,-1,-1,-1,incomeDifference);
    return true;
    }
    else{
    confirmedSavingsByRetirement = incomeDifference * ageDifference;
    if(confirmedSavingsByRetirement <= retirementMoney){
    var shortChange = retirementMoney - confirmedSavingsByRetirement;
    alert(shortChange);
    var yearsNeeded = shortChange/12;
    buildFinalResponse(nameOfPerson,false,yearsNeeded,retire, shortChange);
    return true;
    }
    else{
    var excessMoney = confirmedSavingsByRetirement - retirementMoney;
    alert(excessMoney);
    buildFinalResponse(name,true,-1,retire,excessMoney);
    return true;
    }
    }
    }
    function buildFinalResponse(name,retiring,yearsNeeded,retire, shortChange){
    alert("buildFinalResponse");
    var element = document.getElementById("finalResponse");
    if(retiring == false){
    alert("if retiring == false");
    element.innerHTML += "<p>Hi <b>" + name + "</b>,<p>";
    element.innerHTML += "<p>We've processed your information and we have noticed a problem.<p>";
    element.innerHTML += "<p>Base on your current spending habits, you will not be able to retire by <b>" + retire + " </b> years old.</p>";
    element.innerHTML += "<p>You need to make another <b>" + shortChange + "</b> dollars before you retire inorder to acheive our goal</p>";
    element.innerHTML += "<p>You either have to increase your income or decrease your spending.<p>";
    }
    else{
    // able to retire but....
    alert("retiring == true");
    element.innerHTML += "<p>Hi <b>" + name + "</b>,<p>";
    element.innerHTML += "<p>We've processed your information and are pleased to announce that you will be able to retire on time.<p>";
    element.innerHTML += "<p>Base on your current spending habits, you will be able to retire by <b>" + retire + "</b> years old.</p>";
    element.innerHTML += "<p>Also, you'll have <b>" + shortChange + "</b> amount of excess cash when you retire.</p>";
    element.innerHTML += "<p>Congrats!<p>";
    }
    }
    
    
  2. 保存文档并在网页浏览器中加载它。玩转示例,看看警告框是如何通知你哪部分代码正在执行,以及输入了哪些值。

刚刚发生了什么?

如果你浏览了之前的示例,你会注意到alert()通常放在函数的开始处,以及在变量被初始化时。为了检查函数,我们通常会手动输入函数的名称,并将其作为参数传递给alert方法,以通知我们程序的互动过程中发生了什么。同样,我们将定义的变量(表单元素的值)作为参数传递给alert方法,以通知我们用户输入了哪些值。

因此,通过使用一个alert()方法,我们能够找出正在运行的代码和正在使用的值。然而,这个方法可能有些过于繁琐或令人沮丧,因为警告框会不断地在你的窗口上弹出。这里有一个简单的替代方法,用于检查正在运行的代码,以及检查输入元素。

检查代码正在运行以及使用哪些值的一种不那么侵扰性的方法

为了以一种不那么侵扰的方式测试我们的代码,我们会写一个简单的调试函数。这个调试函数应该打印出函数的名称和其他一些变量。为了简单起见,我们将展示一个简单的调试函数,它打印出函数的名称和正在使用的 HTML 元素。那么,让我们开始吧。

行动时间—检查值的使用是否突兀

如上所述,我们将演示一个非常简单的调试函数,帮助你识别正在运行的代码以及正在使用的 HTML 元素。在这里,你会对如何以一种不那么突兀的方式测试你的代码有一些基本了解。

再次,这个例子与上一个例子相似,但有一些重要的元素我们将添加到上一个例子中。本质上,我们将在其中添加一个函数、一些 HTML 和 CSS。

然而,你可能会发现回头参考上一个例子并给上一个例子添加新元素很繁琐。因此,建议你跟随这个例子。

注意

另外,你可以在源代码文件夹第二章中查看源代码,文件名为getting-value-in-right-places-complete.html

所以,不再赘述,让我们马上开始:

  1. <style>标签之间插入以下 CSS 代码:

    /* this is for debugging messages */
    #debugging{
    float:left;
    margin-left:820px;
    height:95%;
    width:350px;
    border:solid 3px red;
    padding:5px;
    color:red;
    font-size:10px;
    }
    
    
  2. 现在,对于将包含调试信息的 HTML 容器,请在</body>标签前输入以下代码片段:

    <div id="debugging"><h3>Debugging messages: </h3></div>
    
    

    在这里发生的情况是,之前的 HTML 元素将被用来在调试信息与简单应用程序本身之间提供视觉分隔。现在保存文件,然后用网页浏览器打开它,你会看到一个类似于下一张截图中的示例:

    行动时间—检查值的使用是否突兀

  3. 接下来,你需要将以下代码添加到你的 JavaScript 代码中:

    function debuggingMessages(functionName, objectCalled, message){
    var elementName;
    if(objectCalled.name){
    elementName = objectCalled.name;
    }
    else if(objectCalled.id){
    elementName = objectCalled.id;
    }
    else{
    elementName = message;
    }
    var element = document.getElementById("debugging");
    element.innerHTML += "Function name :" +functionName+ "<br>element :" +elementName+"<br>";
    }
    
    

    前面提到的函数用于捕获当前使用的函数名称;这相当于当前正在使用什么代码,因为我们的程序是事件驱动的,函数通常是由用户触发的。

    这三个参数如下:

    • functionName指的是当前使用的函数的 functionName。在下一步,你将看到动态获取这个值的方法。

    • objectCalled指的是正在使用的 HTML 对象。

    • Message指的是一个字符串。这可以是任何你想要的消息;它的目的是为你提供在屏幕上编写调试消息的灵活性。

      此外,我们使用了.innerHTML方法将消息追加到 ID 为"debugging"的 HTMLdiv元素中。

  4. 现在最后,是时候看看我们如何使用这个函数了。通常,我们按照以下方式使用函数:

    debuggingMessages("name of function", elementObj,"empty");
    
    

    如果你查看源代码,你会看到前面提到的函数在程序中谨慎使用。考虑以下代码片段:

    function submitValues(elementObj){
    //alert("submitValues");
    debuggingMessages("submitValues", elementObj,"empty"); 
    //alert(elementObj.name);
    var totalInputElements = document.testForm.length;
    //alert("total elements: " + totalInputElements);
    
    

    在前一个案例中,"submitValues"的值将被传递,因为submitValues是函数的名称。注意我们也把函数参数elementObj传递给debuggingMessages(),以便通知我们当前函数中使用了什么。

  5. 最后,你可能想要在 JavaScript 程序中的每个函数中添加debuggingMessages("function name", elementObj,"empty")。如果你不确定应该在哪个地方使用这个函数,请参考给出的源代码。

    如果你自己正在输入函数,那么请注意你可能需要更改参数名称以适应每个函数。通常,debuggingMessages()可以替代alert()方法。所以,如果你不确定应该在哪个地方使用debuggingMessages(),你可以将debuggingMessages()用于前一个示例中用于检查代码的每个alert()

  6. 如果你已经执行了整个程序,你会看到类似于下一张截图的东西:行动时间—不侵扰地检查使用哪些值

刚才发生了什么?

你刚刚创建了一个函数,它允许你以一种不那么侵扰的方式检查你的代码,这是通过利用 JavaScript 的一些内置方法实现的,包括.innerHTML方法。这里发生的事情是另一个例子,展示了你如何访问值、操纵它们,然后将这些值输出到所需的 HTML 元素,以使检查变得更不侵扰。

如果你查看源代码,你可能会注意到我在不同的情况下使用了不同的消息;如果你使用的话,这将为你的调试函数带来更多的灵活性。

注释掉脚本的一部分以简化测试

注释掉脚本的一部分是测试你的 JavaScript 代码的另一种重要且简单易用的权宜之计。本质上,你注释掉那些立即不会使用的代码。

因为我们还没有介绍如何进行多行注释,所以我借此机会向你展示如何使用它。语法如下:

/*
This is a multiple line comment
*/

注释掉脚本的一部分可以用来简化测试的方法如下:我们通常会注释掉所有我们一开始不会使用的其他代码。例如,getting-values-right-places-complete.html中使用的第一个函数是submitValues()函数。

我们会在取消注释用于的第二个函数之前,确保submitValues()函数是正确的,这个第二个函数是addResponseElement()函数。

这个过程一直持续到所有函数都被取消注释,这意味着代码是正确的。

考虑到所有这些点,我们现在将转到基于前一个示例的简单练习。

行动时间——简化检查过程

在这个例子中,不会有源代码供你复制。相反,你可以使用在getting-values-right-places-complete.html中找到的前一个示例,并尝试以下步骤:

  1. 滚动到源代码的 JavaScript 部分。注释掉所有除了submitValues()addResponseElement()之外的函数。

  2. 保存文件并加载到你的网页浏览器中。现在测试一下这个程序。

    您应该注意到,您的程序仍然可以运行,但是当所有输入字段都正确填写后,您将无法成功提交表单。

    这是因为您注释掉了checkForm()函数,这个函数对于第二次表单提交是必需的。

    这意味着什么?这意味着submitValues()addResponseElement()函数运行正常,现在可以安全地进行下一步。

  3. 现在,取消注释checkForm()buildFinalResponse()debuggingMessages()函数,保存文件并在浏览器中重新加载。继续测试您的程序,直到您提交表单。

    您应该注意到,在提交第二份表单之前,所有事情都进行得很顺利。这是因为您在上一个步骤中已经测试过,所以预料到了这种情况。

    现在,在您完成所有输入字段后,提交表单。因为您已经取消注释了checkForm()buildFinalResponse()函数,现在提交表单后您应该期待有一个回应。

  4. 最后,取消注释debuggingMessages()函数。保存文件并在浏览器中加载它

现在,同样地,像往常一样使用程序,您应该看到所有必要的功能都像以前一样正常工作。

刚才发生了什么?

您刚刚通过取消注释代码的不同部分,以一种有用的方法测试了您的代码。您可能注意到我们从第一个将要使用的函数开始,然后继续到下一个。这个过程将帮助我们找到包含错误的代码块。

这种技术也可以应用于代码语句。我们注释掉了函数中的代码,因为根据示例,这样更容易跟踪。

时机差异——确保在交互之前 HTML 已经准备好了

记住 JavaScript 的本质是通过操作 DOM 元素为网页提供行为吗?这是一个关键点——如果 HTML 在例如执行改变表单颜色的 JavaScript 函数时不可用,那么 JavaScript 函数将无法工作。

在这种情况下,问题不是由于 JavaScript 错误,比如逻辑、运行时和加载错误,而是由于时机问题。

如前章所述,网络浏览器(客户端)从服务器下载一个网页,通常是从上到下读取网页(文档)。因此,例如,如果您有一个大型的 HTML 文档(例如一个在主体中有大图像的 HTML 文档),您的 JavaScript 可能无法与 HTML DOM 交互,因为没有 HTML 可以与之交互。

有两条解决方法可以让我们解决这个问题:

  1. 使用<body>标签的 JavaScript 事件onload。这可以按照以下方式进行:

    <html>
    <head>
    <script>
    function aSimpleFunction()
    {
    alert(window.status);
    }
    </script>
    </head>
    <body onload="aSimpleFunction()">
    </body>
    </html>
    
    

    高亮的行意味着aSimpleFunction()仅在<body>标签中的内容加载完成后执行。您可以利用这个技术确保在执行您的 JavaScript 函数之前,您的 HTML 内容已经加载完成。

    这里有一个(可能是更受欢迎的方法):

  2. 将你的 JavaScript 函数放在</body>标签之前。

这个方法通常被使用;你可以看到提供分析服务的公司通常要求其用户在</body>标签之前放置跟踪代码(通常是 JavaScript,如 Google 分析)。这意味着 JavaScript 代码片段将在<body>标签中的所有内容加载完成后加载,确保 HTML DOM 将与 JavaScript 交互。

为什么临时测试永远不够

到目前为止,你可能已经注意到,为临时测试介绍的方法在应用到你的代码时可能会变得重复。例如,alert方法需要你手动在不同代码部分输入alert函数,包含不同的值,以便你检查代码。这可能会变得繁琐且低效,尤其是当程序开始变得更大时。简单地说,当程序变得太大时,它将无法扩展。同时,alert方法可能会相当显眼。因此,我们创建了一个简单的调试功能。

我们创建的简单调试功能较为不显眼;你可以与程序互动,并在屏幕上获得几乎即时的反馈。尽管它具有不太显眼的优点,但它有两个主要的缺点。第一个缺点是它可能既繁琐又低效,这与alert方法相似。第二个缺点是调试功能的优劣在很大程度上取决于 JavaScript 程序员的技能。然而,作为 JavaScript 的初学者,我们可能有没有创建健壮调试功能的技能。

因此,当需要时,还有其他更强大的工具可以帮助我们完成工作,我们将在后面的章节中讨论这些工具。

总结

在本章中,我们在前一章学到的基础知识上进行了构建,并扩展了我们可以使用本章介绍的各种技术进行临时测试的知识。

总的来说,我们将前一章和本章介绍的各种方法和技巧结合起来,以帮助我们进行临时测试。我们经常通过getElementById查找所需的元素,然后通过form对象访问表单值。我们还使用了alert()方法进行某种形式的临时测试。

具体来说,我们已经介绍了以下主题:

  • 我们学习了如何使用form对象及其方法访问表单上的值,操纵值,并使用前一章学到的技术将值输出到网页的其他部分,例如getElementById。我们通过.innerHTML将 HTML 内容附加到特定的 HTML 元素上。

  • 如果脚本没有提供预期的输出,我们可以采取的行动,即使用alert()方法测试脚本并注释掉代码。这引导我们进行临时测试。

  • 执行临时性测试的各种技术,最值得注意的是,通过使用alert()方法。由于它明显的干扰性,我们创建了一个简单的调试函数,提供了一种不那么干扰的测试方式。

  • 时间差异:我们必须始终确保 HTML DOM 在 JavaScript 可以与其交互之前是可用的。

  • 由于可扩展性和效率问题,临时性测试永远不够。

既然我们已经理解并尝试了临时性测试,现在是时候学习一些关于 JavaScript 测试的更高级内容了。如前所述,尽管临时性测试快速简单,但它并不一定能带来更好的 JavaScript 代码(除了它其他缺点之外)。在下一章,我们将学习如何验证 JavaScript。尽管这个概念听起来简单,但你在实际编码和设计过程中,以及可以帮助你验证你的 JavaScript 程序的其他因素方面,你会学到更多关于 JavaScript 的概念。

第三章:语法验证

为了巩固我们之前学到的知识,我们现在将转向一个稍微困难的话题——验证 JavaScript。在本章中,你可以期待两个主要话题——围绕 JavaScript 代码验证和测试的问题,以及如何使用 JSLint 和 JavaScript Lint(这是一个免费的 JavaScript 验证器)来检查你的 JavaScript 代码,以及如何调试它们。我会明确地展示如何使用 JSLint 发现验证错误,然后,如何修复它们。

我们将简要介绍验证和测试 JavaScript 之间的区别以及你在验证或测试代码时可能需要考虑的一些问题。你还将了解有效的 HTML 和 CSS 与 JavaScript 之间的关系,以及如何尝试编写高质量的代码以帮助你减少 JavaScript 代码中的错误。更重要的是,我们将学习到两个常用于验证 JavaScript 代码的免费工具,如何利用它们检查你的代码,以及最重要的是,如何修复检测到的验证错误。

在本章中,我们将学习以下主题:

  • 验证和测试之间的区别

  • 一个好的代码编辑器如何帮助你发现验证错误

  • 什么使代码质量高

  • 为什么在开始操作 JavaScript 之前需要确保 HTML 和 CSS 是有效的

  • 为什么嵌入在 HTML 中的 JavaScript 可能会被报告为无效

  • 通过验证检测到的常见 JavaScript 错误

  • JSLint 和 JavaScript Lint——如何使用它们检查你的代码

  • 产生验证警告的有效代码结构

  • 如何修复由 JSLint 发现的验证错误

那么,不再赘述,让我们开始讨论一个较轻松的话题——验证和测试之间的区别。

验证和测试之间的区别

有效验证和测试之间有一条细微的界限。如果你对集合(如数学中的集合)有一些了解,我会说验证可以导致更好的测试结果,而测试不一定导致有效代码。

让我们考虑这样一个场景——你编写了一个 JavaScript 程序,并在 Internet Explorer 和 Firefox 等主要浏览器上进行了测试,并且它运行正常。在这种情况下,你已经测试了代码,以确保它是功能性的。

然而,你所创建的同一代码可能有效也可能无效;有效代码类似于具有以下特点的代码:

  • 格式良好

  • 具有良好的编码风格(如适当的缩进、注释良好的代码、适当的间距)

  • 符合语言规范(在我们这个案例中,是 JavaScript)

注意

在某个时间点,你可能会注意到良好的编码风格是非常主观的——有各种验证器可能对所谓的“良好编码风格”有不同的意见或标准。因此,如果你用不同的验证器来验证你的代码,当你看到不同的编码风格建议时,不要惊慌。

这并不意味着有效的代码会导致具有功能的代码(如你所见)以及具有功能的代码会导致有效的代码,因为两者有不同的比较标准。

然而,有效的代码通常会导致更少的错误,既有功能又是有效的代码通常是高质量代码。这是因为编写一段既有效又正确的 JavaScript 代码,比仅仅编写正确的代码要困难得多。

测试通常意味着我们试图让代码正确运行;而验证则是确保代码在语法上是正确的,有良好的风格,并且符合语言规范。虽然良好的编码风格可能是主观的,但通常有一种被大多数程序员接受的编码风格,例如,确保代码有适当的注释、缩进,并且没有全局命名空间的污染(尤其是在 JavaScript 的情况下)。

为了使情况更清晰,以下是三个你可以考虑的情况:

代码是有效的但却是错误的——验证并不能找到所有的错误。

这种错误形式很可能是由 JavaScript 中的逻辑错误引起的。考虑我们之前学到的内容;逻辑错误可能在语法上是正确的,但可能在逻辑上是错误的。

一个典型的例子可能是一个无限for循环或无限while循环。

代码是无效的但却是正确的

这很可能是大多数功能性代码的情况;一段 JavaScript 可能是功能上正确并且运行正常,但它可能是无效的。这可能是由于编码风格不良或有效代码中缺失的其他特征引起的。

在本章后面,你将看到一个完整的、有效的 JavaScript 代码示例。

无效且错误的代码——验证可以找到一些可能用其他方法难以发现的错误

在这种情况下,代码错误可能由第一章中提到的 JavaScript 错误的三个形式引起,什么是 JavaScript 测试,加载错误,运行时错误和逻辑错误。虽然由语法错误引起的错误可能更容易被好的验证器发现,但也有可能一些错误深深地隐藏在代码中,以至于使用手动方法很难发现它们。

既然我们已经对验证和测试有了共同的理解,那么让我们继续下一部分,讨论围绕高质量代码的问题。

代码质量

虽然关于高质量代码有很多观点,我个人认为有一些公认的标准。一些最常提到的标准可能包括代码的可读性,易于扩展,效率,良好的编码风格,以及符合语言规范等。

在这里,我们将关注使代码有效的一些因素——编码风格和符合规范。一般来说,良好的编码风格几乎可以保证代码高度可读(甚至对第三方也是如此),这将有助于我们手动发现错误。

最重要的是,良好的编码风格使我们能够快速理解代码,特别是如果我们需要团队合作或需要独立进行代码调试时。

你会注意到,我们将重点关注代码有效性对于测试目的的重要性。但现在,让我们从质量代码的第一个构建块开始——有效的 HTML 和 CSS。

在开始 JavaScript 之前,HTML 和 CSS 需要是有效的

在第一章中,我们有一个共同的理解,JavaScript 通过操作 HTML 文档的文档对象模型(DOM)为网页注入生命力。这意味着在 JavaScript 可以操作 DOM 之前,DOM 必须存在于代码中。

注意

这里有一个与 HTML、CSS 和浏览器直接相关的重要事实,相比 C 或 Python 等语言的编译器,浏览器对无效的 HTML 和 CSS 代码更加宽容。这是因为,所有浏览器需要做的就是解析 HTML 和 CSS,以便为用户渲染网页。另一方面,编译器通常对无效代码毫不留情。任何缺失的标签、声明等都会导致编译错误。因此,编写无效的或甚至是含有错误的 HTML 和 CSS 是可以的,但仍能得到一个“通常”外观的网页。

根据之前的解释,我们应该明白,为了创建高质量的 JavaScript 代码,我们需要有效的 HTML 和 CSS。

以下是我根据个人经验总结的,在开始学习 JavaScript 之前,为什么需要具备有效的 HTML 和 CSS 知识的原因:

  • 有效的 HTML 和 CSS 有助于确保 JavaScript 按预期工作。例如,考虑这样一种情况,你可能有两个具有相同iddiv元素(在之前的章节中,我们已经提到div id属性是给每个 HTML 元素提供唯一 ID 的),而你的 JavaScript 包含了一段预期对具有上面提到的 ID 的 HTML 元素工作的代码。这将导致意想不到的结果。

  • 有效的 HTML 和 CSS 有助于提高你的网页行为的可预测性;试图用 JavaScript 修复错误的 HTML 或 CSS 是没有意义的。如果你从一开始就使用有效的 HTML 和 CSS,然后应用 JavaScript,你很可能会得到更好的结果。

  • 无效的 HTML 和 CSS 可能会导致不同浏览器中出现不同的行为。例如,一个未闭合的 HTML 标签在不同浏览器中可能会有不同的显示效果。

总之,创建高质量 JavaScript 代码最重要的构建块之一是拥有有效的 HTML 和 CSS。

如果你不验证你的代码会发生什么

你可能会不同意我上文关于为什么 HTML 和 CSS 应该有效的观点。一般来说,验证有助于你防止与编码风格和规格相关的错误。然而,请注意,使用不同的验证器可能会给你不同的结果,因为验证器可能在代码风格方面有不同的标准。

如果你在想无效的代码是否会影响你的 JavaScript 代码,我会建议你尽可能让代码有效;无效的代码可能会导致棘手的问题,比如跨浏览器不兼容、代码难以阅读等。

无效代码意味着你的代码可能不是万无一失的;在互联网的早期,有些网站依赖于早期 Netscape 浏览器的怪癖。回想一下,当 Internet Explorer 6 被广泛使用时,也有许多网站以怪癖模式工作以支持 Internet Explorer 6。

现在,大多数浏览器都支持或正在支持网络标准(尽管有些细微的差异,但它们以微妙的方式支持),编写有效的代码是确保你的网站按预期工作和工作表现的最佳方式之一。

如何通过验证简化测试

虽然无效的代码可能不会导致你的代码功能失效,但有效的代码通常可以简化测试。这是因为有效代码关注编码风格和规格,符合规格的有效代码通常更可能正确,且更容易调试。考虑以下风格上无效的代码:

function checkForm(formObj){
alert(formObj.id)
//alert(formObj.text.value);
var totalFormNumber = document.forms.length;
// check if form elements are empty and are digits
var maxCounter = formObj.length; // this is for checking for empty values
alert(totalFormNumber);
// check if the form is properly filled in order to proceed
if(checkInput(formObj)== false){
alert("Fields cannot be empty and it must be digits!");
// stop executing the code since the input is invalid
return false;
}
else{
;
}
var i = 0;
var formID;
while(i < totalFormNumber){
if(formObj == document.forms[i]){
formID = i;alert(i);
}
i++;
}
if(formID<4){
formID++;
var formToBeChanged = document.forms[formID].id;
// alert(formToBeChanged);
showForm(formToBeChanged);
}
else{
// this else statement deals with the last form
// and we need to manipulate other HTML elements
document.getElementById("formResponse").style.visibility = "visible";
}
return false;
}

你熟悉之前的代码吗?还是没有意识到之前的代码片段来自第二章,《JavaScript 中的即兴测试与调试》?

之前的代码是代码风格不佳的一个极端例子,尤其是在缩进方面。想象一下,如果你必须手动调试你之前看到的第二个代码片段!我敢肯定,你会觉得检查代码很沮丧,因为你几乎无法从视觉上了解发生了什么。

更重要的是,如果你在团队中工作,你将被要求编写可读的代码;总之,编写有效的代码通常会导致代码更具可读性,更容易理解,因此错误更少。

验证可以帮助你调试代码

如上文所述,浏览器通常对无效的 HTML 和 CSS 比较宽容。虽然这是真的,但可能会有一些错误没有被捕捉到,或者没有正确或优雅地渲染。这意味着虽然无效的 HTML 和 CSS 代码在某个平台或浏览器上可能看起来没问题,但在其他平台上可能不受支持。

这意味着使用有效的代码(有效的代码通常意味着由国际组织如 W3C 设定的标准代码集)将使你的网页在不同的浏览器和平台上正确渲染的概率大大增加。

有了有效的 HTML 和 CSS,您可以安全地编写您的 JavaScript 代码并期望它按预期工作,前提是您的 JavaScript 代码同样有效且无错误。

验证帮助您使用好的编程实践

有效的代码通常需要使用好的编程实践。正如本章中多次提到的,好的实践包括适当闭合标签,合适的缩进以提高代码可读性等。

如果您需要更多关于使用 JavaScript 的良好实践的信息,请随时查看 JSLint 的创建者,Douglas Crockford,在 crockford.com。或者您可以阅读 John Resigs 的博客(JQuery 的创建者)在 ejohn.org/。他们都是很棒的人,知道什么是伟大的 JavaScript。

验证

总结以上各节,DOM 由 HTML 提供,CSS 和 JavaScript 都应用于 DOM。这意味着如果存在无效的 DOM,那么操作 DOM 的 JavaScript(有时包括 CSS)可能会导致错误。

带着这个总结在心中,我们将重点关注如何使用颜色编码编辑器来发现验证错误。

颜色编码编辑器—您的编辑器如何帮助您发现验证错误

如果您是一个有经验的程序员,您可以跳过这一节;如果不是,您可能想了解一个好的编程编辑器的价值。

总的来说,一个好的编辑器可以帮助您防止验证错误。基于我们对验证的理解,您应该明白,您的编辑器应该执行以下活动:

  • 突出显示匹配的括号

  • 多种语法高亮

  • 关键字、括号等之后的自动缩进

  • 自动补全语法

  • 自动补全您已经输入的单词

您可能注意到了,我遗漏了一些要点,或者增加了一些要点,关于好的编辑器应该做什么。但总的来说,前面列出的要点是为了帮助您防止验证错误。

注意

作为一个开始,您可以考虑使用微软的 SharePoint Designer 2007,这是一个免费、功能丰富的 HTML、CSS 和 JavaScript 编辑器,可在 www.microsoft.com/downloads/details.aspx?displaylang=en&FamilyID=baa3ad86-bfc1-4bd4-9812-d9e710d44f42获得

例如,突出显示匹配的括号是为了确保您的代码用括号正确闭合,自动缩进是为了确保您为代码块使用了统一的空格。

尽管 JavaScript 代码块通常用花括号表示,但使用缩进来 visually 显示代码结构非常重要。考虑以下代码片段:

function submitValues(elementObj){
var digits = /^\d+$/.test(elementObj.value);
var characters = /^[a-zA-Z\s]*$/.test(elementObj.value);
if(elementObj.value==""){
alert("input is empty");
return false;
}
else if(elementObj.name == "enterNumber" && digits == false){
alert("the input must be a digit!");
debuggingMessages(arguments.callee.name, elementObj, "INPUT must be digit");
return false;
}
else if(elementObj.name == "enterText" && characters == false){
alert("the input must be characters only!");
return false;
}
else{
elementObj.disabled = true;
return true;
}
}

下一个代码片段如下:

function submitValues(elementObj)
{
var digits = /^\d+$/.test(elementObj.value);
var characters = /^[a-zA-Z\s]*$/.test(elementObj.value);
if(elementObj.value=="")
{alert("input is empty");
return false;
}
else if(elementObj.name == "enterNumber" && digits == false)
{alert("the input must be a digit!");
return false;
}else if(elementObj.name == "enterText" && characters == false)
{alert("the input must be characters only!");
return false;
}
else
{
elementObj.disabled = true;
return true;
}
}

我非常确信,您会认为第二个代码片段很乱,因为它的缩进不一致,您可能会遇到分辨哪个语句属于哪个条件块的问题。

从风格上讲,第二个代码示例就是我们所说的“糟糕的代码风格”。您可能会惊讶这可能会导致验证错误。

注意

如果您想知道/^[a-zA-Z\s]*$//^\d+$/是什么,它们实际上是正则表达式对象。正则表达式起源于 Perl(一种编程语言),由于它们的实用性,许多编程语言现在都有自己的正则表达式形式。大多数它们的工作方式相同。如果您想了解更多关于 JavaScript 正则表达式的信息,请随时访问www.w3schools.com/jsref/jsref_obj_regexp.asp以了解正则表达式是如何工作的简要介绍。

JavaScript 中常见的错误,将由验证工具检测到

我会简要提到一些由验证器检测到的最常见的验证错误。以下是它们的简短列表:

  • 不一致的空格或缩进

  • 缺少分号

  • 缺少闭合括号

  • 使用在调用或引用时未声明的函数或变量

注意

您可能已经注意到,一些验证错误并不是 exactly "错误"——就像语法错误——而是风格上的错误。如前所述,编码风格上的差异不一定导致功能错误,而是导致风格错误。但良好的编码风格的一个好处是,它通常会导致更少的错误。

至此,您可能很难想象这些常见错误实际上看起来是什么样子。但不要担心,当我们引入 JavaScript 验证工具时,您将能看到这些验证错误的实际操作。

JSLint—在线验证器

JSLint 是我们将重点介绍的第一种 JavaScript 验证代码。通过访问这个 URL:www.jslint.com,您可以访问 JSLint。JSLint 在线验证器是由道格拉斯·克罗克福德创建的工具。

注意

道格拉斯·克罗克福德(Douglas Crockford)在雅虎!担任 JavaScript 架构师。他还是设计 JavaScript 未来版本的委员会成员。他在 JavaScript 风格和编程实践方面的观点普遍受到认可。您可以在他的网站上了解更多关于他和他的想法:www.crockford.com

总的来说,JSLint 是一个在线 JavaScript 验证器。它帮助验证您的代码。同时,JSLint 足够智能,可以检测到一些代码错误,比如无限循环。JSLint 网站并不是一个特别大的网站,但无论如何,您必须阅读的两个重要链接如下:

我不会试图向你描述 JSLint 是关于什么以及如何使用它;我个人认为应该亲自动手试试。因此,首先,我们将测试我们在第二章中编写的代码,Ad Hoc Testing and Debugging in JavaScript,看看会出现哪些验证错误(如果有的话)。

是行动的时候了——使用 JSLint 查找验证错误

正如前面提到的,我们将测试我们在第二章中编写的代码,Ad Hoc Testing and Debugging in JavaScript,看看我们会得到哪些验证错误。请注意,这个示例的完整验证代码可以在source code文件夹的第三章中找到,文件名为perfect-code-for-JSLint.html

  1. 打开你的网页浏览器,导航至www.jslint.com。你应该会看到一个带有巨大文本区域的首页。这就是你将要复制和粘贴你的代码的地方。

  2. 请前往第二章source code文件夹,打开名为:getting-values-in-right-places-complete.html的文件。然后,将源代码复制并粘贴到步骤 1 中提到的文本区域。

  3. 现在点击名为JSLint的按钮。

    你的页面应该会立即刷新,并且你会收到一些形式的反馈。你可能会注意到你收到了很多(是的,很多)验证错误。而且,很可能会有一些对你来说是不懂的。然而,你应该能够识别出一些验证错误是在关于常见 JavaScript 验证错误的章节中引入的。

    现在,向下滚动,你应该在反馈区域看到以下短语:

    xx % scanned
    too many errors
    
    

    这告诉你 JSLint 只是扫描了代码的一部分,并停止了扫描代码,因为错误太多了。

    我们能对此做些什么呢?如果验证错误太多,你一次无法找出所有的错误怎么办?

    不要担心,因为 JSLint 是健壮的,并且有选项设置,这些设置可以在www.jslint.com/#JSLINT_OPTIONS找到(这实际上位于 JSLint 主页的底部)。需要你输入的一个选项是最大错误数。在我们的例子中,你可能想输入一个巨大的数字,比如 1,000,000。

  4. 输入一个巨大的数字作为最大错误数的输入框后,点击The good parts按钮。你会看到有几个复选框被选中了。

    在步骤 4 之后,你现在正式选择了被称为“The Good Parts”的选项,这是由本工具的作者设定的。这是一个设置,它会自动设置作者认为最重要的验证检查。

    这些选项包括:严格空格,每个函数允许一个 var 声明等等。

  5. 现在点击JSLint按钮。你的浏览器将显示新的验证结果。现在你可以看看 JSLint 检测到的验证错误类型。

刚才发生了什么?

你刚才使用了 JSLint 来查找验证错误。这是 JSLint 的一个简单过程:将你的代码复制粘贴到文本区域,然后点击JSLint。不要对出现的这么多验证错误感到惊讶;我们才刚刚开始,我们将学习如何修复和避免这些验证错误。

注意

你可能注意到了,嵌入在 HTML 表单中的 JavaScript 导致了一个错误,提示缺少 use strict 语句。这个错误源于 JSLint 相信使用use strict语句,这使得代码能够在严格条件下运行。你将在本章的后部分学习如何修复和避免这类问题。

你将继续看到许多错误。在我看来,这是验证代码不容易实现的一个证据;但这是我们将在下一节实现的内容。

正如你所看到的,有各种各样的验证选项,在这个阶段,我们只要能让代码通过好部分的设定要求就足够了。因此,我们接下来将重点放在如何修复这些验证错误上。但在那之前,我会简要讨论产生验证警告的有效代码结构。

产生验证警告的有效代码结构

你可能注意到了,尽管我们的代码结构是有效的,但它产生了验证警告。你可能在想是否应该修复这些问题。以下是一些基本讨论,帮助你做出决定。

你应该修复产生验证警告的有效代码结构吗?

这取决于你的目标。如我在第一章中提到的,《什么是 JavaScript 测试?》,代码至少应该是正确的,按我们的意图工作。因此,如果你的目标是仅创建功能上正确的代码,那么你可能不想花时间和精力来修复这些验证警告。

然而,因为你正在阅读这本书,你很可能会想知道如何测试 JavaScript,正如你在本章后面将看到的,验证是测试 JavaScript 的重要部分。

如果你不修复它们会发生什么

无效代码的主要问题是,它将使代码的维护变得更为困难,在可读性和可扩展性方面。当团队合作时,这个问题会变得更严重,因为其他人必须阅读或维护你的代码。

有效的代码促进了良好的编程实践,这将帮助你避免将来出现的问题。

如何修复验证错误

本节将继续讨论上一节中提到的错误,并尝试一起修复它们。在可能的情况下,我会解释为什么某段代码会被认为是无效的。同时,编写有效且功能性的代码整个过程可能会很繁琐。因此,我会先从更容易修复的校验错误开始,然后再逐步转向更难的错误。

注意

在我们修复上一节中看到的校验错误的过程中,你可能会意识到修复校验错误可能需要在编写代码的方式上做出一些妥协。例如,你会了解到在代码中谨慎使用alert()并不是一种好的编程风格,至少根据 JSLint 的说法是这样的。在这种情况下,你必须将所有的alert()声明合并到一个函数中,同时仍保持代码的功能性。更重要的是,你也会意识到(或许)编写有效代码的最佳方式是从代码的第一行开始就编写有效的代码;你会看到修复无效代码是一个极其繁琐的过程,有时你只能尽量减少校验错误。

在修复代码的过程中,你将有机会练习重要的 JavaScript 函数,并学习如何编写更好的代码风格。因此,这可能是本章最重要的部分,我鼓励你和我一起动手实践。在开始修复代码之前,我首先总结一下 JSLint 发现的错误类型。

  • 缺少“use strict”声明。

  • 意外使用了++

  • ), value, ==, if, else, +后面缺少空格。

  • 函数名(如 debuggingMessages)未定义或在使用定义之前就使用了函数。

  • var声明过多。

  • 使用了===而不是==

  • alert未定义。

  • 使用了<\/而不是</

  • 使用了 HTML 事件处理程序。

不再多说,我们直接开始讲解第一个校验错误:use strict

缺少“use strict”声明的错误。

use strict语句是 JavaScript 中相对较新的特性,它允许我们的 JavaScript 在严格环境中运行。通常,它会捕获一些鲜为人知的错误,并“强制”你编写更严格、有效的代码。JavaScript 专家 John Resig 就此话题写了一篇很好的总结,你可以通过这个链接阅读它:ejohn.org/blog/ecmascript-5-strict-mode-json-and-more/

行动时间——修复"use strict"错误。

这个错误非常容易修复。但小心;如果代码无效,启用use strict可能会导致你的代码无法正常工作。以下是修复这个校验错误的方法:

  1. 打开你的文本编辑器,复制并粘贴我们一直在使用的相同代码,并在你的 JavaScript 代码的第一行添加以下代码片段:

    "use strict";
    
    
  2. 保存你的代码并在 JSLint 上测试它。你会发现,现在错误已经消失了。

你可能会注意到还有一个关于你的 HTML 表单的另一个缺失的use strict错误;不要担心,我们会在本章的稍后部分解决这个问题。现在让我们继续下一个错误。

错误—意外使用++

这段代码在程序上没有问题。我们使用++的目的是在调用函数addResponseElement()时递增globalCounter

然而,JSLint 认为使用++有问题。以下代码片段为例:

var testing = globalCounter++ + ++someValues;
var testing2 = ++globalCounter + someValues++;

之前的陈述对大多数程序员来说可能看起来很困惑,因此被认为是坏的风格。更重要的是,这两个陈述在程序上是不同的,产生不同的结果。出于这些原因,我们需要避免使用++--等这样的语句。

行动时间—修复"意外使用++"的错误

这个错误相对容易修复。我们只需要避免使用++。所以,导航到addResponseElement()函数,寻找globalCounter++。然后将globalCounter++更改为globalCounter = globalCounter + 1。所以,现在你的函数已经从这个样子:

function addResponseElement(messageValue, idName){
globalCounter++; 
var totalInputElements = document.testForm.length;
debuggingMessages( addResponseElement","empty", "object is a value");
var container = document.getElementById('formSubmit');
container.innerHTML += "<input type=\"text\" value=\"" +messageValue+ "\"name=\""+idName+"\" /><br>";
if(globalCounter == totalInputElements){
container.innerHTML += "<input type=\"submit\" value=\"Submit\" />";
}
}

变成了这个样子:

function addResponseElement(messageValue, idName) {
globalCounter = globalCounter + 1; 
debuggingMessages( "addResponseElement", "empty", "object is a value");
document.getElementById('formSubmit').innerHTML += "<input type=\"text\" value=\"" + messageValue + "\"name = \"" + idName + "\" /><br>";
if (globalCounter === 7) {
document.getElementById('formSubmit').innerHTML += "<input type=\"submit\" value=\"Submit\" />";
}
}

比较突出显示的行,你会看到代码的变化。现在让我们继续下一个错误。

错误—函数未定义

这个错误是由 JavaScript 引擎和网页在浏览器中渲染的方式引起的。我们在第一章什么是 JavaScript 测试?中简要提到,网页(和 JavaScript)在客户端从上到下解析。这意味着出现在顶部的东西将被首先阅读,然后是底部的东西。

行动时间—修复"函数未定义"的错误

  1. 由于这个错误是由 JavaScript 函数的不正确流程引起的,我们需要改变函数的顺序。我们在第二章即兴测试和调试 JavaScript中做的是先写了我们将要使用的函数。这可能是错误的,因为这些函数可能需要的是只在 JavaScript 代码的后部分定义的数据或函数。这是一个非常简单的例子:

    <script>
    function addTWoNumbers() {
    return numberOne() + numberTwo();
    }
    function numberOne(x, y) {
    return x + y;
    }
    function numberTwo(a, b){
    return a + b;
    }
    </script>
    
    

    基于之前的代码片段,你将意识到addTwoNumbers()需要从numberOne()numberTwo()返回的数据。这里的问题在于,JavaScript 解释器在阅读addTwoNumbers()之前会先阅读numberOne()numberTwo()。然而,numberOne()numberTwo()都是由addTwoNumbers()调用的,导致代码流程不正确。

    这意味着,为了使我们的代码正确运行,我们需要重新排列函数的顺序。继续使用之前的例子,我们应该这样做:

    <script>
    function numberOne(x, y) {
    return x + y;
    }
    function numberTwo(a, b){
    return a + b;
    }function addTWoNumbers() {
    return numberOne() + numberTwo();
    }
    </script>
    
    

    在之前的代码片段中,我们已经重新安排了函数的顺序。

  2. 现在,我们将重新排列函数的顺序。对我们来说,我们只需要将我们的函数安排成这样:原来在代码中的第一个函数现在是最后一个,最后一个函数现在是第一个。同样,原来在 JavaScript 代码中出现的第二个函数现在是倒数第二个。换句话说,我们将反转代码的顺序。

  3. 一旦您改变了函数的顺序,保存文件并在 JSLint 上测试代码。您应该注意到与函数未定义相关的验证错误现在消失了。

现在,让我们继续下一个验证错误。

太多的 var 声明

根据 JSLint,我们使用了太多的var声明。这意味着什么?这意味着我们在每个函数中使用了不止一个var声明;在我们的案例中,我们显然在每个函数中都使用了不止一个var声明。

这是怎么发生的呢?如果你滚动到底部并检查 JSLint 的设置,你会看到一个复选框被选中,上面写着每个函数只允许一个 var 声明。这意味着我们最多只能使用一个var

为什么这被认为是好的风格呢?虽然许多程序员可能认为这是繁琐的,但 JSLint 的作者可能认为一个好的函数应该只做一件事。这通常意味着只操作一个变量。

当然,有很多讨论的空间,但既然我们都在这里学习,让我们动手修复这个验证错误。

行动时间——修复使用太多 var 声明的错误

为了修复这个错误,我们将需要进行某种代码重构。尽管代码重构通常意味着使您的代码更加简洁(即,更短的代码),您可能意识到将代码重构以符合验证标准是一项艰巨的工作。

  1. 在本节中,我们将更改(几乎)所有将值保存到函数中的单个var声明。

    负责这个特定验证错误的代码是在checkForm函数中找到的。我们需要重构的语句如下:

    var totalInputElements = document.testFormResponse.length;
    var nameOfPerson = document.testFormResponse.nameOfPerson.value;
    var birth = document.testFormResponse.birth.value;
    var age = document.testFormResponse.age.value;
    var spending = document.testFormResponse.spending.value;
    var salary = document.testFormResponse.salary.value;
    var retire = document.testFormResponse.retire.value;
    var retirementMoney = document.testFormResponse.retirementMoney.value;
    var confirmedSavingsByRetirement;
    var ageDifference = retire - age;
    var salaryPerYear = salary * 12;
    var spendingPerYear = spending * 12;
    var incomeDifference = salaryPerYear - spendingPerYear;
    
    
  2. 现在我们将开始重构我们的代码。对于每个定义的变量,我们需要定义一个具有以下格式的函数:

    function nameOfVariable(){
    return x + y; // x + y represents some form of calculation
    }
    
    

    我将从一个例子开始。例如,对于totalInputElements,我将这样做:

    function totalInputElements() {
    return document.testFormResponse.length;
    }
    
    
  3. 基于之前的代码,对您即将看到的内容做类似的事情:

    /* here are the function for all the values */
    function totalInputElements() {
    return document.testFormResponse.length;
    }
    function nameOfPerson() {
    return document.testFormResponse.nameOfPerson.value;
    }
    function birth() {
    return document.testFormResponse.birth.value;
    }
    function age() {
    return document.testFormResponse.age.value;
    }
    function spending() {
    return document.testFormResponse.spending.value;
    }
    function salary() {
    return document.testFormResponse.salary.value;
    }
    function retire() {
    return document.testFormResponse.retire.value;
    }
    function retirementMoney() {
    return document.testFormResponse.retirementMoney.value;
    }
    function salaryPerYear() {
    return salary() * 12;
    }
    function spendingPerYear() {
    return spending() * 12;
    }
    function ageDifference() {
    return retire() - age();
    }
    function incomeDifference() {
    return salaryPerYear() - spendingPerYear();
    }
    function confirmedSavingsByRetirement() {
    return incomeDifference() * ageDifference();
    }
    function shortChange() {
    return retirementMoney() - confirmedSavingsByRetirement();
    }
    function yearsNeeded() {
    return shortChange() / 12;
    }
    function excessMoney() {
    return confirmedSavingsByRetirement() - retirementMoney();
    }
    
    

现在,让我们继续下一个错误。

期待</而不是<\

对我们大多数人来说,这个错误可能是最有趣的。我们之所以有这个验证错误,是因为 HTML 解析器与 JavaScript 解释器略有不同。通常,额外的反斜杠被 JavaScript 编译器忽略,但不被 HTML 解析器忽略。

这样的验证错误可能看起来不必要,但 Doug Crockford 知道这对我们的网页有一定的影响。因此,让我们继续解决这个错误的方法。

行动时间—解决期望的'</'而不是'</'错误

尽管这个错误是最引人入胜的,但它是最容易解决的。我们所需要做的就是找到所有包含</的 JavaScript 语句,并将它们更改为<\/。主要负责这个错误的功能是buildFinalResponse()

  1. 滚动到buildFinalResponse()函数,将所有含有</的语句更改为<\/。完成后,你应该有以下代码:

    function buildFinalResponse(name, retiring, yearsNeeded, retire, shortChange) {
    debuggingMessages( buildFinalResponse", -1, "no messages");
    var element = document.getElementById("finalResponse");
    if (retiring === false) {
    element.innerHTML += "<p>Hi <b>" + name + "<\/b>,<\/p>";
    element.innerHTML += "<p>We've processed your information and we have noticed a problem.<\/p>";
    element.innerHTML += "<p>Base on your current spending habits, you will not be able to retire by <b>" + retire + " <\/b> years old.<\/p>";
    element.innerHTML += "<p>You need to make another <b>" + shortChange + "<\/b> dollars before you retire inorder to acheive our goal<\/p>";
    element.innerHTML += "<p>You either have to increase your income or decrease your spending.<\/p>";
    }
    else {
    // able to retire but....
    //alertMessage("retiring === true");
    element.innerHTML += "<p>Hi <b>" + name + "<\/b>,<\/p>";
    element.innerHTML += "<p>We've processed your information and are pleased to announce that you will be able to retire on time.<\/p>";
    element.innerHTML += "<p>Base on your current spending habits, you will be able to retire by <b>" + retire + "<\/b>years old.<\/p>";
    element.innerHTML += "<p>Also, you'll have' <b>" + shortChange + "<\/b> amount of excess cash when you retire.<\/p>";
    element.innerHTML += "<p>Congrats!<\/p>";
    }
    }
    
    

注意所有</都已更改为<\/。你可能还想检查代码,看看是否有这样的错误残留。

现在,解决了这个错误后,我们可以继续解决下一个验证错误。

期望'='但找到''

在 JavaScript 和大多数编程语言中,=====有显著的区别。一般来说,=====更严格。

注意

JavaScript 中=====的主要区别在于,===是一个严格等于运算符,只有当两个操作数相等且类型相同时,它才会返回布尔值true。另一方面,==运算符如果两个操作数相等,即使它们类型不同,也会返回布尔值true

根据 JSList,当比较变量与真理值时应使用===,因为它比==更严格。在代码严格性方面,JSLint 确保代码质量是正确的。因此,现在让我们纠正这个错误。

行动时间—将==更改为===

由于前面提到的原因,我们现在将所有==更改为===,用于需要比较的语句。尽管这个错误相对容易解决,但我们需要了解这个错误的重要性。=====严格得多,因此使用===而不是==更为安全和有效。

回到你的源代码,搜索所有包含==的比较语句并将它们更改为===. ==主要出现在ifelse-if语句中,因为它们用于比较。

完成后,你可能想测试一下你的更新后的代码是否通过了 JSLint,并看看你是否清除了所有这类错误。

现在,让我们继续处理另一个繁琐的错误:"Alert is not defined"。

Alert is not defined

通常,单独使用alert会导致全局命名空间的"污染"。尽管它很方便,但根据 JSLint,这是一种不好的代码实践。因此,我们将要使用的解决这个验证错误的方法是再次进行某种代码重构。

在我们的代码中,你应该注意到我们主要使用alert()来提供关于函数名、错误消息等方面的反馈。我们需要使我们的alert()能够接受各种形式的消息。

行动时间—解决"Alert is not defined"错误

我们将做的是将所有的alert()语句合并到一个函数中。我们可以向该函数传递一个参数,以便根据情况改变警告框中的消息。

  1. 回到你的代码,在<script>标签的顶部定义以下函数:

    function alertMessage(messageObject) {
    alert(messageObject);
    return true;
    }
    
    
    • messageObject是我们将用来保存我们消息的参数。
  2. 现在,将所有的alert()语句更改为alertMessage(),以确保alert()的消息与alertMessage()的消息相同。完成后,保存文件并再次运行 JSLint 代码。

如果你尝试运行你的代码在 JSLint 中,你应该看到alert()造成的“损害”已经减少到只有一次,而不是十到二十次。

在这种情况下,我们可以做的是最小化alert()的影响,因为对于我们来说,没有一个现成的替代方案可以在警告框中显示消息。

现在是避免 HTML 事件处理程序的下一个错误的时候了。

避免使用 HTML 事件处理程序

良好的编码实践通常指出需要将编程逻辑与设计分离。在我们的案例中,我们在 HTML 代码中嵌入了事件处理程序(JavaScript 事件)。根据 JSLint,这种编码可以通过完全避免 HTML 事件处理程序来改进。

注意

尽管理想情况下是将编程逻辑与设计分离,但在使用 HTML 内置事件处理程序方面并没有什么功能上的问题。你可能需要考虑这是否值得(在时间、可维护性和可扩展性方面),坚持(几乎)完美的编码实践。在本节的后部分,你会发现尝试验证(并功能正确)代码可能会很繁琐(甚至令人烦恼)。

为了解决这个验证错误,我们将需要使用事件监听器。然而,由于事件监听器的兼容性问题,我们将使用 JavaScript 库来帮助我们处理事件监听器支持的不一致性。在这个例子中,我们将使用 JQuery。

JQuery 是一个由 John Resig 创建的 JavaScript 库。你可以通过访问这个链接下载 JQuery:jquery.com。正如这个网站所描述的,“JQuery 是一个快速和简洁的 JavaScript 库,它简化了 HTML 文档遍历、事件处理和动画,以及 Ajax 交互,用于快速网页开发。”在我的个人经验中,JQuery 确实通过修复许多棘手问题(如 DOM 不兼容性、提供内置方法创建动画等)使生活变得更容易。我当然建议你通过访问以下链接跟随一个入门教程:docs.jquery.com/Tutorials:Getting_Started_with_jQuery

行动时间—避免 HTML 事件处理程序

在本节中,你将学习如何通过不同的编码方式避免 HTML 事件处理程序。在这种情况下,我们不仅删除了每个 HTML 输入元素中嵌入的 JavaScript 事件,还需要为我们的 JavaScript 应用程序编写新函数,使其以相同的方式工作。此外,我们将使用一个 JavaScript 库,帮助我们删除与事件处理和事件监听器相关的所有复杂内容。

  1. 打开同一文档,滚动到<body>标签。删除在表单中找到的所有 HTML 事件处理程序。在你删除了所有的 HTML 事件处理程序后,你的表单源代码应该看起来像这样:

    <form name="testForm" >
    <input type="text" name="enterText" id="nameOfPerson" size="50" value="Enter your name"/><br>
    <input type="text" name="enterText" id="birth" size="50" value="Enter your place of birth"/><br>
    <input type="text" name="enterNumber" id="age" size="50" maxlength="2" value="Enter your age"/><br>
    <input type="text" name="enterNumber" id="spending" size="50" value="Enter your spending per month"/><br>
    <input type="text" name="enterNumber" id="salary" size="50" value="Enter your salary per month"/><br>
    <input type="text" name="enterNumber" id="retire" size="50" maxlength="3" value="Enter your the age you wish to retire at" /><br>
    <input type="text" name="enterNumber" id="retirementMoney" size="50" value="Enter the amount of money you wish to have for retirement"/><br>
    </form>
    
    
  2. 现在滚动到</style>标签。在</style>标签之后,输入以下代码片段:

    <script src="img/jquery.js">
    </script>
    
    

    在前一行中,你正在使 JQuery 在你的代码中生效。这将允许你在修复代码时使用 JQuery 库。现在该写一些 JQuery 代码了。

  3. 为了维持我们代码的功能,我们将需要使用 JQuery 提供的.blur()方法。滚动到你的 JavaScript 代码的末尾,添加以下代码片段:

    jQuery(document).ready(function () {
    jQuery('#nameOfPerson').blur(function () {
    submitValues(this);
    });
    jQuery('#birth').blur(function () {
    submitValues(this);
    });
    jQuery('#age').blur(function () {
    submitValues(this);
    });
    jQuery('#spending').blur(function () {
    submitValues(this);
    });
    jQuery('#salary').blur(function () {
    submitValues(this);
    });
    jQuery('#retire').blur(function () {
    submitValues(this);
    });
    jQuery('#retirementMoney').blur(function () {
    submitValues(this);
    });
    jQuery('#formSubmit').submit(function () {
    checkForm(this);
    return false;
    });
    });
    
    

    以下是对 JQuery 工作方式的简短解释:jQuery(document).ready(function ()用于启动我们的代码;它允许我们使用 JQuery 提供的方法。为了选择一个元素,我们使用jQuery('#nameOfPerson')。如前所述,我们需要保持代码的功能,所以我们将使用 JQuery 提供的.blur()方法。为此,我们将.blur()添加到jQuery('#nameOfPerson')中。我们需要调用submitValues(),因此我们需要将submitValues()包含在.blur()中。因为submitValues()是一个函数,所以我们将它这样包含:

    jQuery('#nameOfPerson').blur(function () {
    submitValues(this);
    });
    
    
  • 在此时,我们应该已经完成了必要的修正,以实现有效和功能的代码。我在下一节中简要总结一下这些修正。

我们所做修正的总结

现在,我们将通过快速回顾我们所做以修复验证错误的步骤来刷新我们的记忆。

首先,我们将原始代码粘贴到 JSLint 中,并注意到我们有大量的验证错误。幸运的是,这些验证错误可以分组,这样相同的错误可以通过修正一个代码错误来解决。

接下来,我们开始了修正过程。一般来说,我们试图从那些看起来最容易的验证错误开始修复。我们修复的第一个验证错误是缺少use strict声明的错误。我们所做的是在我们的 JavaScript 代码的第一行输入use strict,这样就修复了错误。

我们修复的第二个验证错误是“函数未定义错误”。这是由于 JavaScript 函数的不正确流程造成的。因此,我们将函数的流程从这:

function submitValues(elementObj){
/* some code omitted */
}
function addResponseElement(messageValue, idName){
/* some code omitted */
function checkForm(formObj){
/* some code omitted */
}
function buildFinalResponse(name,retiring,yearsNeeded,retire, shortChange){
/* some code omitted */
}
function debuggingMessages(functionName, objectCalled, message){
/* some code omitted */
}

到这个程度:

function debuggingMessages(functionName, objectCalled, message) {
/* some code omitted */
}
function checkForm(formObj) {
/* some code omitted */
function addResponseElement(messageValue, idName) {
/* some code omitted */
}
function submitValues(elementObj) {
/* some code omitted */
}

请注意,我们只是简单地反转了函数的顺序来修复错误。

然后我们转向了一个非常耗时的错误——在函数内使用太多的var声明。总的来说,我们的策略是将几乎所有的var声明重构为独立的函数。这些独立函数的主要目的是返回一个值,仅此而已。

接下来,我们又转向了另一个耗时的验证错误,那就是"expected<\/ instead of</。一般来说,这个错误是指闭合的 HTML 标签。所以我们所做的就是将所有的闭合 HTML 标签中的/>更改为\/>。例如,我们将以下代码更改为:

function buildFinalResponse(name,retiring,yearsNeeded,retire, shortChange){
debuggingMessages( buildFinalResponse", -1,"no messages");
var element = document.getElementById("finalResponse");
if(retiring == false){
//alert("if retiring == false");
element.innerHTML += "<p>Hi <b>" + name + "</b>,<p>";
element.innerHTML += "<p>We've processed your information and we have noticed a problem.</p>";
element.innerHTML += "<p>Base on your current spending habits, you will not be able to retire by <b>" + retire + " </b> years old.</p>";
element.innerHTML += "<p>You need to make another <b>" + shortChange + "</b> dollars before you retire inorder to acheive our goal</p>";
element.innerHTML += "<p>You either have to increase your income or decrease your spending.</p>";
}
else{
// able to retire but....
alert("retiring == true");
element.innerHTML += "<p>Hi <b>" + name + "</b>,</p>";
element.innerHTML += "<p>We've processed your information and are pleased to announce that you will be able to retire on time.</p>";
element.innerHTML += "<p>Base on your current spending habits, you will be able to retire by <b>" + retire + "</b>years old.
</p>";
element.innerHTML += "<p>Also, you'll have' <b>" + shortChange + "</b> amount of excess cash when you retire.</p>";
element.innerHTML += "<p>Congrats!<p>";
}
}

到这个:

function buildFinalResponse(name, retiring, yearsNeeded, retire, shortChange) {
debuggingMessages( buildFinalResponse", -1, "no messages");
var element = document.getElementById("finalResponse");
if (retiring === false) {
element.innerHTML += "<p>Hi <b>" + name + "<\/b>,<\/p>";
element.innerHTML += "<p>We've processed your information and we have noticed a problem.<\/p>";
element.innerHTML += "<p>Base on your current spending habits, you will not be able to retire by <b>" + retire + " <\/b> years old.<\/p>";
element.innerHTML += "<p>You need to make another <b>" + shortChange + "<\/b> dollars before you retire inorder to achieve our goal<\/p>";
element.innerHTML += "<p>You either have to increase your income or decrease your spending.<\/p>"; 
}
else {
// able to retire but....
//alertMessage("retiring === true");
element.innerHTML += "<p>Hi <b>" + name + "<\/b>,<\/p>";
element.innerHTML += "<p>We've processed your information and are pleased to announce that you will be able to retire on time.<\/p>";
element.innerHTML += "<p>Base on your current spending habits, you will be able to retire by <b>" + retire + "<\/b>years old.<\/p>";
element.innerHTML += "<p>Also, you'll have' <b>" + shortChange + "<\/b> amount of excess cash when you retire.<\/p>";
element.innerHTML += "<p>Congrats!<\/p>"; 
}
}

请注意,高亮的行是我们将/>更改为\/>的地方。

在修复上一个错误后,我们转向了一个概念上更难以理解,但容易解决的错误。那就是,"expected === instead of saw =="。根据 JSLint,使用===比使用==更严格,更安全。因此,我们需要将所有的==更改为===

下一个错误,"Alert is not defined",在概念上与"Too many var statement"错误相似。我们需要做的是将所有的alert()声明重构为接受参数messageObjectalertMessage()函数。这使我们能够在几乎整个 JavaScript 程序中只使用一个alert()。每当我们需要使用一个警告框时,我们只需要向alertMessage()函数传递一个参数。

最后,我们转向修复一个最棘手的验证错误:"避免使用 HTML 事件处理程序"。由于事件监听器的复杂性,我们得到了流行的 JavaScript 库 JQuery 的帮助,并编写了一些 JQuery 代码。首先,我们从我们的 HTML 表单中移除了所有的 HTML 事件处理程序。我们的 HTML 表单从这样变成了:

<form name="testForm" >
<input type="text" name="enterText" id="nameOfPerson" onblur="submitValues(this)" size="50" value="Enter your name"/><br>
<input type="text" name="enterNumber" id="age" onblur="submitValues(this)" size="50" maxlength="2" value="Enter your age"/><br>
<input type="text" name="enterText" id="birth" onblur="submitValues(this)" size="50" value="Enter your place of birth"/><br>
<input type="text" name="enterNumber" id="spending" onblur="submitValues(this)" size="50" value="Enter your spending per month"/><br>
<input type="text" name="enterNumber" id="salary" onblur="submitValues(this)" size="50" value="Enter your salary per month"/><br>
<input type="text" name="enterNumber" id="retire" onblur="submitValues(this)" size="50" maxlength="3" value="Enter your the age you wish to retire at" /><br>
<input type="text" name="enterNumber" id="retirementMoney" onblur="submitValues(this)" size="50" value="Enter the amount of money you wish to have for retirement"/><br>
</form>

到这个:

<form name="testForm" >
<input type="text" name="enterText" id="nameOfPerson" size="50" value="Enter your name"/><br>
<input type="text" name="enterText" id="birth" size="50" value="Enter your place of birth"/><br>
<input type="text" name="enterNumber" id="age" size="50" maxlength="2" value="Enter your age"/><br>
<input type="text" name="enterNumber" id="spending" size="50" value="Enter your spending per month"/><br>
<input type="text" name="enterNumber" id="salary" size="50" value="Enter your salary per month"/><br>
<input type="text" name="enterNumber" id="retire" size="50" maxlength="3" value="Enter your the age you wish to retire at" /><br>
<input type="text" name="enterNumber" id="retirementMoney" size="50" value="Enter the amount of money you wish to have for retirement"/><br>
</form>

为了支持新的 HTML 表单,我们链接了 JQuery 库,并添加了一些代码来监听 HTML 表单事件,像这样:

<script type="text/javascript"> src="img/jquery.js"></script>
<script type="text/javascript">
/* some code omitted */
jQuery(document).ready(function () {
jQuery('#nameOfPerson').blur(function () {
submitValues(this);
});
jQuery('#birth').blur(function () {
submitValues(this);
});
jQuery('#age').blur(function () {
submitValues(this);
});
jQuery('#spending').blur(function () {
submitValues(this);
});
jQuery('#salary').blur(function () {
submitValues(this);
});
jQuery('#retire').blur(function () {
submitValues(this);
});
jQuery('#retirementMoney').blur(function () {
submitValues(this);
});
jQuery('#formSubmit').submit(function () {
checkForm(this);
return false;
});
});
</script>

完成的代码可以在source code文件夹下的Chapter 3中找到,文件名为perfect-code-for-JSLint.html。你可以将这个与你编辑的代码进行比较,看看你是否理解了我们试图做什么。现在,你可能想将代码复制粘贴到 JSLint 中看看效果如何。你将只会看到与 Jquery 使用相关的错误,一个关于使用alert()的验证错误,以及另一个关于使用太多var声明的错误。

刚才发生了什么?

我们已经纠正了大部分的验证错误,从极其大量的验证错误减少到少于十个验证错误,其中只有两个或三个与我们的代码相关。

注意

你可能会注意到jQuery not defined错误。尽管 JSLint 捕获了外部链接的 JQuery 库,但它并不显式阅读代码,因此导致了jQuery not defined错误。

现在我们已经修复了验证错误,让我们接下来使用另一个免费的验证工具——JavaScript Lint。

JavaScript Lint—一个你可以下载的工具。

JavaScript Lint 可以在www.javascriptlint.com下载,其工作方式与 JSLint 类似。主要区别在于,JavaScript Lint 是一个可下载的工具,而 JSLint 作为一个基于网页的工具运行。

与 JSLint 一样,JavaScript Lint 能够找出以下常见错误:

  • 行尾缺少分号:在每行末尾都要加上分号。

  • 没有if, forwhile的括号。

  • 不执行任何操作的语句。

  • 在 switch 语句中的 case 语句将小数点转换为数字。

您可以通过访问其主页www.javascriptlint.com了解更多关于它的功能。

要了解如何使用 JavaScript Lint,您可以跟随网站上找到的教程。

我们不会讨论如何修复由 JavaScript Lint 发现的验证错误,因为这些原则与 JSLint 相似。然而,我们挑战你修复剩余的错误(除了由 JQuery 引起的错误)。

挑战自己——修复 JSLint 发现的剩余错误。

好的,这是我将向您提出的第一个挑战。修复 JSLint 发现的剩余错误,具体如下:

  • "alert is not defined": 此错误在alertMessage()函数中找到。

  • 太多的 var 声明: 此错误在submitValues()函数中找到。

以下是一些供您开始的想法:

  • 在我们的 JavaScript 应用程序中,有没有办法避免使用alert()?我们如何显示能吸引观众注意力的信息,同时又是有效的?

  • 对于在submitValues()函数中发现的错误,我们如何重构代码,使得函数中只有一个var声明?我们可以将var声明重构为一个函数,并让它返回一个布尔值吗?

好的,现在你可能想试试,但要注意,你所提出的或打算使用的解决方案可能会导致其他验证错误。所以你可能会在实施之前考虑一下你的解决方案。

总结

我们终于完成了这一章的结尾。我首先开始总结我们用来编写有效代码的一些策略和小贴士,然后概述了章节的其余部分。

我们用来编写有效代码(根据 JSLint)的某些策略如下:

  • 适当间距你的代码,特别是在数学符号后,if, else, ( )等地方

  • 每个函数中只使用一个var声明

  • 考虑你的程序流程;编写代码时,确保所需数据或函数位于程序顶部

  • 谨慎使用alert()函数。相反,将你的alert()函数合并成一个函数

  • 使用===而不是==;这确保了你的比较语句更加准确

  • 避免使用 HTML 事件处理程序,而是使用监听器。另外,你可以借助 JavaScript 库(如 JQuery)提供事件监听器给你的代码。

最后,我们讨论了以下主题:

  • 测试与验证之间的区别

  • 验证如何帮助我们写出好代码

  • 如果我们不验证代码,可能会出现什么问题——如果我们不验证代码,它可能不具备可扩展性,可读性较差,并可能导致意外错误

  • 如何使用 JSLint 和 JavaScript Lint 验证我们的代码

既然我们已经学习了如何通过验证工具测试 JavaScript,你可能想考虑一下当我们打算测试代码时可以采用的策略。正如本章中的示例所示,编写有效代码(或修正无效代码)是一个非常繁琐的过程。更重要的是,有些验证警告或错误并不影响我们程序的整个运行。在这种情况下,你认为验证代码值得花费精力吗?还是认为我们应该追求完美,写出完美的代码?这很大程度上取决于我们的测试计划,这将决定测试的范围、要测试的内容以及其他许多内容。这些主题将在下一章第四章,计划测试中介绍。所以,我将在本章结束,下章再见。

第四章:测试计划

欢迎来到第四章。在我们进入更正式的测试过程之前,我们首先必须了解测试是关于什么的。在本章中,我们将学习如何为你的 JavaScript 程序制定测试计划。我们将学习你应该知道的各种测试概念,之后我会向你提供一个简短的指南,它将作为下一章的基础。

在我们进入各种测试概念之前,我们首先需要建立对以下问题的简要理解:

  • 我们真的需要一个测试计划来进行测试吗?

  • 我们应该什么时候为我们的代码开发测试计划?

  • 我们的程序需要多少测试?

在覆盖上述问题之后,我们将学习以下测试概念和想法:

  • 黑盒测试、白盒测试及相关概念

  • 边界条件

  • 单元测试

  • 网页功能测试

  • 集成测试

  • 非功能性测试,如性能测试

  • 可用性测试

  • 测试顺序——我们首先进行上述哪些测试?

  • 回归测试——通常在我们更改代码时进行

为了更好地了解测试在何时何地发挥作用,我们首先从软件生命周期的非常简要介绍开始。

软件生命周期的非常简要介绍

了解软件生命周期将帮助你更深入地了解软件开发过程,更重要的是,了解测试将在何时何地进行。

一般来说,软件生命周期有以下阶段:

  1. 分析

  2. 设计

  3. 实施

  4. 测试

  5. 部署

  6. 维护

在第一阶段,我们通常进行分析以了解干系人的需求。例如,如果你为客户进行定制项目,你需要理解用户需求、系统需求和业务目标。一旦你理解了需求,你需要设计软件。这个阶段需要做的事情包括绘制数据流程图、设计数据库等。下一阶段是实施阶段。我们可以将此视为实际的编码过程。

接下来是测试,这是本书的主要关注点。在本章中,我们将学习如何根据各种测试概念来规划我们的测试。在测试阶段之后,我们将部署项目,最后我们维护项目。因为这是一个循环,理论上我们在维护阶段期间或之后会回到分析阶段。这是因为软件或程序是进化的;随着需求和需求的变化,我们的软件也在变化。

尽管术语和阶段可能与你在其他相关内容中看到的有稍许不同,但过程通常是一样的。这里的主要收获是,测试通常在实施之后进行。

敏捷方法

你可能听说过敏捷方法论,它包括敏捷软件开发方法论,当然还有敏捷测试方法。

一般来说,敏捷软件开发和测试方法通常是以最终用户或客户为目标进行的。通常文档很少,专注于短暂的软件开发周期,通常为一周到四周。

那么这和你们在上一部分读到的软件开发周期有什么关系呢?总的来说,测试不是一个独立的阶段,而是与开发过程紧密集成,从客户的角度进行代码测试,尽可能早,当代码足够稳定以进行测试时。

敏捷方法和软件周期在行动

可能你们很难想象之前的理论是如何应用的。为这本书创建示例代码的过程 closely mimics 软件生命周期和敏捷方法论。所以我打算非常简要地分享一下我根据我们学到的理论为这本书创建代码示例时的经历。

分析和设计

从技术角度来说,分析和设计阶段发生在我思考什么样的代码示例能够满足书籍目标的时候。我认为代码应该足够简单,便于理解,最重要的是能够展示 JavaScript 的各种特性。代码应该为后续章节的代码测试搭建好舞台。

实施和测试

实施阶段发生在我编写代码示例的时候。当我为代码片段创建函数时,我尽可能地进行测试,并问自己代码是否能够展示 JavaScript 的使用并便于后续的测试目的。

所以,这里发生的事情是我在尽可能多地进行测试时使用了一种敏捷测试方法。

部署

在商业世界中,代码的部署通常发生在代码传输给最终用户之后。然而,在我的情况下,部署涉及到将我的代码示例发送给编辑。

维护

维护阶段发生在我提交代码后,编辑发现并修复了 bug 的时候。尽管有意愿,但代码并不总是无懈可击的。

你需要一个测试计划才能进行测试吗?

你很可能会需要一个测试计划来执行测试。这是因为计划帮助你保持清晰的测试目标。它还帮助你确定你想对你的程序进行什么样的测试。

最重要的是,正如您将意识到的,为了进行彻底的测试,您将需要实施各种测试,包括基于白盒测试和黑盒测试的概念测试,网页测试,单元测试,集成测试等。测试计划还作为测试数据,错误,测试结果以及您程序可能的解决方案的记录。这意味着,为了确保不遗漏任何内容,最好有一个明确的计划,了解要测试什么,何时测试以及如何测试您的程序。

何时制定测试计划

理论上,如果您查看软件开发周期,您会发现测试是在实施之后进行的。测试计划在您完成程序的实际编码过程(实施)之后应该进行。这是因为只有在这个时候,您才确认了您有哪些功能,方法和模块;基于您已经完成的内容来规划要测试的内容是有商业意义的,因为您知道要关注什么。

然而,在实践中,建议在实施过程之前开始规划。根据您的具体情况,当然有可能您可以制定一个高级测试计划(HLTP)或高级测试用例(HLTC)。如果您正在开发一个大型复杂的系统,需要 HLTP 来解决整体需求。其他支持性测试计划用于解决系统的详细内容。高级测试用例(HLTC)与高级测试计划(HLTP) somewhat 相似,不同之处在于它覆盖了与系统整体需求直接相关的主要功能测试用例。

您应该注意的另一个要点是,在实践中,测试计划可以广泛地分为系统测试和用户验收测试。系统测试涵盖所有形式的的功能测试和非功能测试(您稍后了解),而用户验收测试是一个阶段,在这个阶段,测试是由最终用户在移交所有权之前进行的。

需要进行多少测试?

你可能急于确定需要测试什么以及不需要测试什么。关于需要进行多少测试有很多不同的观点,但我个人认为,您程序中列出的以下部分内容应该定义您的测试计划范围。

代码意图做什么?

首先,您需要了解代码的意图。例如,我们之前章节中代码的业务需求是为了根据用户的输入计算用户是否能够按时退休,这些输入包括他的当前年龄,他希望退休的年龄,他当前的支出,当前的薪水等等。因此,我们创建了满足业务需求的代码。一旦我们知道我们的代码意图做什么,我们就可以测试代码是否满足我们的业务需求。

测试代码是否满足我们的需求

通过测试代码以查看它是否满足我们的业务需求,我们的意思是对于每个输入,我们需要得到正确的输出。回到我们在第二章,JavaScript 中的即兴测试和调试和第三章,语法验证中提到的例子,如果剩余可支配收入总额小于用于退休的资金量,输出将应该是“无法退休”,至少在字面上是这样的。从测试角度来看,我们需要确保当提到的情况为真时,输出将是“无法退休”。

这可以通过一种称为白盒测试的概念来实现,其中测试是基于测试者知道代码内容的假设进行的。我将在接下来的章节中详细介绍白盒测试和其他测试概念。为了给你一个提示,你将遇到的一些测试概念包括单元测试,你以小单元测试代码,以及边界值测试,你测试代码的最大或最小可接受值。

接下来,我们需要考虑的是如何测试或检测用户无效行为。

测试用户无效行为

在开发网页时,我们最常听到的一句话是“永远不要信任用户”。这是因为可能存在恶意的用户,他们试图通过输入无效数据来“破坏”你的应用程序。以前面章节中的例子为例,姓名输入框只能接受字符和空格,而年龄和工资输入框只能接受数字,不能接受字符。然而,如果有人试图将字符输入年龄或工资字段,这将是一种无效行为。

我们的程序必须足够健壮,能够测试或检查无效行为;错误的输入会导致错误的输出。

上述问题的简要总结

通过了解你的代码旨在做什么以及它应该做什么,并理解检测用户无效行为的需求,你已经定义了测试计划的范围。你的测试应该围绕这些标准进行。

现在我们可以转向你将在测试的不同方面使用的各种测试概念,以及测试计划的构建块——主要测试概念和策略。

主要测试概念和策略

在本节中,我们将介绍不同类型的测试概念和策略。我不会试图详细解释每个概念,而是需要你掌握其大意,并了解这些概念的来源。熟悉这些概念后,我们将着手制定实际的测试计划。作为一个开始,我将从开发者遵循的业务策略讲起(无论你为外部或内部客户执行项目),这样你可以对测试的进行有一个高层次的了解。总之,无论你信仰哪种测试概念、方法论或理念,你都将面临以下测试用例:

  • 功能性需求测试

  • 非功能性需求测试

  • 验收测试

功能性需求测试

功能性需求测试旨在测试软件系统中的代码、功能或模块。例如,回到我们为前几章编写的代码,功能性需求包括以下内容:

  1. 检查用户输入的有效性。

  2. 如果步骤 1 的输入有效,一个新的输入框将在当前输入框的右侧出现,当用户将鼠标移至下一个输入框时。

  3. 根据用户输入提供正确的计算输出。例如,如果用户退休时需要 1,000,000 美元,而他到退休时只有 500,000 美元,那么他将无法退休。

本章涵盖的功能性需求测试示例如下:

  • 网页测试

  • 边界测试

  • 等价类划分

非功能性需求测试

非功能性需求测试指的是与软件的功能或特定行为无关的需求。相反,这是一个指定了可以用来评判软件运行情况的标准的需求。

例如,功能性需求可能是我们的软件应该能够存储用户输入的值,而非功能性需求是数据库应该实时更新。

另一个与前几章示例代码相关的例子是,功能性需求可能是软件能够计算用户是否能够按时退休,而非功能性需求可能是我们的用户界面应该直观。你现在看到非功能性需求与功能性需求之间的区别了吗?

本章涵盖的非功能性需求测试示例如下:

  • 性能测试

  • 可用性测试

  • 集成测试

你作为一名软件开发者在职业生涯中可能会遇到的其它非功能性需求如下:

  • 页面快速加载

  • 搜索引擎优化网页

  • 创建的软件文档

  • 系统的效率

  • 软件的可靠性

  • 你生产的软件代码的互操作性。例如,你可以在主要浏览器上编写 JavaScript

验收测试

验收测试通常是整个测试过程的最后一个阶段。这通常在软件最终被客户接受之前进行。验收测试可以进一步分为两部分。首先由软件供应商进行验收测试,然后由最终用户(称为用户验收测试)进行验收测试。

验收测试是客户(或最终用户)将在您创建的软件上进行实际测试(类似于实际使用系统)的时间。一个典型的过程将包括最终用户创建反映软件商业使用的测试用例。

如果你使用敏捷测试方法,这些测试用例通常被称为故事。这取决于客户在商业环境中如何使用它们。在用户验收测试之后,你将把产品的所有权移交给客户。

在最常见的测试场景涵盖之后,我们将进入测试概念的具体内容。我们将从最常听到的测试概念之一,即黑盒测试概念开始。

黑盒测试

黑盒测试属于“箱子方法”,其中一款软件被视为一个箱子,箱子包含各种功能、方法、类等。比喻来说,“黑盒”通常意味着我们无法看到箱子里面有什么。这意味着我们不知道程序的内部结构而实施测试;我们从程序的外部视角出发,使用有效和无效的输入以确定输出是否正确。

因为我们不知道程序的内部结构和代码,所以我们只能从用户的角度来测试程序。在这种情况下,我们可能试图确定主要功能是什么,然后尝试根据这些功能实施我们的测试。

黑盒测试的主要优点是,测试结果往往是独立的,因为测试人员不知道代码。然而,缺点是,因为测试人员不知道代码是关于什么的,测试人员可能会创建或执行重复的测试,或者测试未能测试软件最重要的方面。更糟糕的是,测试人员可能会漏掉整个功能或方法。

这就是为什么在现实世界中,测试用例会在开发周期的早期阶段准备好的原因,这样我们就不会遗漏某些需求。优点是测试人员将能够访问所需的测试用例,但同时,测试人员无需具备完整的代码知识。

黑盒测试的一些例子包括可用性测试、边界测试和公测。

可用性测试

简单来说,可用性测试通常涉及从用户的角度进行测试,以查看我们创建的程序是否易于使用。这里的关键目标是观察用户使用我们的程序,以发现错误或需要改进的地方。可用性测试通常包括以下方面:

  • 性能:特别是在用户完成特定任务所需的点击(或操作)次数方面,例如注册为会员,或从网站上购买产品等。

  • 召回率:用户在一段时间没有使用程序后,还能记得如何使用程序吗?

  • 准确性:我们的程序设计是否导致了最终用户的错误?

  • 反馈:反馈无疑是 AJAX 相关应用最重要的一个问题之一。例如,在提交 AJAX 表单后,用户通常会等待某种形式的反馈(以视觉反馈的形式,如成功消息)。但是想象一下,如果没有视觉反馈或成功消息,用户怎么知道他是否成功或失败地提交了表单呢?

边界测试

边界测试是一种测试方法,其中测试最大和最小值。边界测试有时包括测试错误值和典型值。

例如,在前几章的程序中,我们允许输入名字的最大字符数是 20 个字符。

等价划分

等价划分测试是一种将数据范围划分为分区,从而导出测试用例的技术。例如,对于接受用户年龄的输入框,它应该表现出以下分区:

等价划分

请注意,在我们的示例中,只接受正数值来输入用户的年龄,因为一个人的年龄技术上应该是正数。因此,任何负值都是不可接受的值。

对于小于-231且大于231-1的范围内,由于硬件和 EMCA 操作符的要求,整数只能持有-231231-1之间的值。

公测

公测已经被当前流行的 Web 2.0 公司,如谷歌,普及,在这些公司中,网络应用程序通常会发布给除了核心编程团队之外的其他有限受众。公测在 alpha 测试之后进行,此时大多数的错误和故障已经被检测并修复。公测通常被用作获取潜在用户反馈的一种方式。

这样的过程在开源项目中很常见,比如 Ubuntu(一个基于 Linux 的开源操作系统)、jQuery(一个 JavaScript 库)和 Django(一个基于 Python 的网页框架)。这些开源项目或软件通常有一系列的内测和公测版本。它们在发布软件或项目的主要版本之前,通常也会有发布候选版本。

白盒测试

白盒测试也被称为透明盒测试、玻璃盒测试或透明测试。白盒测试可以被视为黑盒测试的对立面;我们在了解程序的内部结构的情况下测试程序。我们从程序的内部视角来看待问题,并在实施我们的测试计划时使用这种视角。

白盒测试通常发生在测试可以访问程序的内部代码和数据结构的情况下。因为我们从程序的内部视角来看待问题,并且了解我们的源代码,所以我们的测试计划是基于我们的代码来设计的。

我们可能会发现自己追踪代码的执行路径,并找出程序中各种函数或方法的各种输入和输出值。

白盒测试的一些例子包括分支测试和 Pareto 测试。

分支测试

分支测试是一个概念,它要求代码中的每个分支至少测试一次。这意味着编写的一切功能或代码都应该被测试。在软件测试中,有一个度量标准称为代码覆盖率,它指的是程序的源代码中有多少已经被测试过。分支测试覆盖的一些更重要的类型包括以下内容:

  • 功能覆盖:确保代码中的每个功能都已经被调用(测试)

  • 决策覆盖:每个if else语句都已经被测试过。可能存在这样的情况,代码的if部分可以工作,但else部分却不能,反之亦然。

Pareto 测试

Pareto 测试我个人称之为“现实世界”的测试,并在严格的时间和金钱约束下进行。这是因为 Pareto 测试只关注最常用的功能;最经常使用的功能是最重要的,因此我们应该把时间和精力集中在测试这些功能上。另外,我们可以将 Pareto 测试看作是大多数错误来自于我们程序中少数几个功能的情况;因此,通过发现这些功能,我们可以更有效地测试我们的程序。

注意

Pareto 测试源自一个被称为“帕累托原则”的想法,也许更广为人知的是“80-20 原则”。帕累托原则指出,大约 80% 的效果来自于 20% 的原因。例如,80% 的销售收入可能来自于 20% 的销售团队或客户。或者另一个例子是,世界上 80% 的财富是由世界上 20% 的人口控制的。应用在我们的案例中,我们可以认为 80% 的错误或程序错误来自于 20% 的代码,因此我们应该专注于这部分代码的测试。或者,我们可以说程序的 80% 的使用活动来自于 20% 的代码。同样,我们可以专注于这部分代码的测试。顺便说一下,Pareto 测试可以被视为一个一般的测试原则,而不仅仅是白盒测试的一种形式。

单元测试

单元测试将代码分成逻辑块进行测试,通常一次关注一个方法。单元可以被视为代码的最小可能块,例如一个函数或方法。这意味着在理想情况下,每个单元应该与其他所有单元独立。

当我们执行单元测试时,我们尝试在完成每个函数或方法时进行测试,以确保我们拥有的代码在继续下一个函数或方法之前能够正常工作。

这有助于减少错误,您可能已经注意到,在开发前几章中的 JavaScript 程序时,我们以某种方式应用了单元测试的概念。每当创建一个函数时,我们尽可能地进行测试。

单元测试的一些好处包括最小化错误,以及便于变更,因为每个函数或方法都是单独在隔离环境中测试的,并且在很大程度上简化了集成。

我认为主要好处是单元测试灵活,便于文档记录。这是因为当我们编写和测试新函数时,我们可以轻松地记录下问题所在,以及代码是否能正确工作。实际上,我们是在进行逐步记录——在测试的同时记录结果。

单元测试也是集成测试的一个组成部分,尤其是在自下而上的方法中,因为我们从最小的可能单元开始测试,然后逐步测试更大的单元。例如,在我为第二章创建代码时,JavaScript 中的即兴测试与调试,我实际上进行了非正式的单元测试。我将每个函数视为独立的单元,并使用相关的 HTML 输入字段测试每个 JavaScript 函数,以确保得到正确的输出。这种技术可以看作是在编写新代码时执行持续集成的一部分。

持续集成是一个过程,在这个过程中,开发者频繁地集成他们的代码,以防止集成错误。这通常需要自动构建代码(包括测试)来检测集成测试。当我们创建新代码时,确保与现有代码集成非常重要,以防止出现兼容性问题或新旧错误。持续集成越来越受欢迎,因为它集成了单元测试、版本控制和构建系统。

网页测试

如前所述,网页测试是一种功能测试,通常指的是从用户角度测试用户界面。对于我们在这里的目的,我们将测试我们的 JavaScript 程序与 HTML 和 CSS 结合使用。

网页测试还包括不同浏览器和平台上的正确性测试。我们至少应该关注像 Internet Explorer 和 Firefox 这样的主要网络浏览器,并检查在不同浏览器下的表现和 JavaScript 程序是否正常工作。

要了解浏览器使用情况,你可能想去 www.w3schools.com/browsers/browsers_stats.asp 查看哪些浏览器受欢迎、正在下降或正在崛起。

注意

看起来 Google Chrome 正在迅速增加势头,它有很大的机会成为一个受欢迎的网络浏览器;根据 w3schools 提供的统计数据,不到两年的时间,Google Chrome 的市场份额从 3.15% 增加到 14.5%。这种受欢迎程度的部分原因在于其 JavaScript 引擎性能。

网页测试的另一个主要焦点也包括检查最常用的用户行为,比如非法与合法值、登录、登出、用户错误行为、SQL、HTML 注入、HTML 链接检查、图像、机器人攻击的可能性等等。

由于 SQL、HTML 注入和机器人攻击超出了本书的范围,我们将关注其他问题,比如确保网页在不同浏览器下能够工作、检查非法与合法值、错误行为以及频繁行为等。

性能测试

性能测试包括负载测试、压力测试、耐力测试、隔离测试、尖峰测试等多种类型。我不会试图让你陷入细节之中。相反,我将重点关注 JavaScript 程序员可能会面临的两个更常见的问题。

首先,性能可以指客户端下载一段 JavaScript 所需的时间。你可能会认为下载时间取决于互联网连接。但有一件简单的事情你可以做,那就是压缩你的 JavaScript 代码,而不需要重构或重写它。一个很好的例子就是我们在 第三章,语法验证 中介绍的 JQuery 库。如果你访问 JQuery 的官方网站 jquery.com,你可能注意到了 JQuery 分为两种形式——生产版本和开发版本。生产版本是最小化的,文件大小为 24KB,而开发版本则是 155KB。显然,生产版本的文件大小更小,因此在下载 JavaScript 方面提高了性能。

注意

压缩你的代码——或者最小化你的代码——指的是你删除代码中所有不必要的空白和行,以减小文件大小。一些代码最小化工具会自动删除注释、替换函数、变量,甚至采用不同的编码方式。

其次,性能也可以指执行特定代码的速度,对于给定的输入量而言。通常,我们需要使用外部库或工具来帮助我们找出我们代码中相对较慢的部分,或者瓶颈所在。相关工具以及我们如何可以应用性能测试,将在第六章,测试更复杂的代码中介绍。

集成测试

集成测试是验收测试之前的测试过程中的最后一步。因为我们已经确保程序的基本构建模块作为单独的单位正确工作,我们现在需要确保它们能否一起工作。

集成测试指的是我们程序的所有不同组件的测试。不同的组件可以指的是我们迄今为止谈论的各种单元。集成测试的主要目标是确保功能、性能和可靠性要求得到满足。我们也一起测试不同的单元,看看它们是否能工作;我们需要检查在组合单元时是否有任何异常。

集成测试可以采取不同的形式,例如自顶向下和自底向上的方法。

在自顶向下的方法中,我们首先从最高级别的集成模块开始,然后是每个模块的子模块或函数。另一方面,自底向上的测试从最低级别的组件开始,然后逐步过渡到更高级别的组件。

基于我们迄今为止看到的示例代码,很难理解集成测试是如何工作的。通常,如果我们把 HTML 代码视为一个单元、CSS 作为一个单元、每个单独的 JavaScript 函数作为一个单元,我们可以看到集成测试将包括测试所有三个在一起并确保它是正确的。

在自底向上的方法中,我们从代码的基本单元开始测试。随着我们测试代码的基本单元,我们逐步测试更大单元的代码。这个过程与单元测试类似。

回归测试——在做出更改后重复先前的测试

回归测试专注于在程序被修改或升级时揭示程序中的错误。在现实情况下,我们倾向于对一个程序进行更改——无论是升级它、添加新功能等等。关键是,当我们对程序进行更改时,我们需要测试新组件,看看它们是否能与旧组件一起工作。

我们需要执行回归测试,因为研究和经验表明,在程序被修改时,新的或旧的错误可能会出现。例如,当添加新功能时,可能会重新引入之前修复过的旧错误,或者新功能本身可能包含影响现有功能的错误。这就是回归测试发挥作用的地方:我们执行之前的测试,以确保旧的组件仍然运行良好,并且没有旧的错误再次出现。我们用旧的组件测试新功能,以确保整个系统正常工作。有时,为了节省时间和资源,我们可能只对与旧组件结合的新功能进行测试。在这种情况下,我们可以应用影响分析来确定应用程序通过添加或修改代码的影响区域。

回归测试是最真实的部分。这是因为随着程序的增长,你更改代码的可能性很大。在你更改代码的过程中,可能会引入程序中的错误或不兼容。回归测试帮助你发现这些错误。

测试顺序

我们已经介绍了所需的基础知识,所以是时候了解我们应该从哪种测试开始。我们进行测试的顺序取决于我们是要实施自下而上的测试还是自上而下的测试。测试的顺序没有问题,但我个人更喜欢自下而上的测试:我通常首先从单元测试开始,然后是其他类型的测试(取决于程序的性质),最后是集成测试。

采取这种方法的主要原因是因为单元测试允许我们更早地发现代码中的错误;这防止了错误或缺陷的累积。此外,它提供了在记录测试结果方面更大的灵活性。

然而,如果你喜欢自上而下的方法,你总是可以先以最终用户的角度来测试程序。

在现实世界中,特别是在测试网络应用程序方面,很难区分(至少在概念上)自下而上的测试和自上而下的测试。这是因为尽管用户界面和编程逻辑是分开的,但我们确实需要同时测试两者,以了解它是否按照我们的预期工作。

然而,测试顺序应该以用户验收测试结束,因为最终用户是我们代码的使用者。

在下一节中,我们将向您展示如何编写测试计划。你会注意到,我们将从用户的角度进行测试。现在,是编写我们的测试计划的时候了。

编写你的测试计划

既然我们已经覆盖了必要的测试概念,是时候学习如何创建测试计划了。同时,我们将记录我们的测试计划;这将作为本章下一部分的依据,我们将应用测试。

测试计划

我们的测试计划将包含我们之前覆盖的一些概念,例如网页测试、边界测试、集成测试等。因为我们正在对第二章中使用的代码进行测试(Ad Hoc Testing and Debugging in Javascript),我们知道代码是关于什么的。因此,我们可以设计我们的测试过程,使其能够包含来自黑盒测试和白盒测试的思想。

你可能想前往源代码文件夹并打开sample_test_plan.doc文件,这是我们的示例测试计划。这是一个非常简单和非正式的测试计划,只包含所需组件的最少量。如果你为自己的文档编写参考资料,使用简单的文档可以节省时间和精力。然而,如果你为客户准备测试计划,你需要一个更详细的文档。为了简单起见,我们将使用source code文件夹中提供的示例文档来帮助你快速理解计划过程。我将简要概述我们的测试计划的组件,同时,我将向您介绍我们测试计划的主要组件。

版本控制

在第一个组件中,你会注意到有一个版本表,记录了测试计划的变化。在现实世界中,计划会变化,因此,跟踪已经变化的事情是一个好习惯。

注意

另一种使版本控制更容易维护的方法是使用版本控制软件,如 Git 或 BitBucket。这样的版本工具记录了你代码中的变化;这将使你能够追踪你所做的变化,这使得创建测试计划变得容易得多。你可以访问git-scm.com/了解更多信息关于 Git,以及bitbucket.org/了解关于 BitBucket 的信息。

测试策略

下一个重要的组成部分是你应该注意的测试策略。测试策略表示我们将用于测试计划的主要思想和想法。你会看到我们同时采用了白盒测试和黑盒测试,以及单元测试和集成测试。由于我们的 JavaScript 程序是基于网络的,我们隐式地执行了一种网页测试,尽管这在章节的后续部分没有提到。对于测试的每个阶段,我们将决定所需的测试值。另外,如果你查看sample_test_plan.doc,你会发现我添加了,以预期值的简短描述形式,每个测试部分的结果或响应。

通过白盒测试测试预期和可接受值

我们将要做的第一件事是使用单元测试进行白盒测试。由于我们对代码和用户界面(HTML 和 CSS 代码)有深入的了解,我们将把测试应用到用户界面级别。这意味着我们将通过输入我们已经决定的各个测试值来测试程序。

在此例中,我们将像在第二章JavaScript 中的即兴测试和调试和第三章语法验证中一样使用程序,看看程序是否如我们所预期的那样工作。我们将在此处使用预期和可接受的数据值。

输入将是程序要求我们输入的内容—对于需要我们输入姓名、出生地等内容的输入字段,我们将向其输入字符。需要数字作为输入的输入字段,如年龄、我们希望退休的年龄、薪水、开支等,我们将输入数字。

输入的详细信息如下(输入值仅为演示目的):

输入字段 输入值(案例 1) 输入值(案例 2)
---- ---- ----
姓名 Johnny Boy Billy Boy
出生地 旧金山 旧金山
年龄 25 25
每月开支 1000 1000
每月薪水 100000 2000
你希望退休的年龄 55 55
到退休年龄我想要的钱 1000000 1000000

对于每个输入值,我们期望相应的输入字段在屏幕中间、响应标题下动态创建,同时,原始输入字段将被禁用。这被称为测试的预期输出、结果或响应。这对于第一个表单的其余输入字段也是如此。动态创建的字段的示例如下所示:

通过白盒测试测试预期和可接受值

请注意,在屏幕中间,在响应标题下,有两个输入字段。这些输入字段是动态创建的。

通过黑盒测试测试预期和不可接受值

我们将要做的第二件事是使用边界值测试进行黑盒测试。这个测试分为两部分:首先,我们将测试程序的边界值,以查看输出是否正确。输入与白盒测试中使用的输入类似,不同之处在于我们将每个输入使用异常大的数字或异常大的字符长度。我们还将单个数字和单个字符作为输入的一部分。每个输入的输出应与白盒测试中看到的输出相似。

更具体地说,我们将使用以下测试值(请注意,这些测试值仅用于演示目的;当你创建你的程序时,你必须决定应该使用哪些合适的边界值):

输入字段 最小值 常见值 最大值 注释
姓名 一个字符,例如'a' Eugene 一个非常长的字符串,不超过 255 个字符。 值的范围(X):一个字符 1 <= X <= 255 个字符
出生地 一个字符,例如 a 纽约市 一个非常长的字符串,不超过 255 个字符。 值的范围(X):一个字符 1 <= X <= 255 个字符
年龄 1 25 不会超过 200 岁 值的范围(X):1 <= X <= 200
每月支出 1 2000 1000000000 值的范围(X):1 <= X <= 1000000000
每月工资 2 5000 1000000000 注意到我们假设用户挣得比花得多。值的范围(X):1 <= X <= 1000000000
你希望在退休时的年龄 这个年龄应该大于现在的年龄 这个年龄应该大于现在的年龄 这个年龄应该大于现在的年龄 值的范围(X):1 <= X <= 200
到退休年龄时我想要的钱 我们将使用 1 这里 一个合适的数字,比如 1000000 不会超过一万亿美元 值的范围(X):1 <= X <= 1000000000

如果你参考了sample test文档,你就会发现我为每个输入字段提供了一个测试值的样本范围。

注意

记住我们之前提到等价划分了吗?在实际操作中,给定一个边界值,我们会测试与给定测试值相关的三个值。例如,如果我们想测试一个边界值'50',那么我们会测试 49、50 和 51。然而为了简化,我们只会测试预期的值。这是因为下一章我们将对给定的值进行实际测试;这可能会变得重复和单调。我只是想让你知道真正的世界实践是什么。

这部分测试的第二部分是我们将测试预期的非法值。在第一种情景中,我们将使用既被接受又被拒绝的值。输入将类似于我们在白盒测试阶段所使用的值,不同的是,我们将数字作为需要数字的字段的输入,反之亦然。每次我们输入一个不被接受的值时,预期的输出是会出现一个警告框,告诉我们输入了错误的值。

具体细节,请查看以下表格:

输入字段 输入值 输入值案例 1 输入值案例 2 输入值案例 3
姓名 数字或空值 1 ~!@#$%^&*()" 测试
出生地 数字或空值 1 ~!@#$%^&*()" 测试
年龄 字符和空值 a ~!@#$%^&*()" -1
每月支出 字符和空值 a ~!@#$%^&*()" -1
每月工资 字符和空值 a ~!@#$%^&*()" -1
你希望在多少岁时退休 字符和空值 a ~!@#$%^&*()" -1
我希望在退休年龄拥有多少钱 字符和空值 a ~!@#$%^&*()" -1

通常情况下,对于每个预期的非法值,我们应期待我们的程序会通过一个警告框来提醒我们,告诉我们我们输入了错误类型的值。

在第二个测试场景中,我们将尝试输入非字母数字值,例如感叹号、星号等。

在第三个测试场景中,我们将测试输入字段中数字的负值。第三个测试场景的输入值如下:我们使用-1 来节省一些打字时间;所以像-100000 这样的负值没有区别。

测试程序逻辑

在这一部分的测试计划中,我们将尝试测试程序逻辑。确保程序逻辑的一部分是确保输入是我们需要和想要的。然而,程序逻辑的某些方面不能仅仅通过验证输入值来保证。

例如,我们对用户有一个隐含的假设,即我们认为用户将输入一个比他当前年龄大的退休年龄。虽然这个假设在逻辑上是合理的,但用户可能根据传统假设输入值,也可能不输入。因此,我们需要通过确保退休年龄大于当前年龄来确保程序逻辑是正确的。

这个测试的输入值如下:

输入字段 第一份表单的输入值
姓名 Johnny Boy
出生地 旧金山
年龄 30
每月支出 1000
每月工资 2000
你希望在多少岁时退休 25
我希望在退休年龄拥有多少钱 1000000

这里需要注意的是,“你希望在多少岁时退休”的值比“年龄”的值要小。

我们应该期待我们的程序能够发现这个逻辑错误;如果它没有发现,我们需要修复我们的程序。

集成测试和测试意外值

最后一个阶段是集成测试,在这个阶段中,我们测试整个程序,看它是否能协同工作,这包括第一个表单、由第一个表单派生出的第二个表单,等等。

在第一个测试场景中,我们开始时缓慢而稳定,通过测试预期和可接受的值。第一个测试场景的输入值如下(输入值仅用于演示目的):

输入字段 输入值(情况 1) 输入值(情况 2) 输入值(情况 3) 输入值(情况 4)
姓名 Johnny Boy Johnny Boy Johnny Boy Johnny boy
出生地 旧金山 旧金山 旧金山 旧金山
年龄 25 25 25 25
每月支出 1000 1000 1000 1000
每月工资 100000 2000 2000 100000
希望退休的年龄 55 55 28 28
到退休年龄我想要的钱的数量 2000000 2000000 1000000 100000

请注意下划线的输入值。这些输入值是为了确定我们是否能根据输入得到正确的响应。例如,在输入所有值并提交动态生成的第二表单后,案例 1 和案例 3 的输入值将导致输出表明用户无法按时退休,而案例 2 和 4 的输入值将导致输出表明用户可以按时退休。

这是一个截图,展示了如果用户能够按时退休,输出会是什么样子:

集成测试和测试意外值

下一个截图展示了如果用户不能按时退休的输出:

集成测试和测试意外值

请注意两个不同案例文本的区别。

为了查看测试案例结果的详细信息,请打开sample_test_plan.doc文件,该文件可在本章的source code文件夹中找到。

现在是第二个测试场景的时间。在第二个测试场景中,我们首先填写第一个表单的值。在我们提交由动态创建的第二表单之前,我们将尝试更改值。输入值将包括我们用于白盒测试和黑盒测试的值。第一个测试场景的输入值如下:

输入字段 第一表单的输入值 第二表单的输入值(随机值)
姓名 Johnny Boy 25
出生地 旧金山 100
年龄 25 Johnny Boy
每月支出 1000 一些字符
每月工资 100000 更多字符
希望退休的年龄 20 更多信息
到退休年龄我想要的钱的数量 1000000 1000000

这个测试阶段的目的是测试第二表单的健壮性,这一点我们到目前为止还没有验证。如果第二表单失败,我们将需要更改我们的代码以增强我们程序的健壮性。

现在我们将进入我们测试计划的下一个部分——发现的错误或 bug。

错误表单

最后一个部分帮助我们记录我们找到的 bug。这个区域允许我们记录错误是什么,它们是由什么引起的,以及这些错误发生的功能或特性。通常,无论我们发现什么错误,我们都需要记录导致错误的确切功能,并评论可能的解决方案。

测试计划的总结

上面介绍的组件是测试计划中最重要的组件之一。通常对于测试的每个阶段,我们都明确指出了我们的测试数据和预期输出。请注意,我们使用这份文档作为一种非正式的方式提醒我们需要做哪些测试,所需的输入、预期的输出,更重要的是我们发现的缺陷。这份样本文档没有提到的是对于发现那些缺陷需要执行的操作,这将在下一章节中介绍。

总结

我们有效地完成了测试计划的规划过程。尽管我们的测试计划是非正式的,我们看到了如何应用各种测试概念,结合不同的测试数据值来测试我们之前章节中创建的程序。

具体来说,我们涵盖了以下主题:

  • 我们首先从对软件工程关键方面的简要介绍开始。我们了解到测试发生在实现(编码)阶段之后。

  • 我们已经学会了如何通过询问我们的代码应该做什么来定义测试范围,确保它做它应该做的事情,最后通过测试用户无效行为来测试。

  • 接下来我们介绍了各种测试概念,如白盒测试、黑盒测试、单元测试、网页测试、性能测试、集成测试和回归测试。

  • 我们还学会了我们需要从不同的方面测试我们的程序,从而增强程序的健壮性。

  • 尽管这一章节中介绍的测试概念在某些方面可能有所不同,我们可以将它们归类为:测试预期但可接受的值、预期但不可接受的值和一般意义上的意外值。我们还学会了根据对编写代码的理解来测试逻辑错误。

  • 最后我们规划并记录了我们的测试计划,其中包括测试过程描述、测试值、预期输出等重要的组成部分,如版本控制和缺陷表单。

尽管测试方法可以根据组织类型和应用程序类型有显著不同,但这里列出的一些方法通常更适合轻量级的网页应用。然而,这些概念也是构建大规模、复杂的网页应用的基础。

这一章节标志着测试计划工作的结束。现在请准备好,我们将继续进入下一章节,在那里我们将执行测试计划。

第五章:将测试计划付诸行动

欢迎来到第五章。这一章相当直接;我们基本上把在第四章,计划测试中讨论的计划付诸行动。

我们将如何实施我们的测试计划的步骤如下:首先开始测试预期和可接受的值,接着测试预期但不可接受的值。接下来,我们将测试程序的逻辑。最后,我们将执行集成测试和测试意外值或行为。

除了执行上述测试之外,我们还将在本章涵盖以下内容:

  • 回归测试实战—你将学习如何通过修复 bug 然后再次测试你的程序来执行回归测试

  • 客户端测试与服务器端测试的区别

  • 如何使用 Ajax 对测试产生影响

  • 当测试返回错误结果时该怎么办

  • 如果访客关闭了 JavaScript 会发生什么

  • 如何通过压缩你的 JavaScript 代码来提高性能

那么让我们动手开始测试吧。

应用测试计划:按顺序运行你的测试

在本节中,我们将简单地将测试计划应用于我们的程序。为了简单起见,我们将记录任何在上一节中提供的示例测试计划中的缺陷或错误。除此之外,在每次测试结束时,我们将在sample_text_plan.doc中记录一个通过或失败的文本,我们是在上一章中创建的。然而,请注意,在现实世界中(尤其是如果你为你的客户做一个定制的项目),记录结果非常重要,即使你的测试是正确的。这是因为,很多时候,产生正确的测试结果是向客户交付代码的一部分。

顺便提醒一下—我们即将使用的测试计划是在上一章创建的。你可以在第四章的source code文件夹中找到测试计划,文件名为sample_test_plan.doc。如果你急于想看一个完整的测试计划,其中已经执行了所有测试,请前往第五章的source code文件夹,并打开sample-testplan-bug-form-filled.doc

如果你不想翻页或打开电脑只是为了查看测试列表,测试列表如下:

  • 测试用例 1

    • 测试用例 1a:白盒测试

    • 测试用例 1b:黑盒测试

      • 测试用例 1bi:边界值测试

      • 测试用例 1bii:测试非法值

  • 测试用例 2:测试程序的逻辑

  • 测试用例 3:集成测试

    • 测试用例 3a:使用预期值测试整个程序

    • 测试用例 3b:测试第二个表单的健壮性。

带着这个想法,让我们开始第一次测试。

测试用例 1:测试预期和可接受的值

测试预期和可接受值指的是白盒测试阶段。我们现在将按照计划执行测试(这是第一个测试场景)。

行动时间—测试案例 1a:通过白盒测试测试预期和可接受值

在此部分,我们将通过使用在规划阶段预先确定的值来开始我们的测试。您在本章节中使用的源代码是perfect-code-for-jslint.html,该代码可在第三章的source code文件夹中找到。我们在此将输入预期的和可接受的数据值。我们将从使用输入值案例 1 的输入值开始测试,正如我们的示例测试文档所规划的那样。

  1. 用您最喜欢的网络浏览器打开源代码。

  2. 当您在网络浏览器中打开您的程序时,焦点应该在第一个输入字段上。按照我们的计划输入名字Johnny Boy。在您在第一个输入字段中输入Johnny Boy后,继续下一个字段。

    当您将焦点转移到下一个字段时,您会在原始输入字段的右侧看到一个新的输入字段出现。如果出现这种情况,那么您为第一个输入收到了正确和预期的输出。如果您不理解这意味着什么,请随时参考第四章,测试计划,并查看给出的预期输出的屏幕截图。

  3. 对于第二个输入,我们需要输入出生地。按照计划输入旧金山。点击(或使用标签)转到下一个字段。

    与第一个输入字段类似,在您移动到下一个字段后,您会看到一个包含您输入值的新输入字段。这意味着您此刻已经有了正确的输出。

  4. 这个步骤与上述步骤类似,不同之处在于输入值现在是一个数字。输入您的年龄25。然后继续到下一个字段。您还应该在右侧看到一个新的输入字段。

  5. 现在,请对左侧表单的剩余字段重复上述步骤。重复此操作,直到您在屏幕中间看到一个提交按钮。

    如果为您的每个输入动态创建了一个新的输入字段,并且每个动态创建的新输入字段都包含您输入的确切内容,那么您就得到了正确的输出。如果不是这样,测试失败。然而,根据我们的测试,我们得到了正确的输出。

  6. 现在,在您的浏览器中刷新页面,并为在输入值案例 2 中找到的输入值重复测试。您也应该收到正确的输出。

    假设两个测试案例都产生了正确的输出,那么恭喜你,在这个测试阶段没有发现任何错误或 bug。这个部分的测试并没有什么特别或繁琐的地方,因为我们已经知道,基于我们的输入,我们会收到预期的输出。现在,我们将进行更加令人兴奋的测试——测试预期但不可接受的数据。

测试用例 1b:使用黑盒测试测试预期但不可接受的值

在这个部分,你将继续执行我们的测试计划。随着测试的进行,你会发现我们的程序还不够健壮,存在一些固有错误。你会了解到,你需要记录下这些信息;这些信息将在我们调试程序时使用(这是第二个测试场景)。

行动时间——测试用例 1bi:使用边界值测试测试预期但不可接受的值

在这个测试部分,我们将继续使用前一部分相同的源代码。我们将开始进行边界值测试。因此,我们将首先使用“最小值”,然后是“最大值”进行测试。我们将跳过常见值测试用例,因为那与之前的测试相似。

  1. 再次刷新你的网页浏览器。

  2. 首先,为姓名输入字段输入一个字符a。输入值后,用鼠标点击下一个输入字段。你应该看到在第一个输入字段的右侧动态创建了一个输入字段,与之前的测试一样。

    这个测试的输出与你在前一个测试中看到和经历的情况类似。我们试图测试的是程序是否接受一个最小值。在这个测试阶段,我们天真地选择接受一个字符作为可接受的输入。因为这是可接受的,我们应该看到一个动态生成的值出现在原始输入字段的右侧。如果你看到了这个,那么你得到了正确的输出。

  3. 同样,为出生地输入字段输入一个字符a。输入值后,用鼠标点击下一个输入字段。你会看到在第一个输入字段的右侧动态创建了一个输入字段,正如之前的测试所看到的那样。

    你应该对这个输入值也收到正确的输出。现在让我们继续下一个输入值。

  4. 现在,我们将按计划为输入字段年龄输入数字 1。同样,输入值后,将焦点移动到下一个输入字段。

  5. 我们将按照计划重复进行测试。

    在此阶段的测试中,我们通常不应该收到任何错误。与之前进行的第一次测试类似,我们应该对每个输入都看到熟悉的输出。然而,我想指出这个测试阶段的一个重要点:

    我们天真地选择了一个可能不切实际的最小值。考虑一下接受单个字符值的各个输入字段。在很大程度上,我们的原始程序逻辑似乎并不适合实际世界的情况。通常,我们应该期望对于接受字符值的输入字段至少有两个或三个字符。因此,我们将此视为程序中的一个错误,并在我们的“错误报告表”上记录这一点。您可以打开sample-testplan-bug-form-filled.doc文件,看看我们如何记录这个缺陷。

    既然我们已经通过了最小值测试用例,现在是时候转到下一个测试用例——最大值。

  6. 像往常一样,刷新您的网页浏览器以清除之前输入的所有值。我们现在将开始输入超过 255 个字符的极长字符串。

    正如早前所解释的,我们还应该收到类似的输出——一个动态生成的输入字段,其中包含我们的输入值。

  7. 同样,使用长字符串或大数值为剩余的输入字段输入值。你不应该遇到任何错误。

    虽然我们没有明显的错误,但您可能已经注意到我们遇到了之前经历过的类似问题。我们的程序也没有最大值边界值。看起来如果你尝试输入大于最大值的价值,程序仍然会接受它们,只要这些值不是非法的。同样,如果你尝试输入超过 200 个字符的字符串,程序仍然会接受它,因为它是一个合法值。这意味着我们的程序没有限制用户可以输入的最大字符数。这可以被视为一个错误。我们将在“错误报告表”中记录这个编程错误。您可能想去看看我们是如何记录这个错误的。既然我们已经完成了预期和不可接受值的第一阶段测试,现在是时候进行这个测试的第二阶段——测试预期非法值。

行动时间——测试用例 1bii:使用非法值测试期望但不可接受的价值

此阶段的测试有三种输入情况。在测试的第一个情况中,我们将为需要字符输入的输入字段输入数值,反之亦然。

输入用例 1:

  1. 我们将再次刷新浏览器以清除旧的值。接下来,我们开始输入预期的非法值。对于“name”输入字段,我们将输入一个数字。这可以是任何数字,比如“1”。继续测试。在您输入数字后,尝试将鼠标光标移动到下一个输入字段。

    当你试图将焦点移到下一个输入字段时,你应该看到一个警告框,告诉你输入了错误的类型值。如果按照我们的测试计划看到警告框,那么此刻就没有错误。

  2. 为了测试下一个字段,我们需要在继续之前输入第一个字段的正确值。另一种方法是刷新浏览器并直接跳到第二个字段。假设您使用第一种方法,让我们输入一个假设的名字,史蒂夫·乔布斯,然后继续输入下一个字段。同样,我们尝试为出生地输入一个数字。在您为输入字段输入数字后,尝试移动到下一个字段。

    再次,您将看到一个警告框,告诉您您输入了无效的输入,需要输入文本输入。到目前为止还不错;没有错误或错误,我们可以继续到下一个字段。

  3. 我们需要刷新浏览器并直接跳到第三个字段,或者我们需要在继续到第三个字段之前为姓名出生地字段输入有效值。无论使用哪种方法,我们将尝试为年龄字段输入字符串。完成此操作后,尝试移动到下一个输入字段。

    您将再次收到警告框,告诉您您输入了错误的类型。这是按照计划,也是预期的。因此,还没有错误或错误。

  4. 对剩下的字段重复上述步骤,在输入预期但非法值时尝试移动到下一个字段。

    对于所有剩余的字段,您应该收到警告框,告诉您您输入了错误的类型,这是我们期望和计划的内容。

输入案例 2:

既然我们已经完成了第一个测试场景,是时候进行第二个测试场景了,在那里我们尝试输入非字母数字值。

  1. 测试过程与第一个测试相当相似。我们首先刷新浏览器,然后立即为第一个输入字段输入非字母数字值——姓名输入字段。按照我们的计划,我们将输入~!@#$%^&*()作为输入,然后尝试移动到下一个输入字段。

    对于第一个输入字段,需要字符输入,您应该看到一个警告框,告诉您只能输入文本。如果您看到这个,那么我们的程序按计划工作。现在让我们进行下一步。

  2. 对于下一个输入字段,我们将重复上一步,我们期望得到相同的输出。

  3. 现在,对于第三个输入字段,我们继续输入相同的非字母数字输入值。预计这一步的区别只是警告,它告诉我们我们输入了错误的输入,会告诉我们需要输入数字而不是文本。

  4. 我们对剩余的字段重复上述步骤,通常我们应该期望看到一个警告框,告诉我们需要输入文本或数字,这取决于哪个输入字段。如果是这样,那么一切顺利;这个测试场景中没有相关的错误或错误。

输入案例 3:

现在是我们执行第三个测试场景的时候,我们在需要数字输入的输入字段中输入负值。

  1. 再次,我们将刷新浏览器以清除旧值。我们将按计划输入前两个输入字段。我们将输入Johnny BoySan Francisco作为姓名出生地的输入字段。

  2. 完成上一个步骤后,输入剩余字段的-1。当你为这些字段输入-1时,你应该看到我们的程序没有检测到负值。相反,它给出了一个错误的响应,告诉我们应该输入数字。

    实际上,我们的程序应该足够健壮,能够识别负值。然而,如前述测试所示,我们的程序对非法值似乎有错误的响应。我们的程序确实发现了错误,但它返回了一个错误的响应。给出的响应是一个警告框,告诉您输入必须是数字。这在技术上是错误的,因为我们的输入是一个数字,尽管是一个负数。

    这意味着我们的程序确实发现了负值,但它返回了一个错误的响应。这意味着我们这里有一个严重的错误。我们需要在我们的样本文档中注意这个错误,通过在“错误报告表单”上记录这个错误来 document this error。你可以看看我在sample test plan文档中是如何记录这个的。

    哇!这个小节有点长和无聊。没错,测试可以是无聊的,到现在你应该看到我们在这个部分测试的问题都会包含在一个好的程序设计中。你会注意到,至少对我们在这里的目的来说,检查输入值以确保输入是我们需要的,对我们程序的成功是基本的;如果输入值错误,测试剩下的程序就没有意义,因为我们几乎可以确定会因为错误的输入而得到错误的输出。

测试用例 2:测试程序逻辑

在本小节中,我们将尝试在程序逻辑方面测试程序的健壮性。虽然我们已经通过确保输入正确 somewhat 测试了程序逻辑,但根据我们的测试计划,还有一方面我们需要测试,那就是现在的年龄和退休年龄。

行动时间——测试程序逻辑

通常,我们将尝试输入一个比当前年龄小的退休年龄。现在让我们测试程序的健壮性:

  1. 让我们刷新浏览器,然后按照我们的计划输入值。首先输入Johnny Boy,然后输入San Francisco作为姓名出生地的输入字段。

  2. 现在,请注意这个步骤:我们将现在输入30作为年龄,并继续其他字段。

  3. 当你到达希望退休的年龄输入字段时,你将想输入一个小于年龄字段的值。根据我们的测试计划,我们将输入25。之后,我们将尝试移动到下一个字段。

    因为我们成功移动到了下一个字段,这意味着我们的程序还不够健壮。我们的程序不应该接受小于当前年龄值的退休年龄值。因此,即使我们的程序确实产生了最终结果,我们也可以确定输出不是我们想要的,因为逻辑已经是错误的。

    因此,我们需要注意在这个测试阶段发现的逻辑错误。我们再次在错误报告表上记录这个错误。现在我们将进行我们测试的最后阶段。

测试案例 3:集成测试和测试意外值

我们已经完成了测试的最后阶段。在这一小节中,我们将先使用预期和可接受值测试整个程序,然后通过更改第二个表单的值来中断表单提交流程。

行动时间——测试案例 3a:使用预期值测试整个程序

第一组测试共有四组数据。通常情况下,我们会输入所有的值,然后提交表单,看看我们是否能得到预期的响应:输入案例 1 和输入案例 3 的输入值将导致输出显示用户无法按时退休,而输入案例 2 和输入案例 4 的输入值将导致输出显示用户将能够按时退休。有了这个想法,让我们从第一组输入值开始:

  1. 回到你的网页浏览器,刷新你的程序,或者如果你关闭了程序,重新打开源代码。我们按照计划输入值:Johnny BoySan Francisco 作为姓名出生地

  2. 接下来,我们将输入25作为年龄,然后输入1000作为每月支出。我们将重复这些步骤,直到我们在第二个表单上看到动态生成的提交按钮。

  3. 一旦你看到提交按钮,点击按钮提交值。你应该在最终响应框中看到一些文本被生成。如果你看到输出包含姓名、退休年龄、退休所需金额的正确输出值,更重要的是,响应你将在 55 岁时退休,如下面的屏幕截图所示,那么程序中没有错误!行动时间—测试案例 3a:使用预期值测试整个程序

  4. 现在让我们继续输入案例 2 的值。同样,我们将刷新浏览器,然后开始输入所有计划中的值。

  5. 当你看到动态创建的提交按钮时,点击该按钮以提交表单。在这个测试案例中,你会看到用户将无法按时退休,如下面的屏幕截图所示:行动时间—测试用例 3a:使用预期值测试整个程序

    如果你收到前一张截图中的输出,那么到目前为止没有错误。所以让我们继续第三种情况的输入值。

  6. 再次刷新你的浏览器,然后按照计划开始输入值。需要注意的值包括每月工资你想退休的年龄。通常,我们已经设定了这些值,以测试我们是否能够创建输出,以便能够按时退休或不按时退休。

  7. 继续输入值,直到你看到动态生成的提交按钮。点击提交按钮以提交表单。你会看到如下截图所示的输出:行动时间—测试用例 3a:使用预期值测试整个程序

    如果你收到了之前的输出,那么到目前为止没有错误或故障。

  8. 现在,让我们进入最后一个案例——案例 4。我们基本上会重复之前的步骤。我只需要你注意每月工资的输入值。注意输入值是100000,退休年龄没有改变。我们试图模拟用户能够按时退休的情况。

  9. 继续输入值,直到你看到动态生成的提交按钮。点击提交按钮以提交表单。你会看到如下截图所示的输出:行动时间—测试用例 3a:使用预期值测试整个程序

  • 再次,如果你收到了前一张截图中的输出,那么你已经收到了正确的输出。有了这一点,我们已经完成了这个测试阶段的 第一部分。

    通常,我们已经测试了整个程序,以查看我们是否得到了预期的输出。我们使用了不同的值生成了两种可能的输出:能够按时退休或无法按时退休。我们不仅已经收到了正确的输出,我们还测试了在计算结果方面的函数的健壮性。

    考虑到之前的因素,是时候进入测试的第二阶段了——测试第二个表单的健壮性。

行动时间—测试用例 3b:测试第二个表单的健壮性

如果你从第一章就开始跟随我,你可能会注意到我们只是禁用了左侧表单的输入字段,而没有禁用右侧的输入字段。除了故意这样做以向您展示 JavaScript 编程的不同方面外,我们还设置它可以向我们展示集成测试的其他方面。所以现在,我们将尝试更改动态生成的表单的值看看会发生什么。

  1. 首先刷新浏览器,然后按照计划开始输入输入值。在您输入完所有值之后,根据测试计划更改第二表单中的值。

  2. 现在,提交表单,您将看到如下截图的输出:行动时间—测试用例 3b:测试第二个表单的健壮性

  • 哎呀!显然,我们的程序有一个致命的缺陷。我们的第二个表单没有检查机制,或者说是没有。第二个表单在我们的用户可能想要更改值的情况下存在。从一开始,我们天真地选择相信用户会在第二个表单上输入合法和可接受的值,如果他们选择更改输入。现在我们知道这可能不是事实,我们在“缺陷报告表”上记录这一点。

刚才发生了什么?

通常,我们已经执行了整个测试计划。在这个过程中,我们发现了一些后来要修复的错误。您可能会觉得步骤重复;这是真的,测试有时会重复。但是幸运的是,我们的程序相当小,因此测试它是可以管理的。

现在我们已经完成了测试,是时候考虑我们如何处理那些错误了。我们将在下一部分开始讨论这个问题。

当测试返回意外结果时应该做什么

通常,当测试返回意外或错误的结果时,意味着我们的程序中有一个错误或缺陷。根据我们的测试,您肯定已经注意到我们的程序中有薄弱环节。导致测试返回意外结果的薄弱环节或错误如下:

  • 我们的程序不支持负值

  • 我们编写的代码不支持边界值(最大和最小值)。

  • 第二个表单不检查输入值的正确性;如果我们更改第二个表单中的任何值,程序就会失败。

这些观点意味着我们的代码不够健壮,我们需要修复它;我们将在下一节立即进行。

回归测试在行动

在本节中,我们将通过执行回归测试来亲自动手。我们将尝试通过编写修复我们最初应用测试计划时找到的错误的代码来模拟需要回归测试的情况。编写代码后,我们首先测试编写的代码,然后测试整个应用程序以查看它是否协同工作。

行动时间——修复错误并进行回归测试

我们将逐一修复我们发现的每个错误。我们将从编写一个允许我们的程序支持边界值的函数开始。修复所有错误的完整源代码在source code文件夹的第五章中,文件名为perfect-code-for-JSLInt-enhanced.html

在我们开始第一个错误的实际编码过程之前,让我们考虑一下我们可以做什么来支持边界值。

首先,如果我们回到我们的示例测试计划,你会注意到在我们的“Bug 报告表单”中,我们已经记录了我们可以尝试更改检查表单输入的函数,使其可以检查最小和最大值。为了简单起见,我们将通过检查输入的长度来启用边界值。例如,“Neo”意味着有三个输入字符,“1000”将有四个数字输入字符。

其次,因为第一个表单的输入检查是在submitValues()中完成的,我们将尝试添加这个函数所需的检查机制。有了这个想法,我们可以开始编码过程:

  1. 打开我们在第三章,语法验证中编写的原始源代码,在你的最喜欢的源代码编辑器中,寻找submitValues()函数。接下来,在debuggingMessages()函数之后添加以下代码:

    // this is the solution for checking the length of the input
    // this will allow us to enable boundary values
    // starting with minimum values: we will accept character
    // length of more than or equal than 3
    // and less than 100 characters
    if (elementObj.name === 'enterText') {
    if (elementObj.value.length <= 3) {
    alertMessage("Input must be more than 3 characters!");
    var element = document.getElementById(elementObj.id);
    jQuery(element).focus(); 
    return true;
    }
    if (elementObj.value.length >= 100) {
    alertMessage("Input must be less than 100 characters!");
    var element = document.getElementById(elementObj.id);
    jQuery(element).focus();
    return true;;
    }
    }
    // now for checking the maximum value of digits
    // upper boundary is set at 10 digits
    if (elementObj.name === 'enterNumber') {
    if (elementObj.value.length >= 10) {
    alertMessage("Input must be less than 10 digits!");
    var element = document.getElementById(elementObj.id);
    jQuery(element).focus();
    return true;
    }
    }
    
    

    在之前的代码中发生的事情是我们添加了一些if语句。这些语句通过.name属性检查输入的类型,然后检查它是否大于最小输入或小于最大输出。我们设置了一个最小输入长度为三个字符和一个最大输入长度小于 100 个字符的文本输入。对于需要数字输入的输入,我们设置了最大输入长度为 10 位数字。由于用户可能没有收入,我们没有设置最小输入长度。

  2. 保存你的文件并测试程序。尝试输入少于三个字符或超过 100 个字符。你应该会收到一个警告框,显示你的输入过大或过小。同样,测试需要数字输入的输入字段,并查看程序是否检测到超过 10 位数字的输入长度。如果你为每个不同情况收到了正确的警告框,那么你已经修复了错误。

    现在我们已经修复了与边界值有关的问题,是时候继续处理我们在“Bug 报告表单”上记录的下一个错误了,这是我们在sample-testplan-bug-form-filled.doc中发现的第三个错误(错误编号 3),它与负值有关。

    错误在于,我们的程序将负数输入视为非数字值,并产生错误的输出信息,提示输入必须是数字。因此,在这种情况下,我们需要通过追溯到问题的源头——负责检查输入的函数来修复这个错误。

    请注意,检查输入的函数是submitValues()。现在,让我们进入实际的编程过程:

  3. 回到你的源代码,从submitValues()函数开始。我们需要一个检查负数输入的机制,并返回正确的输出,提示输入必须是正数。所以我们可以这样做:

    // this is the solution for checking negative values
    // this only applies to input fields that requires numeric inputs
    if (elementObj.name === 'enterNumber') {
    if (elementObj.value < 0) {
    alertMessage("("Input must be positive!");
    var element = document.getElementById(elementObj.id);
    jQuery(element).focus(); 
    return true;
    }
    }
    
    

    通过添加上述代码,你将能够检查负值。上述代码应放置在submitValues()函数中,在检查输入长度的if语句之前。

  4. 保存你的程序并测试它。在遇到需要数字输入的表单字段时,尝试输入一个负值,比如-1。如果你收到一个警告框,提示输入必须是正数,那么我们就做对了。

    submitValues()的代码应该包括以下行:

    function submitValues(elementObj) {
    // code above omitted
    // this is the solution for checking negative values
    // this only applies to input fields that requires numeric inputs
    if (elementObj.name === 'enterNumber') {
    if (elementObj.value < 0) {
    alertMessage("Input must be positive!");
    var element = document.getElementById(elementObj.id);
    jQuery(element).focus();
    return false;
    }
    }
    // code below is omitted
    }
    
    

    上述片段中的行是我们在这个小节中添加的。因为我们已经确保了我们的频率相同,所以我们可以继续讨论第四个错误(我们sample_test_plan.doc中的第 4 个 bug)。这个错误与程序逻辑有关。

    本章开始时,我们发现程序没有检测到退休年龄可能小于用户当前年龄的情况。这对我们的程序可能是致命的。因此,我们需要添加一个确保退休年龄大于用户当前年龄的机制。

    因为问题在于输入的检查,我们需要关注submitValues()

  5. 让我们回到源代码,在submitValues()中添加以下代码:

    // this is to make sure that the retirement age is larger than present age
    if (elementObj.id === 'retire') {
    if (elementObj.value < document.getElementById('age').value) {
    alertMessage('Retirement age must be higher than age');
    var element = document.getElementById(elementObj.id);
    jQuery(element).focus();
    return false;
    }
    }
    
    

    你应该在这段代码之前输入上述代码。

    现在,测试你的代码。尝试输入一个小于当前年龄的退休年龄。你应该会收到一个警告消息,提示退休年龄必须大于年龄。

    如果你收到了这个警告,那么恭喜你,你做对了!再次总结这个部分,以确保我们意见一致,submitValues()应该包括以下所示的代码行:

    function submitValues(elementObj) {
    // code above omitted
    // this is to make sure that the retirement age is larger than present age
    if (elementObj.id === 'retire') {
    if (elementObj.value < document.getElementById('age').value){
    alertMessage('retirement age must be larger than age');
    var element = document.getElementById(elementObj.id);
    jQuery(element).focus();
    return true;
    }
    }
    // code below omitted
    }
    
    

    现在让我们继续讨论通过检查第二个表单发现的最后一个错误(我们sample-testplan-bug-form-filled.doc中的第 5 个 bug)。

    我们已经创建了一个 JavaScript 程序,这样当我们为每个输入字段输入值时,会动态创建一个新的输入字段。这意味着在所有输入字段都完成后,会创建一个新的表单。你可能没有注意到,新创建的输入字段允许用户更改它们的值。

    这里的问题在于,用户可能会在新表单中更改输入值,这可能会导致致命错误,因为我们没有检查机制来检查第二表单中的值。所以,我们天真地选择相信用户会相应地行动,只输入有效的值。但显然,我们错了。

    因此,为了检查第二表单,我们很可能需要创建一个新的函数来检查第二表单。

    尽管第二表单是动态生成的,但我们可以通过到目前为止学到的方法获取这些字段内的值。记住,因为 JavaScript 在第二表单中创建了字段,这些字段在技术上存在于内存中,因此仍然可以访问。

    有了这个想法,我们需要创建一个适用于这些字段的函数。

  6. 打开源代码,滚动到最后一个使用 jQuery 语句的函数。在这个函数之前,创建以下函数:

    function checkSecondForm(elementObj) {
    // some code going here
    }
    
    
  7. 首先,开始检查空值。因此,我们可以这样做来检查空值:

    if(document.testFormResponse.nameOfPerson.value === "") {
    alertMessage("fields must be filled!");
    return false;
    }
    if(document.testFormResponse.birth.value === "") {
    alertMessage("fields must be filled!");
    return false;
    }
    if(document.testFormResponse.age.value === "") {
    alertMessage("fields must be filled!");
    return false;
    }
    if(document.testFormResponse.spending.value === "") {
    alertMessage("fields must be filled!");
    return false;
    }
    if(document.testFormResponse.salary.value === "") {
    alertMessage("fields must be filled!");
    return false;
    }
    if(document.testFormResponse.retire.value === "") {
    alertMessage("fields must be filled!");
    return false;
    }
    if(document.testFormResponse.retirementMoney.value === "") {
    alertMessage("fields must be filled!");
    return false;
    }
    
    

    通常,我们应用第三章中学到的知识,使用===而不是==来检查空值。我们基本上检查在动态生成的字段中找到的值,并检查它们是否为空。

    现在我们已经有了检查字段是否为空的代码,是时候编写检查输入正确类型的代码了。

  8. 我们可以应用在第三章中学到的技术语法验证来检查输入的正确性。通常,我们使用正则表达式,像前几章一样,来检查输入的类型。我们可以这样做:

    var charactersForName = /^[a-zA-Z\s]*$/.test(document.testFormResponse.nameOfPerson.value);
    var charactersForPlaceOfBirth = /^[a-zA-Z\s]*$/.test(document.testFormResponse.birth.value);
    var digitsForAge = /^\d+$/.test(document.testFormResponse.age.value);
    var digitsForSpending = /^\d+$/.test(document.testFormResponse.spending.value);
    var digitsForSalary = /^\d+$/.test(document.testFormResponse.salary.value);
    var digitsForRetire = /^\d+$/.test(document.testFormResponse.retire.value);
    var digitsForRetirementMoney = /^\d+$/.test(document.testFormResponse.retirementMoney.value);
    // input is not relevant; we need a digit for input elements with name "enterNumber"
    if (charactersForName === false || charactersForPlaceOfBirth === false) {
    alertMessage("the input must be characters only!");
    debuggingMessages( checkSecondForm", elementObj, "wrong input");
    return false;
    }
    else if (digitsForAge === false || digitsForSpending === false || digitsForSalary === false || digitsForRetire === false || digitsForRetirementMoney === false ){
    alertMessage("the input must be digits only!");
    debuggingMessages( checkSecondForm", elementObj, "wrong input");
    return false;
    }
    // theinput seems to have no problem, so we'll process the input
    else {
    checkForm(elementObj);
    alert("all is fine");
    return false;
    }
    
    

    要查看完整版本的先前代码,请查看第五章source code文件夹,并参考perfect-code-for-JSLInt-enhanced.html文件。

    然而,记住,在早期的调试会话中,我们已经创建了新的检查机制,以支持边界值、防止负值,并确保退休年龄大于用户的当前年龄。

    因为第二表单可能会被更改,之前的错误也可能在第二表单中发生。因此,我们还需要添加那些检查机制。为了看看你是否做得正确,请查看source code文件夹中名为perfect-code-for-JSLInt-enhanced.html的文件中的checkSecondCode()函数。以下是checkSecondCode()的代码片段:

    // above code omitted
    if (elementObj.id === 'retire') {
    if (elementObj.value < document.getElementById('age').value) {
    alertMessage('retirement age must be larger than age');
    var element = document.getElementById(elementObj.id);
    jQuery(element).focus();
    return true;
    }
    }
    // this is the solution for checking negative values
    // this only applies to input fields that requires numeric inputs
    if (elementObj.name === 'enterNumber') {
    if (elementObj.value < 0) {
    alertMessage("Input must be positive!");
    var element = document.getElementById(elementObj.id);
    jQuery(element).focus();
    return true;
    }
    }
    if (elementObj.name === 'enterText') {
    if (elementObj.value.length <= 3) {
    alertMessage("Input must be more than 3 characters!");
    var element = document.getElementById(elementObj.id);
    jQuery(element).focus(); 
    return true;
    }
    if (elementObj.value.length >= 100) {
    alertMessage("Input must be less than 100 characters!");
    var element = document.getElementById(elementObj.id);
    jQuery(element).focus(); 
    return true;
    }
    }
    if (elementObj.name === 'enterNumber') {
    if (elementObj.value.length >= 10) {
    alertMessage("Input must be less than 10 digits!");
    var element = document.getElementById(elementObj.id);
    jQuery(element).focus(); 
    return true;
    }
    }
    // remaining code omitted
    }
    
    

刚才发生了什么?

我们已经完成了整个测试计划,包括回归测试。注意,在编码过程的每个阶段,我们都进行了小测试,以确保我们的解决方案正确工作;我们在回归测试过程中再次使用了单元测试。

请注意,我们还测试了程序逐步;我们测试了每个新函数或我们创建的代码,并确保它正确工作,在我们修复下一个错误之前。

通过这个过程,我们将有更好的机会创建好的程序,并避免将新的错误引入我们的代码。

除了在我们程序变化的过程中进行回归测试之外,关于我们程序的测试还有其他重要的问题。让我们转到第一个重要问题——性能问题。

性能问题——压缩你的代码以使其加载更快

如我在第四章中提到的,计划测试,我们编写的代码的性能取决于各种因素。性能通常指的是你代码的执行速度;这取决于你为代码使用的算法。由于算法问题超出了本书的范围,让我们专注于更容易实现的事情,比如通过压缩你的代码来提高程序的性能。

通常,压缩你的代码后,你的代码文件大小会更小,因此降低了在执行前需要缓存存储代码的磁盘使用量。它还减少了将 JavaScript 文件从 Web 服务器传输到客户端所需的带宽。所以现在,让我们看看我们如何压缩我们的 JavaScript 代码。

我们可以采取两种方式来做这件事:

  1. 我们可以压缩整个程序,这意味着我们将压缩我们的 CSS、HTML 和 JavaScript 在一起。

  2. 我们可以将所有的本地 JavaScript 移到一个外部文件中,然后只压缩这个外部 JavaScript 文件。为了保持简单,我将先使用第一种方法。

首先,我想让你访问jscompress.com/,并将我们的源代码粘贴到输入框中。有一个选项叫做"Minify (JSMin)"。这个选项将会一起压缩 HTML、CSS 和 JavaScript。一旦你将代码复制到输入框中,点击压缩 JavaScript

然后你会看到页面刷新,并将在输入框中显示压缩后的代码。将这段代码复制粘贴到一个新文件中,然后将其保存为testing-compressed.html

如果你去到source code文件夹,你会注意到我已经为你完成了压缩过程。检查testing-compressed.html文件和我们之前编写的代码的大小。根据我们所有的源代码,压缩后的版本是 12KB,而原始版本是 18KB。

现在让我们尝试第二种方法——将所有的 JavaScript 放在一个外部的 JavaScript 文件中并压缩这个文件。我们将这样做:

  1. 剪切掉<head></head>标签之间的所有 JavaScript,并将其粘贴到一个新的名为external.js的文件中。

  2. 保存external.js,并将 HTML 文档的更改也保存下来。

  3. 回到你的 HTML 文档,转到<head></head>标签之间,插入以下内容:<script type="text/javascript" src="img/external.js">。然后保存文件。

这样一来,你的代码就被压缩了,从而使得从服务器加载到客户端的速度更快。

看来我们成功地通过压缩代码来减小了文件大小。当然,由于我们的代码不多,所以区别并不明显。然而,在实际中,代码可以增加到数千甚至数万行,正如我们看到的 jQuery 库一样。在这种情况下,代码压缩将有助于提高性能。

注意

如果你是一个在保密协议(NDA)下工作的开发者,你可能不允许使用我之前提到的任何外部服务。如果是这种情况,你可能想考虑使用雅虎的 YUI 压缩器,它允许你直接从命令行工作。更多信息,请访问developer.yahoo.com/yui/compressor/#using

使用 Ajax 会有所不同吗?

让我先简要解释一下使用 Ajax 时会发生什么。JavaScript 是 Ajax 方程的一部分;JavaScript 的执行负责发送信息和从服务器加载信息。这是通过使用XMLHttpRequest对象来实现的。

当使用 Ajax 进行发送和加载数据时,测试责任是不同的;你不仅要测试我们前面章节中涵盖的各种错误,还要测试每个错误是否导致了信息的成功发送和加载以及对用户的正确视觉响应。

然而,由于你需要和服务器之间发送和接收请求,你可能需要进行某种形式的服务器端测试。这让我们来到了话题的下一部分——JavaScript 测试与服务器端测试的区别。

与服务器端测试的区别

如前一部分所述,当你在进行 Ajax 测试时,可能需要进行服务器端测试。通常,到目前为止你在书中所学的概念也可以应用于服务器端测试。因此,从概念上讲,JavaScript 测试和服务器端测试之间应该没有太大区别。

然而,请注意,服务器端测试通常包括服务器端代码,并且很可能包括 MySQL、PostgreSQL 等数据库。这意味着与 JavaScript 测试相比,服务器端测试的复杂性可能会有所不同。

尽管如此,你还是需要对所使用的服务器端语言、数据库等有深入了解。这是你开始规划测试的最基本要求。

注意

如果你在进行 Ajax 测试的服务器端测试,你肯定想了解一下超文本传输协议(HTTP)响应状态码。这些状态码是确定你的请求是否成功的一种方式。它们甚至告诉你是否发生了任何错误。更多信息请访问:www.w3.org/Protocols/rfc2616/rfc2616-sec10.html

如果访客关闭了 JavaScript 会发生什么

我们已经简要讨论了是否应该为关闭 JavaScript 的用户编写应用程序的问题。虽然关于是否应该支持这类用户存在不同的观点,但在我看来,最好的方法之一是至少告知我们的用户他们的浏览器不支持 JavaScript(或者 JavaScript 已被关闭),他们可能会错过一些内容。为了实现这一点,我们可以使用以下代码片段:

<html>
<body>
<script type="text/javascript">
document.write("Your browser supports JavaScript, continue as usual!");
// do some other code as usual since JavaScript is supported
</script>
<noscript>
Sorry, your browser does not support JavaScript! You will need to enable JavaScript in order to enjoy the full functionality and benefits of the application
</noscript>
</body>
</html>

  • 请注意,我们使用了<noscript>标签,这是在 JavaScript 被关闭或不被支持时显示用户的替代内容的途径。

    既然我们已经接近本章的尾声,你可能已经掌握了要领。让我们看看你是否能通过尝试以下练习来提高你的技能。

尝试英雄——提升我们程序的可用性

既然你已经走到了这一步,你可能想尝试这个任务——通过以下方式提升这个程序的可用性:

  • 确保用户从第一个字段到最后一个字段输入所需信息。

    我们程序可能遇到的另一个问题是,用户可能会点击第一个以外的任何输入字段并开始输入信息。尽管这可能不会直接影响我们程序的正确性,但有可能结果不是我们预期的。

  • 关于第二个表单,你有什么方法能告知你的用户哪些输入字段有错误的输入?用户可以更改错误的输入吗?

    当我们修复与第二个表单相关的错误时,我们只是创建了检测第二个表单输入正确性的机制。然而,如果用户在第二个表单中输入了错误的值,用户可能不会立即知道哪些字段输入错误。

以下是一些帮助你开始这项练习的提示:

  • 从一开始,你可以禁用所有除了第一个以外的输入字段。然后当第一个字段获得正确的输入时,你可以启用第二个输入字段。同样,当第二个输入字段正确完成时,第三个输入字段被启用,依此类推。

  • 对于第二个问题,你可能想查看我们的代码,看看你是否能编辑checkSecondForm()函数中if else语句中的条件。我所做的是将所有可能性合并成一个ifelse if语句,从而使无法检测出哪个字段出了问题。你可以尝试将条件拆分,使得每个ifelse if语句只包含一个条件。这样,如果出现问题,我们就能为第二表单中的每个输入字段创建一个自定义响应。

总结

哇,我们在这一章节中涵盖了大量的知识。我们执行了测试计划并发现了 bug。接下来我们成功地修复了我们发现的问题。在修复每个 bug 后,我们执行了回归测试,以确保保留了原始功能,并且没有在程序中引入新的 bug。

具体来说,我们讨论了以下主题:

  • 如何执行测试计划以及如何记录我们发现的问题

  • 修复每个错误后如何执行回归测试

  • 如何压缩代码以提高性能

  • 如果我们使用 Ajax,测试差异如何

  • 客户端测试与服务器端测试的区别

前面提到的学习点可能看起来很小,但既然你已经阅读了这一章节,你应该知道执行测试计划和随后修复 bug 可能会很繁琐。

现在我们已经讨论了测试计划的执行,是时候讨论稍微复杂一些的内容——测试更复杂的代码。请注意,我们一直以一种一维的方式处理 JavaScript:我们将所有的 JavaScript 放在我们的 HTML 文件中,还包括 CSS。我们一直将 JavaScript 代码开发成这样,因为我们只使用这一段 JavaScript 代码。但是,实际上,通常可以看到 web 应用程序使用不止一段 JavaScript 代码;这段额外的代码通常通过外部 JavaScript 文件附上。

更重要的是,这并不是我们在现实世界中唯一会面临的问题。随着我们的代码变得更加复杂,我们将需要使用更复杂的测试方法,甚至可能需要使用内置控制台等工具,以更有效、更高效地帮助我们进行测试。

在下一章中,我们将讨论之前提到的 issues,第六章,测试更复杂的代码。在那里见!

第六章:测试更复杂的代码

欢迎来到第六章。在这一章中,我们将了解更多关于 JavaScript 测试的内容。更具体地说,我们将学习如何测试更复杂的代码,其中实体之间会有更多的交互。到目前为止,我们一直在对相对简单的代码进行测试,使用的是相对直接的技术。

更具体地说,我们将涵盖以下内容:

  • 组合脚本时可能发生的错误类型

  • 如何处理组合脚本时发生的错误

  • 目前互联网上可用的各种 JavaScript 库,以及我们在测试它们时需要考虑的问题。

  • 如何测试 GUI、库的控件插件以及其他考虑因素

  • 如何使用控制台日志

  • 使用 JavaScript 内置对象进行异常处理

  • 使用 JavaScript 内置对象测试应用程序

让我们从覆盖组合脚本时可能发生的错误类型的基本概念开始。

组合脚本的问题

到目前为止,我们一直专注于在 HTML 文档中编写和测试只有一段 JavaScript 代码。考虑一下现实生活中的情况,我们通常使用外部的 JavaScript;如果我们使用多个 JavaScript 文件会发生什么?如果我们使用多个外部 JavaScript 文件,我们可能会遇到什么问题?我们在下面的子节中都会覆盖到。我们首先从第一个问题开始——组合事件处理器。

组合事件处理器

你可能意识到了,也可能没有意识到,但自从第三章《语法验证》以来,我们就一直在处理事件处理器。实际上,我们在《第一章,什么是 JavaScript 测试》中提到了事件。JavaScript 通过添加交互性,使我们的网页充满生机。事件处理器是交互性的心跳。例如,我们点击一个按钮,一个弹出窗口就会出现,或者我们的光标移动到 HTML div元素上,元素的颜色会改变以提供视觉反馈。

为了了解我们可以如何组合事件处理器,请考虑以下示例,该示例在文件combine-event-handlers.htmlcombine-event-handlers.js中,如以下代码所示:

combine-event-handlers.html中,我们有:

<html>
<head>
<title>Event handlers</title>
<script type="text/javascript" src="combine-event-
handlers.js"></script>
</head>
<body>
<div id="one" onclick="changeOne(this);"><p>Testing One</p></div>
<div id="two" onclick="changeTwo(this);"><p>Testing Two</p></div>
<div id="three" onclick="changeThree(this);"><p>Testing Three</p></div>
</body>
</html>

请注意,每个div元素都由不同的函数处理,分别是changeOne()changeTwo()changeThree()。事件处理器在combine-event-handlers.js中:

function changeOne(element) {
var id = element.id;
var obj = document.getElementById(id);
obj.innerHTML = "";
obj.innerHTML = "<h1>One is changed!</h1>";
return true;
}
function changeTwo(element) {
var id = element.id;
var obj = document.getElementById(id);
obj.innerHTML = "";
obj.innerHTML = "<h1>Two is changed!</h1>";
return true;
}
function changeThree(element) {
var id = element.id;
var obj = document.getElementById(id);
obj.innerHTML = "";
obj.innerHTML = "<h1>Three is changed!</h1>";
return true;
}

你可能想接着测试程序。随着你点击文本,内容会根据函数中的定义发生变化。

然而,我们可以重写代码,使得所有事件都由一个函数处理。我们可以将combine-event-handlers.js重写为如下:

function combine(element) {
var id = element.id;
var obj = document.getElementById(id);
if(id == "one"){
obj.innerHTML = "";
obj.innerHTML = "<h1>One is changed!</h1>";
return true;
}
else if(id == "two"){
obj.innerHTML = "";
obj.innerHTML = "<h1>Two is changed!</h1>";
return true;
}
else if(id == "three"){
obj.innerHTML = "";
obj.innerHTML = "<h1>Three is changed!</h1>";
return true;
}
else{
; // do nothing
}
}

当我们使用if else语句检查我们正在处理的div元素的id,并相应地改变 HTML 内容时,我们可以节省很多行代码。请注意,我们已经将函数重命名为combine()

因为我们对 JavaScript 代码做了一些改动,所以我们还需要对我们的 HTML 进行相应的改动。所以combine-event-handlers.html将被重写如下:

<html>
<head>
<title>Event handlers</title>
<script type="text/javascript" src="img/combine-event- handlers.js"></script>
</head>
<body>
<div id="one" onclick="combine(this);"><p>Testing One</p></div>
<div id="two" onclick="combine(this);"><p>Testing Two</p></div>
<div id="three" onclick="combine(this);"><p>Testing Three</p></div>
</body>
</html>

请注意,现在div元素由同一个函数combine()处理。这些重写的示例可以在combine-event-handlers-combined.htmlcombine-event-handlers-combined.js中找到。

名称冲突

处理名称冲突是我们需要解决的下一个问题。与事件处理程序组合的问题类似,名称冲突发生在两个或更多变量、函数、事件或其他对象具有相同名称时。尽管这些变量或对象可以包含在不同的文件中,但这些名称冲突仍然不允许我们的 JavaScript 程序正常运行。请考虑以下代码片段:

nameclash.html中,我们有以下代码:

<html>
<head>
<title>testing</title>
<script type="text/javascript" src="img/nameclash1.js"></script>
</head>
<body>
<div id="test" onclick="change(this);"><p>Testing</p></div>
</body>
</html>

nameclash1.js中,我们有以下代码:

function change(element) {
var id = element.id;
var obj = document.getElementById(id);
obj.innerHTML = "";
obj.innerHTML = "<h1>This is changed!</h1>";
return true;
}

如果你通过在浏览器中打开文件并点击文本Testing来运行此代码,HTML 内容将按预期更改。然而,如果我们在这段代码后面添加<script type="text/javascript" src="img/nameclash2.js"></script>,并且nameclash2.js的内容如下:

function change(element) {
alert("so what?!");
}

然后我们将无法正确执行代码。我们将看到警告框,而不是 HTML 内容被改变。如果我们改变外部 JavaScript 的位置,那么div元素的内容将被改变,我们将无法看到警告框。

由于这些名称冲突,我们的程序变得不可预测;解决这个问题的方法是在你的函数、类或事件中使用独特的名称。如果你有一个相对较大的程序,建议使用命名空间,这是 YUI 和 jQuery 等几个 JavaScript 库中常见的策略。

使用 JavaScript 库

现在有很多 JavaScript 库可供使用。一些最常用的如下:

还有更多的 JavaScript 库。要查看完整的列表,请随时访问en.wikipedia.org/wiki/List_of_JavaScript_libraries.

  • 如果您考虑使用 JavaScript 库,您可能已经了解到使用 JavaScript 库的好处。事件处理以及令人望而生畏的跨浏览器问题使得考虑使用 JavaScript 库变得必要。但是,您可能想知道作为初学者 JavaScript 程序员在选择 JavaScript 库时应注意什么。这里有一份需要考虑的事项列表:
    • 可用支持的级别,以文档形式表示。
    • 是否提供教程,以及它们是免费还是付费。这有助于加快编程过程。
    • 插件和附加组件的可用性。
    • 库是否有内置的测试套件?这对于我们的目的来说非常重要。

- 您是否需要测试别人编写的库?

  • 首先,当我们学习 JavaScript 测试时,我认为对于初学者学习 JavaScript 编程,可能不建议测试别人编写的 JavaScript 库。这是因为我们需要理解代码才能进行准确测试。能够进行客观(且准确)测试的是 JavaScript 专家,而虽然您正在成为其中的一员,但您可能还没有达到那个水平。

  • 其次,从实际角度考虑,已经为我们完成了许多这样的测试。您需要做的就是在互联网上搜索它们。

  • 但为了学习目的,让我们简要了解一下通常针对库代码运行哪些测试。

- 针对库代码应运行哪些测试

  • 通常,作为各种 JavaScript 库的用户,我们最常进行性能测试和性能测试。

- 性能测试

  • 性能测试,顾名思义,是关于测试您的代码性能。这包括以手动方式测试您的代码(在各种浏览器上)的速度,或使用某些工具(如 Firebug 或其他工具)(更多此类工具将在第八章中介绍)。

  • 通常,为了生成性能测试的准确结果,您需要针对所有流行平台测试您的代码(最理想的是使用工具和测试套件)。例如,对 JavaScript 代码进行性能测试的常见方法是在 Firefox 中安装 Firebug 并使用它进行测试。但是从实际角度考虑,Firefox 用户只占互联网用户的约四分之一(最多三分之一)。为了确保您的代码达到标准,您还需要针对其他平台(如 Internet Explorer)进行测试。我们将在第八章中介绍更多内容。

- 性能测试

剖析测试与性能测试类似,不同之处在于它关注的是代码中的瓶颈,而不是整体性能。瓶颈通常是低效代码的主要罪魁祸首。修复瓶颈(几乎)是提高代码性能的确定方法。

图形用户界面(GUI)和控件插件以及如何测试它们的相关考虑

如果你查看了我向你指出的各种 JavaScript 库的列表,你可能会注意到一些 JavaScript 库也提供了用户界面或控件插件。这些旨在增强你的应用程序的用户界面,最重要的是,通过实现常用的用户界面组件(如对话框、颜色选择器等)来帮助你节省时间和精力。

但是问题就从这里开始——我们如何测试这些用户界面和控件插件呢?我们可以采取很多方法来完成这件事,但最简单的方法(或许也是最繁琐的)莫过于 visually 和 manually 进行测试。例如,如果我们期望一个对话框会出现在屏幕的左上角,并且具有某种颜色、宽度和高度,如果它没有以我们期望的方式出现,那么就出错了。

同样,如果我们看到了我们预期看到的东西,那么我们可以说它是正确的——至少在视觉上是这样。

然而,需要进行更严格的测试。测试用户界面可能是一项艰巨的任务,因此我建议你使用像 Sahi 这样的测试工具,它允许我们用任何编程语言编写自动化网页应用界面测试。像 Sahi 这样的工具超出了本章的范围。我们将在第八章详细介绍 Sahi。与此同时,如果你急于了解 Sahi,可以随时访问他们的网站:sahi.co.in

故意抛出自己的 JavaScript 错误

在本节中,我们将学习如何抛出自己的 JavaScript 错误和异常。我们将简要介绍错误函数和命令的语法。这时给你语法可能有点难以理解,但这是必要的。一旦你理解了如何使用这些命令和保留字,你将了解如何利用它们提供更具体的信息(从而获得更多控制权)来控制你可以在下一节中捕获和创建的错误类型。那么让我们从第一个保留字——throw开始吧。

抛出语句

throw是一个允许你创建异常或错误的语句。它有点像break语句,但throw允许你跳出任何作用域。通常,我们用它来字面意思上抛出一个错误。语法如下:

throw(exception);

我们可以用throw(exception)以下方式:

throw "This is an error";

或者:

throw new Error("this is an error");

Error是一个内置对象,通常与throw语句一起使用;我们稍后会介绍Error。现在要理解的重要一点是语法,以及throw也经常与try, catchfinally一起使用,这将帮助你控制程序流程并创建准确的错误信息。现在让我们继续讲解catch

尝试,捕获和最后语句

try, catchfinally语句是 JavaScript 的异常处理机制,如前所述,它帮助你控制程序流程,同时捕获你的错误。try, catchfinally语句的语法如下:

try {
// exceptions are handled here
}
catch (e) {
// code within the catch block is executed if any exceptions are caught in the try block
}
finally {
// code here is executed no matter what happens in the try block
}

请注意try后面跟着catch,然后可选地使用finally。通常,catch语句捕获try语句中发生的异常。异常是一个错误。只要trycatch语句终止,finally语句就会执行。

既然我们已经介绍了故意抛出 JavaScript 错误的基本命令和保留字,那么让我们来看一个try, catchfinally一起使用的例子。下面的代码可以在第六章source code文件夹中的 HTML 文档try-catch-finally-correct-version.html中找到。查看下面的代码:

<html>
<head>
<script>
function factorial(x) {
if(x == 0) {
return 1;
}
else {
return x * factorial(x-1);
}
}
try {
var a = prompt("Enter a positive integer", "");
var f = factorial(a);
alert(a + "! = " + f);
}
catch (error) {
// alert user of the error
alert(error);
alert(error.message);
}
finally {
alert("ok, all is done!");
}
</script>
</head>
<body>
</body>
</html>

你可以将上面的代码复制并粘贴到你最喜欢的文本编辑器中,保存它,然后在浏览器中运行。或者你可以运行样本文件try-catch-finally-correct-version.html

你将看到一个提示窗口,要求你输入一个正整数。接着输入一个正整数,比如3,然后你会收到一个警告窗口,告诉你3! = 6。之后,你应该会收到另一个警告窗口,其中包含消息好的,一切都完成了!,因为finally块将在trycatch终止后执行。

现在,输入一个负数,比如-1。如果你使用的是 Firefox,你会收到一个提示窗口,告诉你有太多的递归。如果你使用的是 Internet Explorer,你会收到一个[object Error]消息。

在第一个弹出窗口之后,你将收到第二个弹出窗口。如果你使用的是 Firefox,你会看到一个InternalError: Too much recursion消息。如果你使用的是 Internet Explorer,你会收到一个Out of stack space消息。

最后,你应该会看到一个最终的警告窗口,其中包含消息好的,一切都完成了!,因为finally块将在trycatch终止后执行。虽然确实我们遇到了一个错误,但错误信息并不是我们真正需要的,因为它没有告诉我们我们输入了非法值。

这就是throw发挥作用的地方。throw可以用来控制程序流程,并为每种错误给出正确的响应。查看下面的代码,也可以在source code文件夹中的文件try-catch-finally-throw-correct-version.html找到。

<html>
<head>
<script>
function factorial(x) {
if(x == 0) {
return 1;
}
else {
return x * factorial(x-1);
}
}
try {
var a = prompt("Please enter a positive integer", "");
if(a < 0){
throw "negative-error";
}
else if(isNaN(a)){
throw "not-a-number";
}
var f = factorial(a);
alert(a + "! = " + f);
}
catch (error) {
if(error == "negative-error") {
alert("value cannot be negative");
}
else if(error == "not-a-number") {
alert("value must be a number");
}
else
throw error;
}
finally {
alert("ok, all is done!");
}
</script>
</head>
<body>
</body>
</html>

现在请执行程序,输入正确的值、负值和非字母数字值。根据你的输入,你应该会收到正确的错误消息。

注意之前代码行中我们使用throw语句来控制要显示给用户的错误消息类型。这是throw语句可以使用的几种方式之一。请注意,在throw之后定义的字符串用于创建程序逻辑,以决定应调用哪些错误消息。

如果你想知道这种异常处理机制还有哪些其他功能,请从try-catch-finally-correct-version.html中删除factorial函数。或者,你可以打开文件try-catch-finally-wrong-version.html并运行程序。然后尝试输入任何值。你应该会收到一个警告消息,告诉你factorial函数未定义,之后你将收到另一个警告框,显示好的,一切都完成了。请注意,在这种情况下,我们不需要编写任何形式的消息;catch足够强大,可以告诉我们出了什么问题。

需要注意的是,如果不对异常编写处理程序,JavaScript 运行时可能会捕获异常。

既然我们已经介绍了异常处理机制的基本知识,接下来让我们具体了解一下——处理错误的内置对象。

使用内置对象捕获错误

在本节中,我们将简要介绍每种内置对象是什么,以及它们的语法,然后展示每个内置对象如何工作的示例。请注意,我们将在示例中适度使用警告消息,这些消息是基于 Firefox 浏览器。如果你在 Internet Explorer 上尝试代码,你可能会看到不同的错误消息。

错误对象

Error是一个通用的异常,它接受一个可选的消息,提供异常的详细信息。我们可以使用Error对象,使用以下语法:

new Error(message); // message can be a string or an integer

以下是一个显示Error对象动作的示例。这个示例的源代码可以在文件error-object.html中找到。

<html>
<head>
<script type="text/javascript">
function factorial(x) {
if(x == 0) {
return 1;
}
else {
return x * factorial(x-1);
}
}
try {
var a = prompt("Please enter a positive integer", "");
if(a < 0){
var error = new Error(1);
alert(error.message);
alert(error.name);
throw error;
}
else if(isNaN(a)){
var error = new Error("it must be a number");
alert(error.message);
alert(error.name);
throw error;
}
var f = factorial(a);
alert(a + "! = " + f);
}
catch (error) {
if(error.message == 1) {
alert("value cannot be negative");
}
else if(error.message == "it must be a number") {
alert("value must be a number");
}
else
throw error;
}
Error objectworkingfinally {
alert("ok, all is done!");
}
</script>
</head>
<body>
</body>
</html>

你可能注意到了,这个代码的结构与之前的例子相似,我们在其中演示了try, catch, finallythrow。在这个例子中,我们利用了我们所学的知识,并没有直接抛出错误,而是使用了Error对象。

我需要你关注上面给出的代码。注意我们已经将整数和字符串作为var error的消息参数,分别是new Error(1)new Error("it must be a number")。请注意我们可以使用alert()创建一个弹出窗口,以通知用户发生的错误和错误的名称,因为它是Error对象,所以名称是Error。同样,我们可以使用消息属性来为适当的错误消息创建程序逻辑。

了解Error对象是如何工作的很重要,因为以下我们要学习的内置对象的工作方式与Error对象的工作方式相似。(我们可能能够展示如何在这些错误中使用控制台日志。)

RangeError 对象

当一个数字超出其适当的范围时,会发生RangeError。这个语法与我们之前看到的Error对象相似。这是RangeError的语法:

new RangeError(message);

message 可以是字符串或整数。

我们从一个简单的例子开始,展示这是如何工作的。查看以下代码,可以在source code文件夹中的rangeerror.html文件找到:

<html>
<head>
<script type="text/javascript">
try {
var anArray = new Array(-1);
// an array length must be positive
}
catch (error) {
alert(error.message);
alert(error.name);
}
finally {
alert("ok, all is done!");
}
</script>
</head>
<body>
</body>
</html>

当你运行这个例子时,你应该会看到一个警告窗口,通知你数组长度无效。在此警告窗口之后,你应该会收到另一个警告窗口,告诉你错误是 RangeError,因为这是一个RangeError对象。如果你仔细查看代码,你会看到我故意创建了这个错误,给数组长度一个负值(数组长度必须是正数)。

引用错误

当你引用的变量、对象、函数或数组不存在时,会发生引用错误。到目前为止你看到的语法相似,如下所示:

new ReferenceError(message);

message 可以是字符串或整数。

因为这个问题很简单,所以我直接进入下一个例子。以下例子的代码可以在source code文件夹中的referenceerror.html文件找到。

<html>
<head>
<script type="text/javascript">
try {
x = y;
// notice that y is not defined
// an array length must be positive 
}
catch (error) {
alert(error);
alert(error.message);
alert(error.name);
}
finally {
alert("ok, all is done!");
}
</script>
</head>
<body>
</body>
</html>

注意y未定义,我们期望在catch块中捕获这个错误。现在在你的 Firefox 浏览器中尝试之前的例子。你应该会收到四个关于错误的警告窗口,每个窗口都会给你不同的消息。消息如下:

  • 引用错误: y 未定义

  • y 未定义

  • 引用错误

  • 好的,一切都完成了

如果你在使用 Internet Explorer,你会收到稍微不同的消息。你会看到以下消息:

  • [object Error] message

  • y 是未定义的

  • TypeError

  • 好的,一切都完成了

TypeError 对象

当尝试访问类型不正确的值时,会抛出一个TypeError。语法如下:

new TypeError(message); // message can be a string or an integer and it is optional

TypeError的一个例子如下:

<html>
<head>
<script type="text/javascript">
try {
y = 1
var test = function weird() {
var foo = "weird string";
}
y = test.foo(); // foo is not a function
}
catch (error) {
alert(error);
alert(error.message);
alert(error.name);
}
finally {
alert("ok, all is done!");
}
</script>
</head>
<body>
</body>
</html>

如果你尝试在 Firefox 中运行此代码,你应该会收到一个警告框,指出它是一个TypeError。这是因为test.foo()不是一个函数,这导致了一个TypeError。JavaScript 能够找出捕获了哪种类型的错误。同样,你可以通过取消注释代码来使用传统的抛出自定义TypeError()的方法。

以下内置对象使用较少,所以我们快速浏览一下内置对象的语法。

SyntaxError 对象

当你在语法上出错时,会发生语法错误SyntaxError的语法如下:

new SyntaxError([message,[,,[,filename[, lineNumber]]]); // message can be a string or an integer and it is optional

请注意filenamelineNumber参数是非标准的,如果可能的话应避免使用它们。

URIError对象

URIError是在遇到格式不正确的 URI 时发生的。该语法的格式如下:

new URIError([message,[,filename[, lineNumber]]]);

类似于SyntaxError,请注意filenamelineNumber参数是非标准的,如果可能的话应避免使用它们。

EvalError对象

EvalError是在使用不正确或包含其他错误的eval语句时发生的。

new EvalError([message,[,filename[, lineNumber]]]);// message can be a string or an integer and it is optional

类似于SyntaxErrorURIError,请注意filenamelineNumber参数是非标准的,如果可能的话应避免使用它们。

使用错误控制台记录信息

Firefox 的控制台是一个足够强大的工具,可以让你记录 JavaScript 消息。你可以记录内置对象的错误信息,也可以编写你自己的信息。

错误信息

我们在本节中看到的错误信息是在 Firefox 错误控制台中生成的,并记录在错误控制台的日志中。在开始之前,我需要你打开你的 Firefox 浏览器,点击菜单栏上的工具,然后选择错误控制台。确保你没有打开其他标签页。

现在,打开你的代码编辑器,并在新文档中输入以下代码:

<html>
<head>
<script type="text/javascript">
try {
var anArray = new Array(-1););
}
catch (error) {
throw error;
}
finally {
alert("ok, all is done!");
}
</script>
</head>
<body>
</body>
</html>

将文档保存为.html文件,然后在你的 Firefox 浏览器中运行该文件。或者,你可以使用位于source code文件夹中的源代码与 HTML 文档一起使用,文档名为:error-message-console.html。如果你现在查看你的控制台,你应该会收到以下错误信息:无效的数组长度。这是因为我们在上面的代码中定义了一个负长度的数组。

这里的技巧是使用throw语句来抛出错误信息。请注意,Firefox 的错误控制台不会显示错误的name

现在我们将看看如何创建自定义错误信息。

编写你自己的消息

让我们继续创建我们自己的错误信息。完整的代码可以在source code文件夹中的test-custom.html文件找到。

再次打开你的代码编辑器,创建一个新文档,并输入以下代码:

<html>
<head>
<script type="text/javascript">
function factorial(x) {
if(x == 0) {
return 1;
}
else {
return x * factorial(x-1);
}
}
try {
var a = prompt("Please enter a positive integer", "");
if(a < 0){
throw new Error("Number must be bigger than zero"); 
}
else if(isNaN(a)){
throw new Error("You must enter a number"); 
}
var f = factorial(a);
alert(a + "! = " + f);
}
catch (error) {
throw error; 
}
</script>
</head>
<body>
</body>
</html>

我们所做的是在try块中抛出两个带有自定义消息的新Error对象,然后在catch块中再次抛出Error对象。在try块中,我们创建了一个自定义的Error对象,而在catch块中,我们将消息抛向错误控制台

请注意突出显示的行。我们在Error对象中定义了我们自己的消息。保存文件,然后打开你的 Firefox 浏览器。转到工具 | 错误控制台。在错误控制台中,确保你在所有标签或错误标签。现在在你的 Firefox 浏览器中运行你的代码。如果你输入非数字输入,你将在错误控制台收到你必须输入一个数字的消息。如果你输入的数字小于零,你将收到数字必须大于零的消息。关键在于利用提供的方法和属性来抛出你自己的错误信息。

修改脚本和测试

既然我们已经介绍了使用内置对象抛出和捕获错误的基本模块,以及使用控制台抛出错误消息,是时候学习我们如何可以将所学应用到一个简单的应用程序上了。

行动时间——编码、修改、抛出和捕获错误

在这一部分,我需要你集中注意力,因为我们将会应用我们之前创建第一个应用程序时学到的所有知识。之后,我们将尝试生成我们自己的错误,并在测试过程中抛出各种错误信息。

我们将要创建的是一个模拟电影预订系统。我不知道你们是否注意到了,但我注意到服务台的工作人员使用某种电影预订系统,它有一个 GUI 来帮助他们的预订过程。我们不仅会创建那个系统,还会添加更多功能,比如购买与电影票一起的食品和饮料。

以下是电影票预订系统的详细信息:当你点击每个座位时,你正在执行一个预订动作。如果座位已被预订,点击它将执行一个取消预订动作。

其他重要的设计规则如下:你不能购买比你预订的门票更多的餐点。例如,如果你预订了四张门票,你只能购买最多四份餐点,无论是热狗套餐还是爆米花套餐。同样,你每购买一份餐点,你可以购买一个 Sky Walker。这意味着如果你购买了三份餐点,你只能购买最多三个 Sky Walker。另外,你只能用百元钞票支付。这意味着你只能在请用 100 美元钞票支付输入框中输入百位数的数字。

如果您在想各种商品的价格,门票每张 10 美元。热狗套餐 6 美元,爆米花套餐 4 美元。Sky Walker 每个 10 美元。

清楚这些规则了吗?如果你清楚这些规则,我们首先开始创建这个应用程序。之后,我们将把异常捕获机制作为最后一步。顺便说一下,这个例子完成的代码可以在第六章的cinema-incomplete文件夹中找到。

  1. 打开代码编辑器,创建一个新文件。将以下代码输入到你的文件中。

    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
    <html >
    <head>
    <title>JavaScript Cinema</title>
    </head>
    <body>
    </body>
    </html>
    
    

    这将构成我们程序的骨架。现在,它不会做任何事情,也不会在您的网页上显示任何设计。因此,我们将从创建我们应用程序的布局开始。

  2. 在您的 HTML 文档的<body>标签内输入以下代码。

    <div id="container">
    <div id="side-a">
    <h1>Welcome to JavaScript Cinema </h1>
    <div class="screen">
    <p> Screen is located here. </p>
    </div>
    <div class="wrapper" id="tickets">
    <p>You have booked 0 tickets</p>
    </div>
    <div class="wrapper">
    <p>Click on the seats above to make your booking.</p>
    </div>
    </div>
    <div id="side-b">
    <div class="menuRight">
    <h4>Meal Pricing</h4>
    <p>Hotdog Meal : $6 <br />Popcorn Meal : $4</</p>
    <form name="foodForm" onsubmit="return checkForm()">
    <!-- total number of meals cannot exceed total number of tickets purchased -->
    # of Hotdog Meal ($6/meal): <input type="text" name="hotdogQty" length="3" size="3px"/>
    <br />
    # of Popcorn Meal ($4/meal): <input type="text" name="popcornQty" length="3" size="3px" />
    <p class="smalltext">Total # of meals cannot exceed total number of tickets purchases</p>
    <br />
    <!-- here's some specials to go with -->
    <p>Here's the special deal of the day:</p>
    Sky Walker($10):<input type="text" name="skywalker" length="3" size="3px"/>
    <p class="smalltext">You can only buy 1 Sky Walker for every meal you've purchased.</p>
    <br />
    <!-- show total price here -->
    Please pay in $100 notes
    <input type="text" name="hundred" length="3" size="3px" />
    <br />
    <input type="submit" value="Order Now">
    </form>
    </div>
    <div id="orderResults"> </div>
    </div>
    </div>
    
    

    这段代码构成了我们电影票预订应用程序的基本控制。您可能已经注意到有各种带有 wrapper 类的div元素。这些元素将用于创建一个类似网格的用户界面,代表影院的座位。所以现在我们将开始创建用于表示座位的网格。

  3. 首先,我们将构建网格的第一行。首先,在具有 wrapper 类的第一个div元素内输入以下代码:

    <div class="left1" id="a1" name="seats" onclick="checkBooking(this);">
    <p>Available</p>
    </div>
    <div class="left2" id="a2" name="seats" onclick="checkBooking(this);">
    <p>Available</p>
    </div>
    <div class="left8" id="a8" name="seats" onclick="checkBooking(this);">
    <p>Available</p>
    </div>
    <div class="left9" id="a9" name="seats" onclick="checkBooking(this);">
    <p>Available</p>
    </div>
    
    

    请注意,您在具有 wrapper 类的第一个div元素内输入的每个<div>元素都有一个classid属性。通常,第一个div将有一个left1类和一个a1ID。下一个div元素将有一个left2类和a2ID,依此类推。这是我们设计网格的方式。现在,让我们进行下一步。

  4. 与步骤 3 类似,我们将构建网格的下一行。在第二个具有 wrapper 类的div元素内输入以下代码:

    <div class="left1" id="b1" name="seats" onclick="checkBooking(this);">
    <p>Available</p>
    </div>
    <div class="left2" id="b2" name="seats" onclick="checkBooking(this);">
    <p>Available</p>
    </div>
    <div class="left8" id="b8" name="seats" onclick="checkBooking(this);">
    <p>Available</p>
    </div>
    <div class="left9" id="b9" name="seats" onclick="checkBooking(this);">
    <p>Available</p>
    </div>
    
    

    注意,构成网格第二行的div元素具有以"b"开头的 ID,与第一行的"a"开头形成对比。这是我们将继续用来命名和构建网格的方式。这意味着下一行将具有以"c"开头的 ID,第四行将以"d"开头,依此类推。

    总共我们将创建五行。这意味着我们还有三行要做。

  5. 现在我们将构建网格的下一三行。将上一步给出的代码输入到剩余的div元素中,但请记住根据行号更改每个元素的id。同时,记得包含onclick="checkBooking(this)",因为这将用于执行我们的 JavaScript 函数。

    完成 HTML 后,是时候添加 CSS 以创建我们应用程序的正确设计和布局。

  6. 对于这个例子,我们将使用外部 CSS。因此,在<title>标签之后插入以下代码。

    <link rel="stylesheet" type="text/css" href="cinema.css" />
    
    
  7. 现在我们将创建一个 CSS 文件。打开一个新文档,将其保存为cinema.css,因为这是我们步骤 6 中提到的。接下来,将以下代码输入到cinema.css中:

    body{
    border-width: 0px;
    padding: 0px;
    padding-left: 20px;
    margin: 0px;
    font-size: 90%;
    }
    #container {
    text-align: left;
    margin: 0px auto;
    padding: 0px;
    border:0;
    width: 1040px;
    }
    #side-a {
    float: left;
    width: 840px;
    }
    #side-b {
    margin: 0;
    float: left;
    margin-top:100px;
    width: 200px;
    height: 600px;
    background-color: #cccc00;
    }
    
    

    这是用于构建应用程序框架的 CSS 类和 ID 选择器的代码。如果您忘记了 CSS 是如何工作的,您可能想回到第一章,什么是 JavaScript 测试,复习一下。

    现在,我们将决定网格上座位的大小和其他重要属性。

  8. 我们将定义座位的宽度、高度、背景颜色和文本颜色。将以下代码添加到cinema.css中:

    #a1,#a2,#a3,#a4,#a5,#a6,#a7,#a8,#a9,
    #b1,#b2,#b3,#b4,#b5,#b6,#b7,#b8,#b9,
    #c1,#c2,#c3,#c4,#c5,#c6,#c7,#c8,#c9,
    #d1,#d2,#d3,#d4,#d5,#d6,#d7,#d8,#d9,
    #e1,#e2,#e3,#e4,#e5,#e6,#e7,#e8,#e9
    {
    background:#e5791e;
    color:#000000;
    width: 71px;
    height: 71px;
    }
    
    

    之前的代码为我们的电影院中的所有“座位”定义了大小、颜色和背景。现在我们在创建应用程序的布局和设计方面迈出了最后一步。

  9. 现在我们将定义包含我们的座位的网格的布局和颜色。完成的 CSS 代码可以在cinema-incomplete文件夹的source code文件夹中的cinema.css文件中找到。将以下代码添加到cinema.css中:

    .wrapper{
    position: relative;
    float: left;
    left: 0px;
    width: 840px;
    margin-bottom: 20px;
    background-color: #cccccc
    }
    ...
    .left1{
    position: relative;
    float: left;
    left: 10px;
    z-index:0;
    }
    .left2{
    position: relative;
    float: left;
    left: 30px;
    width: 71px;
    height: 71px;
    }
    ... ...
    .left8{
    position: relative;
    float: left;
    left: 150px;
    }
    .left9{
    position: relative;
    float: left;
    left: 170px;
    }
    
    

    这段 CSS 代码基本上定义了网格的每一列。一旦你完成了这个,将其保存为cinema.csscinema.html。确保这些文件在同一个文件夹中。打开cinema.html在你的网页浏览器中,你应该会看到类似以下屏幕截图的东西:

    行动时间—编码、修改、抛出和捕获错误

    如果你发现有什么不对劲的地方,你可能想比较一下你的代码和在cinema-incomplete文件夹中找到的示例源代码。

    现在我们已经完成了应用程序的设计和布局,是时候为我们添加应用程序的行为了。以下部分的完整代码示例可以在第六章cinema-complete文件夹中找到。

  10. 我们将使用一个外部的 JavaScript 文件。所以让我们在</head>标签之前添加以下代码片段:

    <script type="text/javascript" src="img/cinema.js"></script>
    
    
  11. 现在让我们创建一个新的文件,命名为cinema.js。我们将专注于创建票务预订机制。因为我们将通过点击座位来预订票,所以我们需要一个处理点击事件的机制。因为我们在 HTML 代码中已经包含了onclick="checkBooking(this)",我们现在需要做的是创建一个处理点击事件的函数。将以下代码添加到cinema.js中:

    function checkBooking(element) {
    var id = element.id;
    var status = document.getElementById(id).innerHTML;
    // "<P>Available</P>" is for an IE quirks
    if(status === "<p>Available</p>" || status === "<P>Available</P>" )
    addBooking(id);
    else
    removeBooking(id);
    //alert(id);
    return true;
    }
    
    

    请注意,之前的代码检查了div元素的innerHTML,并检查它是否为<p>Available</p>。如果是,这意味着座位是可用的,我们可以继续预订座位。如果不是,座位已被预订,点击div元素将导致取消座位的预订。

    带着这个想法,我们需要再写两个函数,以帮助我们进行座位预订和取消预订。

  12. 我们现在将创建两个更多的函数,用于预订或取消座位的预订。在cinema.js前添加以下代码:

    var counterNumReservations = 0;
    function addBooking(id) {
    // add 1 to counterNumReservations when a user clicks on the seating
    // alert("addBooking");
    document.getElementById(id).style.backgroundColor = "#000000";
    document.getElementById(id).style.color = "#ffffff";
    document.getElementById(id).innerHTML = "<p>Booked!</p>";
    counterNumReservations = counterNumReservations + 1;
    document.getElementById("tickets").innerHTML = "<p>You have booked " + counterNumReservations + " tickets</p>">";
    return true;
    }
    function removeBooking(id) {
    // minus 1 from counterNumReservations when a user clicks on a seating that is already booked
    // alert("removeBooking");
    document.getElementById(id).style.backgroundColor = "#e5791e";
    document.getElementById(id).style.color = "#000000";
    document.getElementById(id).innerHTML = "<p>Available</p>";
    counterNumReservations = counterNumReservations - 1;
    document.getElementById("tickets").innerHTML = "<p>You have booked " + counterNumReservations + " tickets</p>">";
    return true;
    }
    
    

    我们使用了一个全局变量来跟踪预订的票数或座位数。之前的函数所做的就是它们将增加或减少(如适当)counterNumReservations,同时改变div元素的内容,以反映预订过程的状态。在这种情况下,被预订的座位将是黑色的。

    现在,保存你的文件,点击座位。你应该能够收到关于预订过程的视觉反馈。

    我们将转移到表单处理机制。

  13. 表单处理机制基本上处理以下内容:计算总消费、总餐量、用户支付的金额、找零(如有)、以及其他可能的错误或条件,如是否支付了足够的金额、是否使用了百元大钞等。有了这个思路,我们将创建以下函数:

    function checkForm(){
    var mealPrice;
    var special;
    var hundred;
    var change;
    var ticketPrice
    if(calculateMealQty() == 1 && checkHundred() == 1 && checkSpecial() == 1 && checkMoney() == 1) {
    alert("passed! for checkForm");
    mealPrice = calculateMealPrice();
    special = specialOffer();
    ticketPrice = calculateTicketPrice();
    change = parseInt(amountReceived()) - parseInt((mealPrice + special + ticketPrice));
    alert(change);
    success(change);
    }
    else
    alert("there was something wrong with your order.");
    return false;
    }
    
    

    为了创建模块化的代码,我们将功能划分为单独的函数。例如,success()failure()用于创建 HTML 内容,显示预订过程的状态。

    同样地,注意我们将需要为计算餐量、检查总消费金额等创建其他函数。这些函数是基于我们从第一章第五章所学习的内容创建的,所以我将快速进行。现在,让我们创建这些函数。

  14. 我们现在将为计算餐量、总餐价、总票价等创建各种函数。我们从计算餐量开始:

    function calculateMealQty() {
    var total = parseInt(document.foodForm.hotdogQty.value) + parseInt(document.foodForm.popcornQty.value);
    alert("you have ordered " + total + " meals");
    if(total > counterNumReservations) {
    alert("you have ordered too many meals!");
    failure("you have ordered too many meals!");
    return 0;
    }
    else {
    alert("ok proceed!");
    return 1;
    }
    }
    
    

    现在,我们将编写用于计算餐价的函数:

    function calculateMealPrice() {
    // add up total price
    var price = 6*parseInt(document.foodForm.hotdogQty.value) + (4*parseInt(document.foodForm.popcornQty.value));
    alert("meal price is " + price);
    return price;
    }
    
    

    接下来是用于计算票价的函数:

    function calculateTicketPrice() {
    var price = counterNumReservations * 10;
    alert("ticket price is " + price);
    return price;
    }
    
    

    我们现在将编写用于计算用户在天行者套餐上花费的函数:

    function specialOffer() {
    // for more ordering offers
    var skywalker = 10 * parseInt(document.foodForm.skywalker.value);
    alert("skywalker price is " + skywalker);
    return skywalker;
    }
    
    

    完成这一步后,我们将编写一个小函数来核对收到的金额:

    function amountReceived() {
    var amount = parseInt(document.foodForm.hundred.value);
    alert("I received "+ amount);
    return amount;
    }
    
    

    既然我们已经完成了大部分计算的功能函数,是时候编写用于检查用户是否点了过多的天行者套餐的函数了:

    function checkSpecial() {
    if(parseInt(document.foodForm.skywalker.value) > (parseInt(document.foodForm.hotdogQty.value) + parseInt(document.foodForm.popcornQty.value))){
    alert("you have ordered too many sky walker");
    failure("you have ordered too many sky walker");
    return 0;
    }
    else {
    return 1;
    }
    }
    
    

    完成上一步后,是时候检查用户是否支付了太少的钱:

    function checkMoney() {
    var mealPrice = calculateMealPrice();
    var special = specialOffer();
    var ticketPrice = calculateTicketPrice();
    var change = amountReceived() - (mealPrice + special + ticketPrice);
    alert("checkMoney :" + change);
    if(change < 0) {
    alert("you have paid too little money!");
    failure("you have paid too little money!");
    return 0;
    }
    else
    return 1;
    }
    
    

    正如一开始所规定的,我们还需要检查用户是否使用了百元大钞支付。这样做如下:

    function checkHundred() {
    // see if notes are in hundreds
    var figure = parseInt(document.foodForm.hundred.value);
    if((figure%100) != 0) {
    alert("You did not pay in hundreds!");
    failure("You did not pay in hundreds!");
    return 0;
    }
    // can use error checking here as well
    else {
    alert("checkHundred proceed");
    return 1;
    }
    }
    
    

    最后,创建反映预订状态的 HTML 内容的函数如下:

    function failure(errorMessage) {
    document.getElementById("orderResults").innerHTML = errorMessage;
    }
    function success(change) {
    document.getElementById("orderResults").innerHTML = "Your order was successful.";
    document.getElementById("orderResults").innerHTML += "Your change is " + change + " and you have purchased " + counterNumReservations + " tickets.";
    }
    
    

    哇!编写了不少代码!你可能想保存你的文件并在浏览器中测试你的应用程序。你应该有一个完整运行的应用程序,前提是你正确输入了代码。至此阶段的完整代码可以在cinema-complete文件夹中找到。

    虽然我们刚刚经历了一个繁琐的过程,但这是一个必要的过程。你可能会问为什么我们首先要编写代码而不是立即测试。我的回答是,首先,在真实的企业世界中,我们很可能会先编写代码然后再测试我们编写的代码。其次,如果我要创建一个教程并让你测试代码,而不知道代码是什么,这可能会让你处于困境,因为你可能不知道要测试什么。最重要的是,我们采取的方法允许你练习编程技能并理解代码的内容。

    这将帮助你理解如何在代码中应用trycatch和其他内置异常对象;我们现在就会进行这个操作。

  15. 我们现在将创建一个函数,用于通过使用内置对象抛出和捕获我们的错误。现在,打开cinema.js并在文档顶部添加以下代码:

    function catchError(elementObj) {
    try {
    // some code here
    }
    catch (error) {
    if(error instanceof TypeError){
    alert(error.name);
    alert(error.message);
    return 0;
    }
    else if(error instanceof ReferenceError){
    alert(error.name);
    alert(error.message);
    return 0;
    }
    ... ...
    else if(error instanceof EvalError){
    alert(error.name);
    alert(error.message);
    return 0;
    }
    else {
    alert(error);
    return 0;
    }
    }
    finally {
    alert("ok, all is done!");
    }
    }
    
    

    之前的代码将构成我们的catchError()函数的框架。基本上,这个函数所做的就是捕获错误(或潜在的错误),并测试它是什么类型的错误。在这个例子中,我们将看到这个函数的两个示例用法。

    第一个例子是一个简单的例子,展示我们如何在其他函数中使用catchError(),以便我们可以捕获任何实际或潜在的错误。在第二个例子中,我们将使用catchError()抛出和捕获一个TypeError

    这个阶段的完整代码可以在cinema-error-catching文件夹中找到。请注意,除了添加catchError()addBooking()函数的一些小改动外,大部分代码都没有改变。

  16. 我们将现在尝试通过在try块中添加以下代码片段来捕获一个ReferenceError(如果你使用的是 Internet Explorer,则为TypeError):

    x = elementObj;
    
    

    接下来,在函数addBooking()顶部添加以下代码:

    var test = catchError((counterNumReservations);
    if(test == 0)
    return 0; // stop execution if an error is catched;
    
    

    我们在这里试图做的是,如果我们发现任何错误,就停止 JavaScript 代码的执行。在上面的代码片段中,我们向catchError()传递了一个变量,counterNumReservations,作为示例。

    现在,保存文件并测试程序。程序应该正常工作。然而,如果你现在将try块中的代码更改为:

    var x = testing;
    
    

    在测试未定义的地方,当你执行你的应用程序时,你将收到一个ReferenceError(如果你使用的是 Firefox 浏览器)或TypeError(如果你使用的是 Internet Explorer)。

    之前的简单示例显示,你可以向catchError()函数中传递变量,以检查它是否是你想要的。

    现在,让我们来做一些更难的事情。

  17. 我们将现在尝试抛出和捕获一个TypeError。首先,移除上一个示例中我们所做的更改。我们在这里所做的就是检查传递到addBooking()函数中的对象是否是我们想要的nodeType。通过在addBooking()函数顶部添加以下代码,我们可以实现这一点:

    var test = document.getElementById(id);
    // alert(test.nodeName); // this returns a DIV -> we use nodeName as it has more functionality as compared to tagName
    var test = catchError(test.nodeType);
    // nodeType should return a 1
    if(test == 0)
    return 0; // stop execution if an error is catched;
    
    

请注意上述代码行。我们所做的是获取id元素的nodeType。这个结果将被用作catchError()函数的参数。关于nodeType的一些基本细节,请访问www.w3schools.com/htmldom/dom_nodes_info.asp

现在,移除你对catchError()所做的任何更改,并在try块中添加以下代码:

var y = elementObj;
// var correct is the type of element we need.
var correct = document.getElementById("a1").nodeType;
alert("Correct nodeType is: " + correct);
var wrong = 9; // 9 represents type Document
if(y != correct){
throw new TypeError("This is wrong!");
}

请注意,我们通过检查结果整数来测试nodeType。任何不正确的东西(correct变量是 1)都会导致错误,如if语句块所示。

保存文件,然后运行你的示例。你应该首先收到一个警告框,告诉你正确的 nodeType 是 1,然后是消息TypeError。接下来,你会看到消息这是错误的(这是一个个性化消息)和最后的消息好的,一切都完成了,表示catchError()函数的结束。

我们所做的是针对不同的错误类型抛出自定义错误。在我们的案例中,我们想要确保我们传递了正确的nodeType。否则,这是一个错误,我们可以抛出自定义错误。

有了这些,我们将结束这个示例。

有勇气尝试的英雄——使用 catchError 函数检查输入

既然你已经覆盖了不少代码并获得了一些新知识,你可能想尝试一下:使用catchError()函数来检查用户输入的正确性。你会怎么进行呢?以下是一些帮你开始的想法:

  • 你可能想确保输入的值在传递给其他函数之前经过catchError()

  • 你会在其他函数中实现catchError()吗?还是输入时立即传递给catchError()的值,然后传递给其他函数?

总结

在本章中我们已经覆盖了不少概念。最重要的是使用内置对象通过 JavaScript 的异常处理机制,以及这些对象与try, catchfinally语句一起使用。然后我们尝试将这些概念应用到我们创建的电影票预订应用程序中。

我们还学习了以下主题:

  • 当使用脚本一起时发生的问题,如名称冲突和组合事件处理程序以使代码更加紧凑。

  • 为什么我们需要使用 JavaScript 库,以及需要考虑的问题,如文档的可用性、教程、插件和测试套件。

  • 我们如何利用像 Selenium 这样的工具来测试库的 GUI 和小部件插件(这些将在第八章中详细介绍)。

  • 我们如何可以编写错误消息,或者我们自己的消息,到控制台日志。

  • 如何通过使用 JavaScript 内置对象进行异常处理,并使用这些对象与try, catchfinally语句一起使用。

  • 如何在示例应用程序中使用 JavaScript 的异常处理机制。

到目前为止,我们已经使用手动方式测试我们的代码,尽管现在使用更先进的测试方法。在下一章中,我们将学习如何使用不同的调试工具来使调试更容易,这将是测试的一部分。这将包括使用如 IE8 开发者工具、Firefox 的 Firebug 扩展、Google Chrome 网络浏览器检查器以及 JavaScript 调试器等工具。

这些工具之所以强大,是因为它们允许我们以一种不那么侵扰的方式进行测试;例如,我们通常无需使用alert(),因为我们可以将这些工具的内置控制台作为日志输出窗口。这能节省大量时间,并使我们的测试过程更加顺畅。我们将在接下来的课程中学习这些不同的调试工具。

第七章:调试工具

在本章中,我们将学习一些可以使我们的生活更轻松的调试工具。我们将使用市场上主要浏览器(如 Internet Explorer、Firefox、Google Chrome 和 Safari)提供的调试工具。

我明白互联网上有详尽的文档,因此你可以在这一章期待的是我会非常简要地介绍一下特性,然后通过一个简单的例子说明如何利用调试功能让生活变得更轻松。

通常,你会了解到每个浏览器中提到的调试工具的以下主题:

  • 获取调试工具的位置和方式

  • 如何使用工具调试 HTML、CSS 和 JavaScript

  • 高级调试,如设置断点和观察变量

  • 如何使用调试工具进行性能分析

那么让我们开始吧。

IE 8 开发者工具(以及为 IE6 和 7 设计的开发者工具栏插件)

本节我们将重点介绍 Internet Explorer 8 的开发者工具栏。

注意

如果你正在使用 Internet Explorer 6 或 7,以下是你如何可以为 Internet Explorer 6 或 7 安装开发者工具栏的方法。

你需要访问 www.microsoft.com/downloads/details.aspx?familyid=e59c3964-672d-4511-bb3e-2d5e1db91038&displaylang=en 并下载开发者工具栏。如果你阅读的是这本书的纸质版,无法复制和粘贴上述 URL,那么就谷歌“IE6 或 IE7 的开发者工具栏”,你应该会来到你需要的下载页面。

请注意,上述网页上的工具栏与 Internet Explorer 8 不兼容。

如果你不想单独安装开发者工具,我建议你安装 Internet Explorer 8;IE8 预装了他们的开发者工具,与为 IE6 或 IE7 单独安装开发者工具相比,它更为方便。

从这一刻起,我将涵盖使用 Internet Explorer 8 内置工具的开发者工具。

使用 IE 开发者工具

因为我们已经获得了插件,现在是时候通过一个例子来了解它是如何工作的了。我为此章节准备了source code文件夹中的示例代码;转到文件夹并在浏览器中打开名为IE-sample.html的文件。这个示例基本上要求你输入两个数字,然后对这两个数字进行加法、减法、乘法和除法。结果将显示在表单右侧的框中。

现在给它一个测试,完成后我们开始学习如何使用 IE8 的调试工具调试这个网页。

打开

我假设文件仍然在你的浏览器中打开。如果不是,请在浏览器中打开IE-sample.html(当然,使用 Internet Explorer)。一旦示例打开,您需要打开调试工具。您可以导航到工具,然后点击开发者工具。或者,您可以通过按键盘上的Shift + F12来访问调试工具。

用户界面的简要介绍

在我们进入实际的调试过程之前,我将简要关注 IE 调试工具的关键特性。

用户界面简介

  1. HTML:HTML标签显示您当前查看的脚本或网页的源代码。当你点击这个标签时,你会在右侧得到相关标签,如图所示。

  2. CSS:CSS标签显示了当前您正在查看的网页所使用的 CSS 样式表。

  3. 脚本:脚本标签是您执行 JavaScript 调试任务的地方。当你点击这个标签时,你会得到一些与调试任务相关的特性,比如控制台、断点、局部变量监视

  4. ProfilerProfiler标签显示了网页的剖析数据,如果您选择进行剖析的话。

IE 调试工具的基本调试

通常,我们可以用 IE 的调试工具两种方式:

  • 在一个单独的窗口中

  • 在浏览器内停靠

您可以通过点击调试窗口右上角的钉子图标将调试工具停靠在浏览器内。在我的情况下,我更喜欢将其停靠在我的浏览器中,这样我屏幕上就有更多的查看空间。而且,由于示例代码相当小,将其停靠在您的浏览器上应该就足够了。

通常,调试面板的左侧是 IE 团队所说的主要内容面板。这个面板显示了网页的文档对象模型;这个面板让我们从程序化的角度 overview 网页的源代码。

以下是一些使用 IE 调试工具进行调试的基本知识。

行动时间——使用 IE8 开发者工具调试 HTML

  1. 要检查网页的 HTML 元素,请点击主要内容面板中的HTML标签。我们可以点击位于主要内容面板第一行上的+图标。

  2. 一旦你点击了+图标,你应该会看到<head><body><html>标签展开后立即出现;再次点击它们将会显示<head><body>标签包含的其他元素。例如,让我们点击具有id wrapdiv元素。

  3. 点击div元素后,您可以立即看到与wrap相关的各种属性,如其父元素、继承的 HTML 和 CSS,以及属于wrap的 CSS 属性。

    我们可以通过点击调试窗口中属性面板上的各种命令来进一步检查:

    • 样式样式命令通过提供适用于选定元素的的所有规则列表,改善了 CSS 的调试。规则按优先级顺序显示;所以最后应用的规则出现在底部,任何被另一个属性覆盖的属性都会被划掉,让你能快速理解 CSS 规则如何影响当前元素,而不需要手动匹配选择器。您可以通过切换规则旁边的复选框,快速开启或关闭 CSS 规则,动作将立即在您的页面上生效。在我们这个案例中,你会看到我们的#wrap元素有两个继承:body 和 HTML。你可以通过点击属性值并输入#eee,将颜色属性更改为#eee。完成后,按下Enter,您可以看到变化立即生效。

    • 追踪样式:这个命令包含了与样式相同的信息,只不过它按照属性对样式进行了分组。如果你正在寻找关于某个特定属性的信息,切换到追踪样式命令。只需找到你感兴趣的属性,点击加号(+)图标,就能看到设置该属性的所有规则列表——再次按照优先级顺序排列。

    • 布局布局命令提供了盒模型信息,如元素的偏移、高度和内边距。在调试元素的定位时使用这个命令。

    • 属性属性命令允许你查看选定元素的的所有定义属性。这个命令还允许你编辑、添加或删除选定元素的属性。

行动时间——使用 IE8 开发者工具调试 CSS

现在让我们将注意力重新转移到主要内容面板

  1. 点击CSS标签,以便我们可以访问所有的 CSS(外部或内部)文件。一旦你这样做,你会看到我们网页上使用的相同的 CSS。

  2. 现在我想要你点击BODY中的一项样式属性,比如color,并将其更改为#ccc。你将立即看到我们网页上文本颜色的变化。

刚才发生了什么?

我们刚刚完成了调试的基本操作,这为我们提供了在使用 IE 的调试工具调试 JavaScript 之前所需的知识。

我们上面执行的简单例子,就是我们所说的实时编辑源;我们可以编辑任何 HTML 或 CSS 属性,而无需回到我们的源代码,更改它,保存它,然后在浏览器中重新加载文件。在我看来,这样的功能是我们使用调试工具的关键原因之一。

然而,请注意,你所做的更改只存在于 Internet Explorer 对网站的内部表示中。这意味着刷新页面或导航离开会恢复原始网站。

然而,有些情况下你可能想保存更改,为了做到这一点,你可以点击保存按钮,将当前的 HTML 或 CSS 保存到文件中。这是为了防止意外覆盖你的原始源代码。

让我们继续学习 JavaScript。

调试 JavaScript

现在该学习如何使用 IE 的开发者工具调试 JavaScript 了。

行动时间——使用 IE8 开发者工具进行更多 JavaScript 调试

以下是开始调试的步骤:

  1. 点击在主内容面板中找到的脚本标签。

  2. 接下来,点击写着开始调试的按钮。

  3. 点击开始调试后,你将拥有一个完整调试器的所有功能。

    如果你希望在调试过程中的任何一点停止调试,请点击停止调试

    现在让我们看看我们可以使用调试工具的各种功能做什么。让我们从第一个开始:设置断点。

    我们通常设置断点以控制执行。在前几章中,我们通常依赖于alert()或其他函数来控制程序执行。

    然而,通过使用 IE 的调试工具,你只需设置断点就可以控制程序执行;在这个过程中,你可以节省很多alert(),或其他自定义函数。

    现在,让我们通过使用断点来控制执行。

  4. 你可以通过右键点击行号并选择插入断点来设置断点。在我们的案例中,让我们去包含buildContent(answerB, "minus");的那一行,右键点击它,然后选择插入断点

  5. 现在尝试在浏览器中输入一些值到输入字段中。你会看到,动态内容不会在右侧的黑色正方形上创建。这是因为代码执行在buildContent(answerB, "minus");处停止了。

    我们通常使用断点来检查变量;我们需要知道我们的代码是否以我们希望的方式执行,以确保它是正确的。现在,让我们看看如何设置断点和检查变量。

    我们通过使用监视功能来检查变量。继续上一个示例,我们可以通过点击监视窗格来使用监视功能。另外,你也可以点击本地变量,它提供了类似的功能,允许我们看到一组变量。这可以用来监视自定义变量列表,也可以检查变量的当前状态。

    要执行我们刚刚描述的操作,我们需要执行以下步骤:

  6. 点击开始调试,并为包含var answerA = add(numberA, number);和buildContent(answerA, "add");的行设置断点。

  7. 现在,运行示例,分别为输入字段输入53。然后点击提交

  8. 现在转到你的 调试器 面板,点击 局部变量。你会看到以下截图的输出:行动时间—使用 IE8 开发者工具进行更多 JavaScript 调试

    • 这个面板显示的是与设置断点的函数相关的局部变量列表

      注意到 answerA, answerB, answerC, 和 answerD 目前都是未定义的,因为我们还没有为它们执行任何计算,因为我们已经在 var answerA = add(numberA, number) 处设置了断点。

  9. 接下来,点击 监视。现在你可以添加你想要检查的变量。你可以通过输入变量的名称来实现这一点。输入 answerAnumberB,然后按 Enter。你会看到一个类似于以下截图的屏幕:行动时间—使用 IE8 开发者工具进行更多 JavaScript 调试

    • 如前所述,answerA 目前还没有定义,因为它还没有被我们的程序计算出来。另外,因为我们已经为 numberAnumberB 输入了值,所以 numberB 自然是有定义的。

    注意

    你注意到我们的输入类型不正确了吗?这是因为我们使用了 .value 方法来访问输入字段的值。作为一个优秀的 JavaScript 程序员,我们应该使用 parseFloat() 将值转换为浮点数。

    • 我们可以在调试模式下继续执行代码(在调试窗口中)通过执行 Continue、Step In、Step Over 和 Step Out 操作。

      我们将快速进入示例,看看 Continue、Step In、Step Over 和 Step Out 是如何工作的。继上面的例子继续:

  10. 点击绿色的 Continue 按钮,它看起来像一个 "播放" 按钮。立即,你会看到代码将执行到下一个断点。这意味着之前未定义的变量现在将被定义。如果你点击 局部变量,你会看到类似于下一个截图的输出:行动时间—使用 IE8 开发者工具进行更多 JavaScript 调试

  11. 点击 监视,你会看到一个类似于下一个截图的屏幕:行动时间—使用 IE8 开发者工具进行更多 JavaScript 调试

    这意味着 Continue 的效果是执行从第一个断点到最后一个断点的代码。如果没有第二个断点,代码将执行到末尾。

    你可能想尝试 Step In、Step Over 和 Step Out。

    通常,它们就是这样做的:

    • 步入(Step In):这会跟踪代码的执行。例如,您可以执行上述示例中的步骤,只是点击步入而不是继续。您会注意到,您实际上正在跟踪代码。接下来,您可以查看局部变量监视窗口,您会注意到 previously-undefined 变量将在代码执行过程中被定义。

    • 步过(Step Over):这会直接跳到下一行代码,而不是像步入(Step In)那样跳进其他函数。

    • 步出(Step Out):这会“步出”当前断点,直到下一个断点。它与继续类似。如果您在步入(Step In)之后使用步出(Step Out),它将继续到下一个断点(如果有)。

      现在让我们继续了解下一个有用功能,即在遇到错误时停止代码。

      要启用此功能,您需要点击在错误时中断按钮,或者您可以简单地按Ctrl + Shift + E。一旦您开始调试,此功能应该会自动启用。

      这个功能的作用是如果在执行代码时发现任何错误,就停止执行。例如,取消注释说:buildContent(noSuchThing, "add"); 这行代码,并在调试模式下运行代码。您将在调试窗口的控制台中看到以下屏幕截图:

    行动时间—使用 IE8 开发者工具进行更多 JavaScript 调试

    使用调试器的一个酷炫之处在于,它可以帮助您在运行时发现错误,这样您就可以快速识别您犯的错误。

    现在我们已经对 IE 调试工具的一些更高级功能有了基本的了解和认识,是时候关注我们 JavaScript 程序的性能了。

    Internet Explorer 调试工具附带一个内置分析器,名为 JavaScript 分析器,通过提高性能帮助您的网站达到一个新的水平。

    通常,分析器会为您提供您网站的 JavaScript 方法以及内置 JavaScript 函数中花费的时间数据。这就是如何使用这个功能。

  12. 使用浏览器中的示例源代码,打开开发工具并点击分析标签。然后点击开始分析,以开始一个会话。

  13. 打开您的浏览器,输入一些示例值。例如,我输入了53。输入示例值后,转到您的调试窗口并点击停止分析。将显示以下屏幕截图的类似屏幕:行动时间—使用 IE8 开发者工具进行更多 JavaScript 调试

  • 请注意,Jscript Profiler 包括每个函数花费的时间(每个函数的名称也给出)。每个函数的使用次数也给出,如计数栏所示。您可能注意到我们每个函数的时间都是 0.00;这是因为我们的示例程序相对较小,所以所需时间接近零。

刚才发生了什么?

我们刚刚介绍了 Internet Explorer 的开发者工具,它帮助我们从更流畅的方式执行调试任务。

以防你想知道手动调试与使用调试工具之间的区别,我可以根据经验 safely tell you that the amount of time saved by using a debugging tool alone is a good enough reason for us to use debugging tools.

你可能知道,在为 Internet Explorer 开发时会有各种怪癖;使用其内置的调试工具可以帮助你更有效地找出这些怪癖。

带着这个想法,让我们继续介绍下一个工具。

Safari 或 Google Chrome 网络检查器和 JavaScript 调试器

在本节中,我们将学习 Safari 和 Google Chrome 中使用的 JavaScript 调试器。尽管两者有相似的代码基础,但存在微妙的差异,因此让我们先了解 Safari 和 Google Chrome 之间的区别。

Safari 与 Google Chrome 之间的差异

如果你是苹果粉丝,你无疑会认为 Safari 可能是地球上最好的浏览器。然而,Google Chrome 和 Safari 都源自一个名为 WebKit 的开源项目。

Safari 和 Google Chrome 使用不同的 JavaScript 引擎。从Safari 4.0开始,Safari 使用了一种名为 SquirrelFish 的新 JavaScript 引擎。Google Chrome 使用 V8 JavaScript 引擎。

然而,在使用 Google Chrome 和 Safari 提供的内置调试器进行 JavaScript 调试时,两者几乎完全相同,甚至界面也很相似。

在接下来的部分,我将使用 Chrome 来解释示例。

Chrome 中的调试

对于 Google Chrome,我们无需下载任何外部工具即可执行调试任务。调试工具随浏览器本身一起提供。所以现在,我们将看到如何使用sample.html开始我们的调试会话。

打开和启用:我们首先需要在 Chrome 中打开和启用调试。在 Google Chrome 中,您可以使用两个工具来帮助您为 Web 应用程序执行调试任务:网络检查器和 JavaScript 调试器。

网络检查器:谷歌浏览器的网络检查器主要用于检查您的 HTML 和 CSS 元素。要使用网络检查器,只需在网页上的任何组件上右键单击即可启动网络检查器。您将能够看到您点击的组件的相关元素和资源,包括 DOM 的层次视图和一个 javascript 控制台。要使用网络检查器,请在谷歌浏览器中打开example.html。将鼠标移至侧边栏上写着列 2的地方。在列 2上右键单击,您将看到一个弹出菜单。选择检查元素。一个新的窗口被打开。这就是网络检查器。

现在我们将进入 javascript 调试器。

javascript 调试器:要使用谷歌浏览器的 javascript 调试器,选择页面菜单图标,该图标位于URL输入字段的右侧,然后单击开发者 | 调试 javascript 控制台。你也可以通过按下Ctrl + Shift + J 来启动 javascript 调试器。如果您使用的是 Safari,您需要首先通过点击位于页面图标右侧的显示设置图标来启用开发者菜单,选择偏好设置,然后单击高级。在此屏幕上,启用在菜单栏中显示开发菜单选项。然后,您可以通过点击页面图标并选择开发者开始调试 javascript来访问这个菜单栏。这个界面与我们在谷歌浏览器中看到的基本相同。

请注意,打开 javascript 调试器后,您将打开与网络检查器中看到的相同的窗口。然而,现在的默认标签页是脚本。在这个标签页中,您可以查看前一小节中提到的我们例子的源代码。

这是我们将要用来执行我们的调试任务的主屏幕。在接下来的会话中,我们将开始做一些基本的调试,让我们的手指稍微脏一些。

如果您已经完成了我们在使用 Internet Explorer 开发者工具的调试会话,您将要执行的大部分任务和行动在概念上应该是相似的。

我们刚刚探索了打开和开始网络检查器和 javascript 调试器的基本操作。现在让我们简要介绍一下用户界面,以便让您跟上进度。

用户界面的简要介绍

以下是对您如何在谷歌浏览器调试工具中找到关键功能的简要说明,如图所示:

用户界面的简要介绍

  1. 元素:元素标签页显示您当前正在显示的脚本或网页的源代码。当你点击元素图标时,你会得到一些相关标签页(如前一个屏幕快照中所示的计算样式)。

  2. 脚本:脚本标签是你将执行你的 JavaScript 调试任务的地方。当你点击脚本图标时,你会得到一个与调试相关的功能的列表,比如监视表达式、调用栈、作用域变量断点

  3. 配置文件:配置文件标签显示了你选择进行配置时网页的配置数据。

行动时间—使用 Chrome 进行调试

  1. 我们现在将学习如何使用控制台并利用断点来简化我们的调试会话。我们从控制台开始。

  2. 控制台基本上显示了你在调试会话中做了什么。我们首先看到如何访问控制台。

  3. 首先,在你的 Google Chrome 浏览器中打开sample.html文件,如果你还没有这么做的话。一旦你完成了这个,按照以下步骤进行操作,以显示控制台:

  4. 打开你的 JavaScript 调试器,通过选择页面菜单图标 行动时间—使用 Chrome 进行调试,该图标可以在URL输入字段的右侧找到,然后前往开发者 | 调试 JavaScript。你也可以按Ctrl + Shift + J 启动 JavaScript 调试器。

  5. 完成第 4 步后,点击控制台图标,该图标可以在 JavaScript 调试器的底部找到。完成后,你会看到一个类似于以下屏幕截图的屏幕:行动时间—使用 Chrome 进行调试

    现在我们已经打开了控制台,我们将继续学习调试器的最常用功能。在这个过程中,你也将看到控制台如何记录我们的行动。

    我们现在将继续学习如何设置断点。

    如前所述,设置断点是调试过程的重要部分。所以我们实际调试过程的起点就是设置一个断点。

  6. 在 Google Chrome 中打开sample.html,开始你的调试器,并确保你处于脚本标签。你可以通过点击我们想要设置断点的行号来设置断点。让我们尝试点击包含buildContent(answerB, "minus")的行;然后点击行号。你会看到一个类似于以下屏幕截图的屏幕:行动时间—使用 Chrome 进行调试

    注意现在第 130 行有一个蓝色箭头(突出显示的行),在源代码面板的右侧,你会看到断点面板。现在它包含了我们刚刚设置的断点。

  7. 运行示例,在浏览器中输入一些值到输入字段中。我希望你在第一个输入字段中输入4,在第二个输入字段中输入3。然后点击提交。你会看到动态内容不会在右边的黑色正方形中创建。这是因为代码已经停止在buildContent(answerB, "minus");.

  8. 现在回到你的调试器,你会看到你的源代码右侧下一个屏幕截图,类似于下面的示例:行动时间—使用 Chrome 进行调试

    你会看到调用栈作用域变量现在正在用值填充,而监视表达式没有。我们将在接下来的几段中详细介绍这些内容。但现在,我们首先从调用栈作用域变量开始。

    正如上一个屏幕截图所示,当我们执行程序时,调用栈作用域变量现在正在用值填充。一般来说,调用栈包含了正在执行的函数的序列,而作用域变量显示了可用直到断点或执行结束的变量的值。

    当我们点击提交按钮时,会发生以下情况:首先执行的是formSubmit()函数,在这个函数内部,计算了var answerAvar answerBvar answerCvar answerD。这就是作用域变量如何用我们的值进行填充的。

    通常,这就是 Google Chrome 中调用栈作用域变量的工作方式。现在,让我们关注一下我们心中一直存在的问题,监视表达式

    在解释监视表达式之前,最好我们先看看它如何行动。回到上一个屏幕截图,你会注意到此时监视表达式还没有被填充。我们现在尝试通过执行以下步骤来填充监视表达式:

  9. 刷新你的浏览器,回到你的调试器。

  10. 监视表达式面板上,点击添加,并输入以下内容:document.sampleform.firstnumber.valuedocument.getElementById("dynamic")

  11. 回到你的浏览器,输入43作为输入值。点击提交。假设你没有在上一个部分中移除我们设置的断点,你将在监视表达式面板上看到下一个屏幕截图中的信息:行动时间—使用 Chrome 进行调试

    监视表达式现在被填充了。document.sampleform.firstnumber.valuedocument.getElementById("dynamic")是从我们的 JavaScript 程序中复制的代码行。如果你追踪代码,你会注意到document.sampleform.firstnumber.value用于推导第一个输入字段的值,而document.getElementById("dynamic")用于引用div元素。

    截至目前,你已经理解了监视表达式用于检查表达式。你只需要添加你想要看到的表达式,在执行程序后,你将看到该表达式的意思、指向的内容,或者它当前的值。这允许你在程序执行时监视表达式的更新。你不需要完成程序就能看到变量的值。

    现在该转到调试窗口中的继续(Continue)、步进(Step In)、步过(Step Over)和步出(Step Out)操作了。

    这里的概念与我们之前在 Internet Explorer 开发者工具中看到的内容非常相似。如果你想知道执行这些操作的按钮在哪里,你可以发现在观察表达式(Watch Expression)面板上方。以下是每个操作的相关概念:

    • 步进(Step In):这会在代码执行时跟踪代码。假设你仍然在我们的示例中,你可以点击带有向下箭头的图标。你会看到你实际上正在跟踪代码。继续点击步进(Step In),你会看到作用域变量(Scope Variables)调用栈(Call Stack)中的值发生变化。这是因为代码的不同点会有各种变量或表达式的不同值。

    • 步出(Step Out):这仅仅是移动到代码的下一行,而不跳入其他函数,与步进(Step In)类似。

    • 步过(Step Over):这仅仅是移动到代码的下一行。

      在本节最后,我们将重点介绍如何暂停在异常处。这意味着程序将在遇到问题的那行停止。我们将做什么来看它的实际作用:

  12. 打开sample.html文件,在编辑器中搜索buildContent (noSuchThing, "add")这一行;取消注释它。保存文件并在 Google Chrome 中打开。

  13. 打开调试器。点击带有暂停标志的按钮 行动时间—使用 Chrome 进行调试,该按钮位于显示控制台(Show Console)按钮的右侧。这将在遇到错误时使调试器停止执行。

  14. 像往常一样,为输入字段输入一些值。点击提交。完成后,回到你的调试器,你会看到以下屏幕截图中的信息:行动时间—使用 Chrome 进行调试

  • 如果你启用了暂停异常功能,通常你会得到这种视觉信息。

刚才发生了什么?

我们已经介绍了使用 Google Chrome 的基础知识。如果你遵循了之前的教程,你将学会如何使用控制台、设置、步进、步出和越过断点、在异常时暂停以及观察变量。

通过使用上述功能的组合,你将能够快速嗅出并发现不意的 JavaScript 错误。你甚至可以跟踪你的 JavaScript 代码是如何执行的。

在接下来的几节中,你将开始注意到大多数工具都有非常相似的功能,尽管有些可能有不同术语表示相同的功能。

现在该转向另一个工具,即 Opera JavaScript 调试器了。

Opera JavaScript 调试器(Dragonfly)

Opera 的 JavaScript 调试器被称为 Dragonfly。为了使用它,你所需要做的就是下载最新版本的 Opera;Dragonfly 已经包含在最新版本的 Opera 中。

既然你已经安装了必要的软件,是时候进行调试任务了。

使用 Dragonfly

我们首先从我们的example.html文件开始。在 Opera 浏览器中打开这个文件。现在我们将了解如何启动 Dragonfly。

启动 Dragonfly

要访问 Dragonfly,请转到菜单选项工具。选择高级,然后点击开发者工具。一旦你这样做,Dragonfly 就会出现。像往常一样,我们将从工具的用户界面简介开始。

用户界面简介

以下是我们将使用的一些最重要功能的简要概述,如图所示:

用户界面简介

  1. DOM: 这个标签页用于检查 HTML 和 CSS 元素

  2. 脚本: 当我们调试 JavaScript 时使用此标签页

  3. 错误控制台: 这个标签页在调试 JavaScript 时显示各种错误信息。

我们现在开始调试example.html

行动时间—使用 Opera Dragonfly 进行调试

  1. 在本节中,我们将学习如何使用 Dragonfly 的调试功能。我们将从设置断点开始。

    这就是我们在 Dragonfly 中设置断点的方法:

  2. 在 Opera 中打开sample.html,启动 Dragonfly,然后点击脚本标签页。您可以通过点击我们想要设置断点的行号来设置断点。让我们尝试转到包含buildContent(answerB, "minus");的行,然后点击行号。

  3. 打开你的浏览器,执行example.html。输入53作为输入值。点击提交按钮。像往常一样,你不会看到任何动态生成的内容。程序的断点在包含buildContent(answerB, "minus");的位置。

  4. 现在回到龙 fly,你会注意到调用堆栈检查面板现在已填充。如果你输入与我相同的值,你应该会看到与下一个截图相似的值:行动时间—使用 Opera Dragonfly 进行调试

  • 检查调用堆栈中显示的值是在断点之前的计算和执行的值和函数。

刚才发生了什么?

我们刚刚使用 Dragonfly 设置了一个断点,当我们执行我们的 JavaScript 程序时,我们看到了 Dragonfly 的各种字段是如何填充的。现在我们将详细介绍每个字段。

检查和调用堆栈

如前一个截图所示,当我们执行程序时,调用堆栈检查会填充值。一般来说,调用堆栈显示特定函数调用时的运行时环境性质—已经调用了什么,以及以什么顺序调用。检查面板列出了当前调用的所有属性值及其他信息。堆栈帧是调用堆栈中的特定部分。检查的概念与在 Google Chrome 中看到的作用域变量相似。

线程日志

线程日志:这个面板显示了穿过你当前正在调试的脚本的各个线程的详细信息。

现在我们将继续深入了解龙翼的功能。

继续、步入、单步跳过、单步跳出和错误停止

我们还可以在调试代码时执行通常的继续、步入、单步跳过和单步跳出的任务。下面是一个截图,显示我们如何找到前面提到的功能:

继续、步入、单步跳过、单步跳出和错误停止

  1. 继续:在停止在断点后继续当前选中的脚本。如果有的话,这将继续到下一个断点,或者它将继续到脚本的末尾。

  2. 步入:这允许你在包含断点的当前函数之后的下一个函数中步入。它有效地追踪代码的执行。假设你仍然在我们的示例中,你可以点击带有向下箭头的步入图标。你会发现你实际上正在追踪代码。继续点击步入,你会看到检查调用栈中的值发生变化。这是因为代码的不同点会有各种变量或表达式的不同值。

  3. 单步跳过:这允许你在设置断点的行之后跳到下一行——你可以多次使用这个功能来跟随脚本的执行路径。

  4. 单步跳出:这将使你跳出函数。

  5. 错误停止:这允许你在遇到错误时停止执行你的脚本。为了看到这个功能,请在你的编辑器中打开example.html文件,并查找写着buildContent(noSuchThing, "add")的行;然后取消注释。保存文件,然后再次使用 Opera 打开它。打开龙翼,点击图标。现在在 Opera 中执行你的程序并输入一些示例值。完成后,你将在龙翼中看到以下截图:继续、步入、单步跳过、单步跳出和错误停止

注意,在46行有一个指向右边的黑色箭头。这意味着这行代码有一个错误。

在我们结束龙翼节段之前,我们再来看一个重要的功能:设置功能。

设置

OPERA 的龙翼有一个让我们为我们的调试任务创建不同设置的巧妙功能。这一系列设置很多,所以我不会全部介绍。但我将重点介绍那些对你的调试会话有用的设置。

  • 脚本:在这个面板中,选中窗口后自动重新加载文档是一个巨大的时间节省功能,尤其是当你有多个 JavaScript 文件需要调试时,因为它将帮助你自动重新加载文档。

  • 控制台:此面板允许你在调试会话中控制你想看到的信息。从 XML 到 HTML,你可以启用或禁用消息,以看到最重要的信息。

有了这个,我们将结束 Dragonfly 部分,继续学习 Firefox 和 Venkman 扩展。

Firefox 和 Venkman 扩展

我们知道 Firefox 有很多插件和工具,其中一些是专为网页开发而设计的。在本节中,我们将学习 Mozilla 的 JavaScript 调试器 Venkman 扩展。

使用 Firefox 的 Venkman 扩展

我们将先获得扩展;我们将假设您已经安装了 Firefox。在我的情况下,我正在使用 Firefox 3.6.3。

获得 Venkman JavaScript 调试器扩展

为了获得 Venkman JavaScript 调试器扩展,请前往addons.mozilla.org/en-US/Firefox/addon/216/并点击添加到 Firefox。安装后,Firefox 将提示您重新启动 Firefox 以使更改生效。

打开 Venkman

为了开始调试,让我们在 Firefox 中打开文件example.html。在这里,我们可以现在开始 Venkman。点击工具并选择JavaScript 调试器。如果你使用的是 Firefox 的旧版本,可以通过前往工具 | 网页开发 | JavaScript 调试器菜单来访问它。

现在我们将对 Venkman 的用户界面进行简要介绍。

用户界面的简要介绍

下一张截图显示了 Venkman 扩展的用户界面:

用户界面的简要介绍

  1. 已加载脚本:已加载脚本面板显示了您可以用于调试的脚本列表。加载脚本后,你将在源代码面板中看到它。

  2. 局部变量和观察:局部变量面板显示在执行调试任务时可用的局部变量。如果您点击观察标签,您将看到观察面板。您将使用这个来输入您想要观察的表达式。

  3. 断点和调用堆栈:断点面板允许您添加一系列断点,而调用堆栈面板按顺序显示执行的函数或变量。

  4. 源代码:源代码面板显示您当前正在调试的源代码。

  5. 互动会话:互动会话面板是此调试器的控制台。

现在我们将使用 Venkman 扩展开始调试:

是时候行动了——使用 Firefox 的 Venkman 扩展进行调试

我们将先设置断点,然后再详细说明:

与其他所有调试器一样,我们可以通过以下步骤设置断点:

  1. 首先,在 Firefox 中打开文件example.html

  2. 打开 JavaScript 调试器,调试器窗口将显示出来。

  3. 当你看到调试器窗口时,转到加载脚本面板,你将在其中看到文件example.html。点击它,你将在源代码面板上看到代码被加载。

  4. 设置断点时,点击你想要设置断点的行。例如,我在包含以下代码的行130上设置了断点:buildContent(answer, "minus");你应该会看到类似以下截图的内容:行动时刻—使用 Firefox 的 Venkman 扩展进行调试

刚才发生了什么?

首先要注意的是,在之前的截图中,有一个白色 B在一个红色矩形内。这表示已经设置了一个断点。

在 Venkman 中,有时你会看到一个白色 F在一个黄色盒子内;这表示 Venkman 只能设置一个未来的断点。当你的选择行没有源代码,或者如果该行代码已经被 JavaScript 引擎卸载(顶级代码有时在执行完成后不久就会被卸载)。

未来断点意味着 Venkman 现在无法设置一个硬断点,但如果文件稍后加载,并且在选择的行号有可执行代码,Venkman 将自动设置一个硬断点。

要关注的第二件事是断点面板。这个面板包含了我们在这个调试会话中设置的所有断点。

现在,在我们将要进入以下小节之前,我需要你通过打开浏览器输入我们示例应用程序的输入。在我的案例中,我在第一个和第二个输入框中分别输入了53。完成输入后,点击提交

再次,你会注意到原来空白的面板现在充满了值。我们将在以下小节中介绍这个。

断点或调用栈

在前一个小节中我们已经简要介绍了断点。如果你看看断点面板,你会注意到在那个面板的右侧,有一个名为调用栈的标签页。

点击调用栈,你应该在这个新面板中看到一些数据。假设你已经输入了相同的输入和同样的断点,你会看到一个与下一个截图示例相似的屏幕:

断点或调用栈

通常,调用栈显示了在特定函数调用时的运行环境性质—调用什么,以及调用顺序。在 Venkman 中,它显示函数名、文件名、行号和 pc(程序计数器)。

局部变量和监视器

现在让我们关注局部变量监视器局部变量监视器的面板位于断点调用栈面板之上。如果你一直按照我的指示操作,并且输入完全相同的输入,你应在局部变量面板中看到以下内容:

本地变量和观察

本地变量 面板简单地显示了具有值(由于代码执行)的变量的值,直到断点,或者程序结束,根据它们创建或计算的顺序。

我们想要讨论的下一个面板是 Watches 面板。Watches 面板的作用和我们之前为其他浏览器做的 watch 表达式一样。然而,因为我们还没有为 Watches 面板添加任何内容,所以让我们采取一些行动来看看 Watches 面板是如何工作的:

是时候行动了——使用 Venkman 扩展进行更多调试

在本节中,我们将介绍更多的调试功能,比如观察、停止、继续、单步进入、单步跳过、单步退出、边缘触发和抛出触发。但首先,让我们执行以下步骤,以便看到 Watches 面板的实际作用:

  1. 点击 Watches 标签。

  2. Watches 面板内部右键点击,选择 添加观察

  3. 输入 document.sampleform.firstnumber.value

  4. 重复步骤 2 和 3,这次输入 document.getElementById("dynamic")

    完成后,你会看到以下屏幕截图的输出:

    是时候行动了——使用 Venkman 扩展进行更多调试

    Watches 面板的作用是允许我们添加一个表达式列表,以便我们跟踪这些表达式,并且还能显示这些表达式的值。

    现在让我们来看看停止和继续功能。

    Venkman 提供了一些有用的功能,包括停止和继续。停止功能基本上会在下一个 JavaScript 语句处停止,而继续功能则继续代码的执行。

    你可以让 Venkman 在下一行 JavaScript 语句处停止。

  5. 点击工具栏上较大的红色 X,或者你可以去菜单选择 调试,然后选择 停止

    有时没有执行 JavaScript。如果出现这种情况,你会在工具栏上的 X 上看到省略号(...),菜单项会被勾选。当遇到下一行 JavaScript 时,调试器将停止。你可以通过点击 X 或再次选择 停止 来取消这个操作。

    除了停止和继续功能,Venkman 还提供了标准的单步进入、单步跳过和单步退出功能。

    • 单步执行:这会执行一行 JavaScript 代码,然后停止。你可以通过点击写着 Step Into 的图标来尝试这个功能。如果你多次点击它,你会注意到局部变量在变化,你将能够看到代码被执行的情况,就像你在追踪代码一样。

    • 单步跳过:用于跳过即将到来的函数调用,并在调用返回时将控制权返回给调试器。如果你点击 单步跳过,你会发现新内容正在你的浏览器中创建。对于文件 example.html,假设你从断点点击 单步跳过,你会看到内容是从 buildContent(answer, "minus") 创建的;。

    • 步出:执行直到当前函数调用退出。

      我们将看到如何使用错误触发器和抛出触发器。

      错误触发器用于让 Venkman 在下一个错误处停止,而抛出触发器用于让 Venkman 在下一个异常抛出时停止。

      为了看到它的实际效果,我们将执行以下操作:

  6. 在你的编辑器中打开example.html文件,再次搜索到buildContent(noSuchThing, "add")这一行,并取消注释。保存文件后再次打开,使用 Firefox。

  7. 在 Firefox 中打开文件后,打开 Venkman。

  8. 一旦你打开了 Venkman,点击调试|错误触发器,选择在错误处停止。然后,再次点击调试|抛出触发器,选择在错误处停止

  9. 打开你的浏览器,为输入字段输入任意两个数字——比如说分别是53。点击提交

  10. 返回 Venkman,你会发现buildContent(noSuchThing, "add")这一行被突出显示,在交互式会话(或控制台)面板中,你会看到一个错误信息,写着X 错误。noSuchThing 未定义

    既然我们已经看到了 Venkman 在遇到错误时如何停止我们的程序,现在让我们转到它的剖析功能。

    正如我们在前一章节中提到的,剖析是用来测量脚本的执行时间的。要启用剖析:

  11. 点击工具栏上的剖析按钮。当剖析被启用时,你会在工具栏按钮上看到一个绿色的勾选标记。

  12. 一旦你启用了剖析,打开你的浏览器并输入一些示例值。我还是用53吧。然后点击提交

  13. 回到 Venkman,点击文件,选择另存为配置数据。我已经包含了一个例子,展示了我们刚刚做了什么,并将其保存为data.txt文件。你可以打开这个文件,查看剖析会话的内容。你可以在data.txt文件中通过搜索example.html来找到sample.html的剖析数据。

  14. 完成剖析后,点击剖析再次停止收集数据。

    在剖析被启用时,Venkman 将为每个调用的函数收集调用次数、最大调用持续时间、最小调用持续时间和总调用持续时间。

    你也可以使用清除剖析数据菜单项清除所选脚本的剖析数据。

刚才发生了什么?

我们已经介绍了 Venkman 扩展的各种功能。像停止、继续、步进、步出和断点步进这些功能,在现阶段对你来说应该不再陌生,因为它们与我们之前介绍的工具概念上是相似的。

那么现在让我们转移到最后一个工具,Firebug 扩展程序。

Firefox 和 Firebug 扩展程序

我个人认为 Firebug 扩展无需进一步介绍。它可能是市场上最受欢迎的 Firefox 调试工具之一(如果不是最流行的话)。Firebug 是免费和开源的。

它具有以下功能:

  • 通过在网页上点击和指向来检查和编辑 HTML

  • 调试和分析 JavaScript

  • 快速发现 JavaScript 错误

  • 记录 JavaScript

  • 执行飞行的 JavaScript

Firebug 或许是互联网上最好的文档化调试工具之一。所以我们将查看你可以访问的 URL,以便利用这个免费、开源且强大的调试工具:

总结

我们终于到了本章的结尾。我们已经介绍了可用于我们的调试任务的各个浏览器的特定工具。

具体来说,我们已经介绍了以下主题:

  • 用于 Internet Explorer 的开发者工具

  • Google Chrome 和 Safari 的 JavaScript 调试器和 Web 检查器

  • Opera 的 Dragonfly

  • Firefox 的 Venkman 扩展

  • Firebug 资源

如果你需要更多关于每个特定工具的信息,你可以通过在本书中提到的工具和功能后添加关键词“教程”来使用 Google 搜索。

我们已经介绍了可以帮助你开始调试 JavaScript 应用程序的工具的最重要功能。在我们最后一章中,我们将重点介绍各种测试工具,这些工具可以在你的测试需求不能手动满足时使用。

第八章:测试工具

在最后一章中,我们将介绍一些高级工具,您可以使用它们来测试您的 JavaScript。我们将介绍一些可以帮助您进一步自动化测试和调试任务的工具,并向您展示如何测试您的用户界面。

我理解您有很多选择,因为市面上有很多可供您选择用于测试任务的工具。但我会关注那些通常免费、跨浏览器和跨平台的工具;您是 Safari、IE、Chrome 或其他浏览器的粉丝并不重要。根据w3schools.com/browsers/browsers_stats.asp,大约 30%的网页浏览器使用 Internet Explorer,46%使用 Firefox 浏览器,其余的使用 Chrome、Safari 或 Opera。这意味着您使用的工具将满足这些统计数据。尽管有些应用程序是为只有一个浏览器开发的,但我们学习如何为不同的浏览器编写代码是一个好的实践和学习经验。

更重要的是,我将详细介绍的工具是我个人认为更容易上手的工具;这将帮助您对测试工具有一个大致的了解。

以下工具将详细介绍:

  • Sahi 是一个跨浏览器自动化测试工具。我们将用它来执行 UI 测试。

  • QUnit 是一个 JavaScript 测试套件,可以用来测试几乎任何 JavaScript 代码。我们将用它来执行 JavaScript 代码的自动化测试。

  • JSLitmus,一个用于创建即兴 JavaScript 基准测试的轻量级工具。我们将使用这个工具进行一些基准测试。

除了前面提到的工具,我还将介绍一些重要的测试工具,我认为这些工具对您的日常调试和测试任务很有用。所以,一定要查看这一部分。

Sahi

我们简要讨论了测试由 JavaScript 库提供的用户界面小部件的问题。在本节中,我们将开始测试使用 JavaScript 库小部件构建的用户界面。同样的技术也可以用于测试自定义用户界面。

Sahi 是一个使用 Java 和 JavaScript 的浏览器无关的自动化测试工具。我们将关注这个工具,因为它与浏览器无关,我们不能总是忽视 IE 用户。

Sahi 可以用来执行各种测试任务,但我想要强调的一个功能是它能够记录测试过程并在浏览器中回放。

您将看到使用 Sahi 执行用户界面测试是多么有用。

行动时间—使用 Sahi 进行用户界面测试

我们将向您展示 Sahi 的记录和回放功能,并了解如何使用它来测试由 JavaScript 库(如 jQuery)提供的用户界面小部件。

  1. 我们首先安装 Sahi。访问sahi.co.in并下载最新版本。在我写下这段文字的时候,最新版本是 V3 2010-04-30。下载后,解压到C:驱动器。

  2. 打开 Internet Explorer(我在这篇教程中使用的是 IE8),然后访问jqueryui.com/themeroller/。我们将使用这个用户界面进行演示。

  3. 为了使用 Sahi,我们需要首先导航到C:\sahi_20100430\sahi\bin并查找sahi.bat。点击它,这样我们就可以启动 Sahi。

  4. 现在,是时候设置你的浏览器,以便它能与 Sahi 一起使用。打开你的浏览器,前往工具 | Internet 选项 | 连接,然后点击局域网设置。点击代理服务器并输入以下屏幕截图中的信息:行动时刻—使用 Sahi 进行用户界面测试

    完成操作后,关闭此窗口以及与工具相关的所有其他窗口。

  5. 完成上一步后,让我们回到浏览器中。为了在浏览器中使用 Sahi,你需要按Ctrl + Alt,同时双击网页上的任何元素(jqueryui.com/themeroller/)。你应该看到一个新的窗口,如下下一个屏幕截图所示:行动时刻—使用 Sahi 进行用户界面测试

  6. 如果你看到了上面截图中的窗口,那么你已经正确设置了并启动了 Sahi。现在,让我们来了解它的自动化测试功能,记录和回放功能。

  7. 脚本名称输入字段中输入jquery_testing,然后在显示在前一个屏幕截图中的窗口中点击记录。这将开始记录过程。

  8. 现在,让我们点击几个用户界面元素。在我的情况下,我点击了第二部分,第三部分,打开对话框字体设置。这可以在左侧菜单找到。

  9. 导航到C:\sahi_20100430\sahi\userdata\scripts,你会看到一个名为jquery_testing.sah的文件。用 WordPad 打开这个文件,你将看到我们刚刚创建的行动列表,记录在这个文件中。

  10. 进入 Sahi 窗口,点击停止。现在,我们已经停止了记录过程。

  11. 在 WordPad 中打开jquery_testing.sah,并更改代码,使其如下所示:

    function jquery_testing() {
    _click(_link("Section 2"));
    _click(_link("Section 2"));
    _click(_link("Section 3"));
    _click(_link("Section 3"));
    _click(_link("Open Dialog"));
    _click(_link("Font Settings"));
    }
    jquery_testing();
    
    

    我定义了一个名为jquery_testing()的函数来包含我们创建的行动列表。然后,我把jquery_testing()添加到文件的末尾。这一行是为了在我们激活回放功能时调用这个函数。

  12. 现在让我们进入 Sahi 窗口,点击回放。然后,输入如下下一个屏幕截图中的信息:行动时刻—使用 Sahi 进行用户界面测试

    点击设置,等待页面刷新。

  13. 页面刷新后,点击播放。在浏览器中,我们将看到我们执行的操作按照前面提到的步骤重复进行。您还将在 声明 面板中收到一个 成功 消息,这意味着我们的测试过程是成功的。

刚才发生了什么?

我们刚刚使用 Sahi 完成了一个简单的用户界面测试过程。Sahi 的回放过程和记录功能使我们能够轻松地测试用户界面。

请注意,Sahi 允许我们以视觉方式进行测试。与前面章节中看到的其他手动测试方法相比,除了为回放功能定义一个函数外,并没有太多编码工作。

现在,让我们关注与 Sahi 相关的其他重要且相关的话题。

使用 Sahi 进行更复杂的测试

如本节开头所述,Sahi 可以与任何浏览器一起使用,执行各种任务。它甚至可以用来进行断言测试。

查看sahi.co.in/static/sahi_tutorial.html,了解如何在您的测试过程中使用断言。

注意

在本节结束后,请确保您回到工具 | Internet 选项 | 连接,点击 LAN 设置,取消勾选代理服务器,以便您的浏览器可以像往常一样工作。

QUnit

QUnit 是一个 jQuery 测试套件,但它可以用来测试我们编写的 JavaScript 代码。这意味着代码不必依赖于 jQuery。通常,QUnit 可以用来进行断言测试和异步测试。此外,断言测试有助于预测您代码的返回结果。如果预测失败,那么您的代码中很可能会出错。异步测试简单地指的是同时测试 Ajax 调用或函数。

让我们立即行动来看看它是如何工作的。

是时候进行行动测试 JavaScript with QUnit

在本节中,我们将更深入地了解 QUnit,通过编写一些代码,也学习 QUnit 支持的各种测试。我们将编写正确的测试和错误的测试,以了解它是如何工作的。本节的源代码可以在 source code 文件夹的 qunit 中找到。

  1. 打开您的编辑器,将文件保存为 example.html。在其中输入以下代码:

    <!DOCTYPE html>
    <html>
    <head>
    <title>QUnit Example</title>
    <link rel="stylesheet" href="http://github.com/jquery/qunit/raw/master/qunit/qunit.css" type="text/css" media="screen">
    <script type="text/javascript" src="img/qunit.js"></script> 
    <script type="text/javascript" src="img/codeToBeTested.js"></script>
    <script type="text/javascript" src="img/testCases.js"></script>
    </head>
    <body>
    <h1 id="qunit-header">QUnit Test Results</h1>
    <h2 id="qunit-banner"></h2>
    <div id="qunit-testrunner-toolbar"></div>
    <h2 id="qunit-userAgent"></h2>
    <ol id="qunit-tests"></ol>
    </body>
    </html>
    
    

    之前的代码所做的就是简单地为测试设置代码。注意突出显示的行。前两行 simply point to the hosted version of the QUnit testing suite (both CSS and JavaScript),最后两行是您的 JavaScript 代码和测试用例所在的地方。

    codeToBeTested.js 只是指您编写的 JavaScript 代码,而 testCases.js 是您编写测试用例的地方。在接下来的步骤中,您将看到这两个 JavaScript 文件是如何一起工作的。

  2. 我们将从在codeToBeTested.js中编写代码开始。创建一个 JavaScript 文件,并将其命名为codeToBeTested.js。首先,我们将编写一个简单的函数,用于测试输入的数字是否是奇数。带着这个想法,输入以下代码:

    codeToBeTest.js:
    function isOdd(value){
    return value % 2 != 0;
    }
    
    

    isOdd()接收一个参数值,并检查它是否是奇数。如果是,这个函数将返回 1。

    现在让我们为我们的测试用例编写一段代码。

  3. 创建一个新的 JavaScript 文件,并将其命名为testCases.js。现在,将其输入以下代码:

    test('isOdd()', function() {
    ok(isOdd(1), 'One is an odd number');
    ok(isOdd(7), 'Seven is an odd number');
    ok(isOdd(-7), 'Negative seven is an odd number');
    })
    
    

    注意我们使用 QUnit 提供的方法编写测试用例的方式。首先,我们定义一个函数调用test(),它构建了测试用例。因为我们要测试isOdd()函数,所以第一个参数是一个将在结果中显示的字符串。第二个参数是一个包含我们断言的回调函数。

    我们使用断言语句,通过使用ok()函数。这是一个布尔断言,它期望它的第一个参数为真。如果是真,测试通过,如果不是,测试失败。

  4. 现在保存所有你的文件,并在你喜欢的任何浏览器中运行example.html。根据你的机器,你会收到一个类似于以下示例的屏幕截图:Time for action testing JavaScript with QUnitQunitworking

    通过点击isOdd(),你可以查看测试的详细信息,并将看到它的结果。输出如前一个屏幕截图所示。

    现在让我们模拟一些失败的测试。

  5. 回到testCases.js,在test()的最后一行添加以下代码:

    // tests that fail
    ok(isOdd(2), 'So is two');
    ok(isOdd(-4), 'So is negative four');
    ok(isOdd(0), 'Zero is an even number');
    
    

    保存文件并刷新你的浏览器。你现在将在浏览器中看到一个类似于以下示例的屏幕截图:

    Time for action testing JavaScript with QUnitQunitworking

现在你可以看到测试4, 56失败了,它们是红色的。

在这个时候,你应该看到 QUnit 的好处在于,它很大程度上自动化了我们的测试过程,我们不需要通过点击按钮、提交表单或使用alert()来进行测试。使用这样的自动化测试无疑可以节省我们大量的时间和精力。

刚才发生了什么?

我们刚刚使用了 QUnit 对自定义的 JavaScript 函数进行了自动化测试。这是一个简单的例子,但足以让你入门。

在现实生活中的 QUnit 应用

你可能想知道如何在现实生活中的情况下使用这些测试来测试你的代码。我会说,你很可能会用ok()来测试你的代码。例如,你可以测试真值,如果用户输入是字母数字,或者用户输入了无效值。

各种情况的断言测试更多

另一个你可以注意的事情是ok()并不是唯一你可以执行的断言测试。你还可以执行其他测试,比如比较断言和相同断言。让我们看一个关于比较的短例子。

在本节中,我们将学习使用另一个断言语句,equals()

  1. 打开你的编辑器,打开testCases.js。注释掉你之前写的代码,并在文件中输入以下代码:

    test('assertions', function(){
    equals(5,5, 'five equals to five');
    equals(3,5, 'three is not equals to five');
    })
    
    

    这段代码与您注释掉的代码具有相同的结构。但是请注意,我们使用的是equals()函数而不是ok()equals()函数的参数如下:

    • 第一个参数是实际值

    • 第二个参数是期望的值

    • 第三个参数是自定义消息

    我们使用了两个equals()函数,其中第一个测试将通过,但第二个不会,因为三和五不相等。

  2. 保存文件并打开example.html在浏览器中。你会看到以下截图:各种情况的更多断言测试

JSLitmus

根据 JSLitmus 主页的介绍,JSLitmus 是一个用于创建临时 JavaScript 基准测试的轻量级工具。在我看来,这绝对是正确的。使用 JSLitmus 非常简单,尤其是当它支持所有流行浏览器,如 Internet Explorer、Firefox、Google Chrome、Safari 等时。同时,它完全免费,包含我们在这里提到的产品。

在本节中,我们将快速举一个例子,展示如何创建临时 JavaScript 基准测试。

行动时刻—创建临时 JavaScript 基准测试

现在我们将看到使用 JSLitmus 创建临时 JavaScript 基准测试是多么简单。但首先,让我们安装 JSLitmus。顺便说一下,本节的所有源代码可以在本章的source code文件夹中找到,在jslitmus文件夹下。

  1. 访问www.broofa.com/Tools/JSLitmus/ 并下载JSlitmus.js

  2. 打开你的编辑器,在JSLitmus.js相同的目录下创建一个名为jslitmus_test.html的新 HTML 文件。

  3. 将以下代码输入jslitmus_test.html:

    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
    <html  xml:lang="en" lang="en">
    <head>
    <meta http-equiv="Content-Type"
    content="text/html;charset=utf-8" />
    <title>JsLitmus Testing Example</title>
    <script type="text/javascript" src="img/JSLitmus.js"></script>
    <script type="text/javascript">
    function testingLoop(){
    var i = 0;
    while(i<100)
    ++i;
    return 0;
    }
    JSLitmus.test('testing testingLoop()',testingLoop);
    </script>
    </head>
    <body>
    <p>Doing a simple test using JsLitmus.</p>
    <div id="test_element" style="overflow:hidden; width: 1px;
    height:1px;"></div>
    </body>
    </html>
    
    

    实际上,我这段代码是从 JSLitmus 官网提供的官方示例中摘取的。我会以稍微不同于官方示例的方式进行测试,但无论如何,它仍然展示了我们如何使用 JSLitmus 的语法。

    上面的代码片段包含了用户定义的函数testingLoop(),而JSLItmus.test('testing testingLoop()', testingLoop);是用 JSlitmus 的语法测试testingLoop()的 JavaScript 代码行。

    让我解释一下语法。通常,我们是这样使用 JSLitmus 的:

    JSlitmus.test('some string in here', nameOfFunctionTested);
    
    

    第一个参数是你可以输入的字符串,第二个参数是你打算测试的函数的名称。只需确保这段代码位于你的函数定义之后的地方。

  4. 现在我们已经设置了我们的测试,是时候运行它,看看结果如何。保存jslitmus_test.html并在浏览器中打开这个文件。你应该在浏览器中看到以下内容:创建临时 JavaScript 基准测试的行动时刻

    注意在测试列下,它显示了我们作为JSLItmus.test()的第一个参数输入的文本。

  5. 点击运行测试按钮。你应该在浏览器中收到以下结果:行动时刻—创建即兴 JavaScript 基准测试

  • 它基本上显示了执行代码所需的时间和其他相关信息。你甚至可以通过访问动态生成的 URL 来查看图表形式的性能。如果你收到了与之前截图类似的的东西,那么你已经完成了一个即兴基准测试。

注意

如果你在 Internet Explorer 上运行此测试,并且恰好收到以下(或类似)信息:“脚本执行时间过长”,那么你需要调整你的 Windows 注册表,以便允许测试运行。访问support.microsoft.com/default.aspx?scid=kb;en-us;175500以了解如何调整你的 Windows 注册表设置。

刚才发生了什么?

我们刚刚使用 JSLitmus 创建了一个即兴基准测试。注意使用 JSLitmus 执行即兴基准测试是多么简单。JSLitmus 的酷之处在于它的简单性;没有其他工具,没有需要打开的窗口等等。你所需要做的就是编写JSLItmus.test()并输入你想测试的函数的信息和名称。

使用 JSLitmus 进行更复杂的测试

上面的例子是一个非常简单的例子,帮助你入门。如果你对执行更复杂的测试感兴趣,可以随意查看www.broofa.com/Tools/JSLitmus/demo_test.html并查看其源代码。你将看到使用 JSLitmus 的不同风格的测试,在其带有注释的源代码中。

现在我们已经介绍了与浏览器无关的工具,是时候快速介绍其他类似的测试工具,这些工具可以帮助你调试 JavaScript。

你应该查看的其他测试工具

现在我们即将结束这一章节,我会留给你一个简单的测试工具列表,你可以查看用于测试目的:

  • 塞利姆(Selenium):Selenium是一个自动化测试工具,只能记录在 Firefox 上,并且在其他浏览器中回放时可能会超时。还有其他版本的 Selenium 可以帮助你在多个浏览器和平台上进行测试。Selenium 使用 Java 和 Ruby。获取更多信息,请访问seleniumhq.org。要查看一个简单的介绍,请访问seleniumhq.org/movies/intro.mov

  • Selenium Server:也称为 Selenium 远程控制,Selenium Server是一个允许你用任何编程语言编写自动化 Web 应用程序 UI 测试的工具,针对任何 HTTP 网站,使用任何主流的 JavaScript 支持浏览器。你可以访问seleniumhq.org/projects/remote-control/

  • Watir:Watir是一个作为 Ruby 宝石的自动化测试工具。Watir 有详细的文档,可以在wiki.openqa.org/display/WTR/Project+Home找到。

  • 断言单元框架断言单元框架是一个基于断言的单元测试框架。截至编写本文时,文档似乎有限。但是你可以通过访问jsassertunit.sourceforge.net/docs/tutorial.html来学习如何使用它。你可以访问jsassertunit.sourceforge.net/docs/index.html获取其他相关信息。

  • JsUnit是一个从最流行的 Java 单元测试框架 JUnit 移植过来的单元测试框架。JsUnit 包括一个平台,用于在不同的浏览器和不同的操作系统上自动执行测试。你可以在www.jsunit.net/获得 JsUnit。

  • FireUnit:FireUnit是一个设计在 Firebug 中运行的 Firefox 单元测试框架。它也是 Firefox 的一个流行调试工具,网上有大量的关于它的教程和文档。你可以在fireunit.org/获得 FireUnit。

  • JSpec:JSpec是一个使用自定义语法和预处理器的 JavaScript 测试框架。它还可以以多种方式使用,例如通过终端,通过浏览器使用 DOM 或控制台格式化器,等等。你可以在visionmedia.github.com/jspec/获得 JSpec。

  • TestSwarm:TestSwarm为 JavaScript 提供分布式、持续集成测试。它最初是由 John Resig 为支持 jQuery 项目而开发的,现在已成为官方 Mozilla 实验室项目。请注意,它仍然处于严格测试中。你可以在testswarm.com/获得更多信息。

总结

我们已经终于完成了这一章的结尾。我们覆盖了可用于我们调试任务的各个浏览器的特定工具。

具体来说,我们涵盖了以下主题:

  • Sahi:一个使用 Java 和 JavaScript 的浏览器无关的自动化测试工具

  • QUnit:一个可以用来测试 JavaScript 代码的 jQuery 测试套件

  • JsLitmus:创建即兴 JavaScript 基准测试的轻量级工具

  • 你可以查看的工具列表

最后,我们终于来到了本书的结尾。我希望你从这本书中学到了很多关于 JavaScript 测试的知识。我想感谢你花费时间和精力阅读这本书,同时也想感谢 Packt 出版社的支持。

posted @ 2024-05-23 14:40  绝不原创的飞龙  阅读(12)  评论(0编辑  收藏  举报