JavaScript和jQuery实战手册（原书第2版）

（美）麦克法兰（McFarland，D.S）

孙向阳　译

ISBN：978-7-111-39903-2

版权所有，侵权必究

客服热线：+ 86-10-68995265

客服信箱：service@bbbvip.com

官方网址：www.hzmedia.com.cn

新浪微博 @研发书局

腾讯微博 @yanfabook

The Missing Credits

作者简介

David Sawyer McFarland是Sawyer McFarland Media公司总裁。该公司是位于Oregon的Portland的一家Web开发和培训公司。他从1995年开始开发Web站点，那时候他设计了自己的第一个Web站点——通信专业的一份在线杂志。他曾经是加利福尼亚大学伯克利分校和伯克利多媒体研究中心的网站管理员，并且负责Macworld.com的一次完全的CSS驱动的重新设计。

除了构建Web站点外，David也是一名作家、培训讲师和老师。他在加利福尼亚大学伯克利分校新闻研究所、电子艺术中心、艺术大学研究院、新媒体Ex'Pressions中心，以及波特兰州立大学教授Web设计。他为《Practical Web Design》、《MX Developer's Journal》、《Macworld》杂志和CreativePro.com撰写关于Web设计的文章。

他欢迎读者通过missing@sawmac.com对本书提供反馈（但是，如果你要寻求技术帮助，请参考附录A中列出的资源）。

制作团队简介

Nan Barber（编辑）从“The Missing Manual”系列诞生之初就为其努力工作，这时间已经足够长了，还记得那时候她是通过软盘启动自己的计算机的。

Holly Bauer（产品编辑）居住在Ye Olde Cambridge, MA。她是一位产品编辑，同时业余时间热衷于家庭烹饪、prolific DIYer和中世纪现代Web设计。她的E-mail是holly@oreilly.com。

Carla Spoon（技术审校者）是一位自由作家和文字编辑。她喜爱跑步。她在Mount Rainier工作，并且喜欢在家中研究各种科技配件。她的E-mail是carla_spoon@comcast.net。

Angela Howard（索引制作者）已经从事索引制作工作10年以上了，主要是为计算机图书制作索引，但偶尔也为其他领域的图书，如旅游、非传统医学、动物类图书制作索引。她和丈夫、女儿和两只猫一起居住在加利福尼亚。

致谢

多谢所有在写作本书中给予帮助的人，包括Shelley Powers和Steve Suehring，他们警惕的目光保证我没有犯那些潜在的令人尴尬的错误。也感谢我在波特兰州立大学的很多学生，他们坐下来听我的长长的JavaScript课程并且努力地完成我布置的编程作业，特别感谢Team Futzbit的成员（Combination Pizza Hut和Taco Bell组合）提供了测试的教程，他们是Julia Hall、Amber Brucker、Kevin Brown、Josh Elliott、Tracy O'Connor和Blake Womack。另外，还应该感谢John Resig和jQuery团队，他们创建了使得JavaScript充满乐趣的最好的工具。

最后，感谢David Pogue帮助我开始起步，感谢Nan Barber使我的写作更敏捷且清晰，感谢我的妻子Scholle忍受一个作者的偏执，感谢我的孩子Graham和Kate，他们真了不起。

“The Missing Manual”系列

“The Missing Manual”系列图书语言诙谐，内容丰富，指导人们使用那些没有印刷版手册的计算机产品（这个系列正是关于所有这些计算机产品的）。每本书都配有一个手工的索引和交叉引用。

最新的以及即将出版的书籍包括：

·Access 2010：The Missing Manual by Matthew MacDonald

·Buying a Home：The Missing Manual by Nancy Conner

·CSS：The Missing Manual, Second Edition, by David Sawyer McFarland

·Creating a Website：The Missing Manual, Third Edition, by Matthew Mac Donald

·David Pogue's Digital Photography：The Missing Manual by David Pogue

·Dreamweaver CS5. 5：The Missing Manual by David Sawyer McFarland

·Droid X2：The Missing Manual by Preston Gralla

·Droid 2：The Missing Manual by Preston Gralla

·Excel 2010：The Missing Manual by Matthew MacDonald

·Facebook：The Missing Manual, Third Edition, by E. A.Vander Veer

·FileMaker Pro 11：The Missing Manual by Susan Prosser and Stuart Gripman

·Flash CS5. 5：The Missing Manual by Chris Grover

·Galaxy Tab：The Missing Manual by Preston Gralla

·Google Apps：The Missing Manual by Nancy Conner

·Google SketchUp：The Missing Manual by Chris Grover

·The Internet：The Missing Manual by David Pogue and J. D.Biersdorfer

·iMovie'11＆iDVD：The Missing Manual by David Pogue and Aaron Miller

·iPad 2：The Missing Manual by J. D.Biersdorfer

·iPhone：The Missing Manual, Fourth Edition, by David Pogue

·iPhone App Development：The Missing Manual by Craig Hockenberry

·iPhoto'11：The Missing Manual by David Pogue and Lesa Snider

·iPod：The Missing Manual, Ninth Edition, by J. D.Biersdorfer and David Pogue

·Living Green：The Missing Manual by Nancy Conner

·Mac OS X Snow Leopard：The Missing Manual by David Pogue

·Mac OS X Lion：The Missing Manual by David Pogue

·Microsoft Project 2010：The Missing Manual by Bonnie Biafore

·Motorola Xoom：The Missing Manual by Preston Gralla

·Netbooks：The Missing Manual by J. D.Biersdorfer

·Office 2010：The Missing Manual by Nancy Connor, Chris Grover, and Matthew MacDonald

·Office 2011 for Macintosh：The Missing Manual by Chris Grover

·Palm Pre：The Missing Manual by Ed Baig

·Personal Investing：The Missing Manual by Bonnie Biafore

·Photoshop CS5. 5：The Missing Manual by Lesa Snider

·Photoshop Elements 10：The Missing Manual by Barbara Brundage

·PowerPoint 2007：The Missing Manual by E. A.Vander Veer

·Premiere Elements 8：The Missing Manual by Chris Grover

·QuickBase：The Missing Manual by Nancy Conner

·QuickBooks 2011：The Missing Manual by Bonnie Biafore

·QuickBooks 2012：The Missing Manual by Bonnie Biafore

·Switching to the Mac：The Missing Manual, Snow Leopard Edition, by David Pogue

·Switching to the Mac：The Missing Manual, Lion Edition, by David Pogue

·Wikipedia：The Missing Manual by John Broughton

·Windows Vista：The Missing Manual by David Pogue

·Windows 7：The Missing Manual by David Pogue

·Word 2007：The Missing Manual by Chris Grover

·Your Body：The Missing Manual by Matthew MacDonald

·Your Brain：The Missing Manual by Matthew MacDonald

·Your Money：The Missing Manual by J. D.Roth

前言

不久以前，Web实在是一个令人讨厌的地方。用陈旧的HTML构建的Web页面只是显示信息，而没有其他更多的东西。人们单击一个链接，然后等待载入一个新Web页面，这就是他所能获得的交互。

现如今，大多数Web站点几乎具有和台式机程序一样的响应性，对于每次鼠标单击都迅速响应。这都得益于我们将要在本书中学习的内容，即JavaScript及其亲密战友jQuery。

JavaScript

JavaScript是一种程序设计语言，它允许用动画、交互性和动态的视觉效果来增强HTML的功能。

JavaScript可以通过提供即时反馈而使得Web页面更为有用。例如，一个具备JavaScript功能的购物车页面，当访问者选择要购买的一件商品的时候，可以即时显示总金额，以及税费和运费。当某人试图提交一个遗漏了必需信息的Web表单的时候，JavaScript可以产生一条错误消息。

JavaScript还允许创建有趣的、动态的和交互性的界面。例如，使用JavaScript，可以将由缩略图组成的一个静态页面转换为一场包含动画的幻灯片放映（我们将在10.2.2节学习如何做到这点）。或者，可以做一些更加微妙的事情，例如，通过将内容组织到很小的面板，访问者只要简单地单击一下鼠标就可以访问它们，从而可以在页面上放置更多的信息，而不必使得页面显得拥挤（参见10.1.1节）。或者，添加一些有用的和吸引人的内容，例如，弹出的工具提示为Web页面上的项目提供补充信息（参见10.4.1节）。

JavaScript的主要优点之一是其即时性。它使得页面可以立即对于某人和页面的交互操作做出响应，例如单击一个链接、填充一个表单，或者只是在屏幕上移动鼠标等操作。JavaScript不会遭遇到和PHP这样的服务器端编程相关的令人沮丧的延迟，而后者则依赖于Web浏览器和Web服务器之间的通信。因为JavaScript不会依赖于频繁加载和重新载入Web页面，使用它创建的Web页面给人们的感觉及其操作都更像是桌面程序，而不像是一般的Web页面。

如果你访问Google Maps（http：//maps.google.com/），将会看到JavaScript的实际应用。Google Maps允许我们查看所在城市的地图（或者很多其他地方的地图），放大以获取街道和公交车站的详细视图，或者缩小以得到如何穿越城镇、州或全国的鸟瞰图。尽管在Google之前有很多的地图站点，但它们总是要求重新载入多个Web页面（这通常是一个很慢的过程）来获得想要的信息。相反，Google Maps不需要刷新页面，它对我们的选择做出快速响应。

使用JavaScript创建的程序，既有真正简单的程序（例如，弹出一个新的浏览器窗口，其中有一个Web页面），也有像Google Docs（http：//docs.google.com/）这样功能完备的Web应用程序，而后者允许我们使用Web浏览器来创建幻灯片、编辑文档和创建电子表格，其感觉就像是在计算机上直接运行的程序一样。

JavaScript简史

JavaScr ipt由Netscape于1995年后期发明，它几乎和Web本身一样老。尽管JavaScript在如今广受重视，但是它却有着颇为跌宕起伏的历史。一度认为它是狂热爱好者的编程语言，用来添加不太有用的效果，如像证券报价器一样在Web浏览器状态栏滚动的消息，或者是跟随鼠标光标在页面上移动的、动画的蝴蝶效果。在JavaScript的早期，很容易在网上找到数以千计的JavaScript程序（也叫做脚本），但是，很多这样的脚本通常不会在所有的Web浏览器上工作，并且有时候甚至导致浏览器崩溃。

注意：JavaScript和Java编程语言毫无关系。JavaScript最初名为LiveScript，但是，Netscape的市场营销人员确信，如果试图和当时热门的Java语言联系起来，JavaScript应该会得到更多的关注。但是，不要把两者混为一谈，尤其是在求职面试的时候。

在早期，JavaScript也遭遇到两个主流浏览器Netscape Navigator和Internet Explorer不兼容的问题。由于Netscape和Microsoft都试图通过添加新的和（表面上）更好的功能来超越对方，这两个浏览器常常以不同的方式工作，这使得人们很难创建在两种浏览器中都能很好工作的JavaScript程序。

注意：在Netscape引入了JavaScript之后，Microsoft引入了jScript，这是它们自己的JavaScript版本，包含在Internet Explorer之中。

黎明前的曙光就要来临了，并且当前的浏览器（如Firefox、Safari、Chrome、Opera和Internet Explorer 9）都将它们操作JavaScript的很多方式标准化了，这使得编写在大多数浏览器下工作的JavaScript程序容易一些了（在当前的Web浏览器中仍然有一些不兼容，因此，你需要学习一些处理跨浏览器问题的技巧。我们将在本书中学习如何克服浏览器不兼容性的问题）。

在过去的几年里，通过在Google、Yahoo和Flickr等特色鲜明的网站上的应用，JavaScript已经获得了重生。这些站点广泛地使用JavaScript来创建交互式的Web应用程序。现在来学习JavaScript是再好不过的时机了。有了已知的充足的知识和已经编写的高质量的脚本，即便你是初学者，也可以为Web站点添加高级的交互，你完全不必要成为一名计算机科学家。

注意：JavaScript也称为ECMAScript。ECMAScript是JavaScript规范的“正式”名称，它由名为Ecma国际（http：//www.ecmascript.org/）的国际标准化组织负责开发和维护。

JavaScript随处可见

JavaScript并不是仅用于Web页面。它已经被证明是如此有用的一种编程语言，如果学习了JavaScript，你可以创建Yahoo Widget和Apple的Dashboard Widget，为iPhone开发程序，并且在像Acrobat、Photoshop、Illustrator和Dreamweaver这些众多的Adobe程序中加入脚本编程功能。实际上，Dreamweaver已经为聪明的JavaScript程序员提供了一种方式来向程序中添加他们自己的命令。

此外，Flash的编程语言ActionScript也是基于JavaScript的，因此，如果学习了JavaScript的基础知识，你将能够为承担Flash编程项目做好充分准备。

jQuery

JavaScript有一个令人尴尬的小秘密：编写起来比较难。尽管JavaScript比很多其他编程语言要简单一些，但它仍然是一门编程语言。并且，很多人（包括Web设计师）发现编程很难。更为复杂的是，不同的Web设计师对JavaScript的理解不同，因此，在Chrome中工作的程序，可能在Internet Explorer 9中完全没有反应。这种常见的情况可能导致在不同的机器和不同浏览器上要花很多时间进行测试，才能确保程序对站点的所有访问者都能很好地工作。

这也是jQuery诞生的原因。jQuery是一个JavaScript库，其目的是使得JavaScript编程更容易、更有趣。JavaScript库是一个复杂的JavaScript程序，它简化了有难度的编程任务，并且解决了跨浏览器的问题。换句话说，jQuery解决了JavaScript最头疼的两个问题，即不同Web浏览器复杂和繁琐的特性。

在JavaScript编程中，jQuery是Web设计师的秘密武器。有了jQuery，只用单独一行代码就能完成任务，而要编写自己的JavaScript代码来完成这些任务，要写上百行程序并且花很多时间进行浏览器测试。实际上，一本深入介绍JavaScript的图书，至少是本书厚度的两倍，并且，当你读完它（如果你能设法读完的话），你所能做到的事情，只需一点jQuery知识就能够完成一半。

这就是为什么本书的主要内容是介绍jQuery，它使你可以如此容易地做到很多事情。jQuery的另一个美妙之处在于，你可以使用上千个易于使用的jQuery插件，向Web站点添加高级功能。例如，FancyBox插件（将在7.4节介绍）允许我们将一个简单的缩略图页面转变成为一个交互性的幻灯片播放，只需要一行程序就能做到！jQuery已经用于数百万个站点（http：//trends.builtwith.com/javascript/JQuery），这一点也不令人惊奇。甚至像Drupal和WordPress这样流行的内容管理系统，也内置了支持jQuery的模块。你甚至可以找到一种叫做“jQuery程序员”的招聘职位，而根本不提JavaScript。当你学习jQuery的时候，你就加入了Web设计师和程序员的一个大社群，这些人使用一种简单和更为强大的方法来创建交互的、功能强大的Web页面。

HTML：主题结构

如果没有Web设计的两大支柱HTML和CSS, JavaScript一筹莫展。很多程序员提到，3种语言构成了一个Web页面的三个“层”：HTML提供了结构层，以一种更有意义的方式来组织图片和文字等内容；CSS（Cascading Style Sheet，层叠样式表）提供表现层，使得HTML中的内容很好地显示；JavaScript添加行为层，给Web页面带来生气，使它能够和Web访问者交互。

换句话说，要掌握JavaScript，我们还需要很好地理解HTML和CSS。

提示：要了解HTML和CSS的完备介绍，请参阅Elisabeth Freeman和Eric Freeman编写的《Head First HTML with CSS and XHTML》。如果需要CSS的技巧性主题的深入介绍，请参阅David Sawyer McFarland所著的《CSS：The Missing Manual》（这两本书都由O'Reilly出版）。

HTML（HyperText Markup Language，超文本标记语言）使用叫做标签的简单命令来定义Web页面的各个部分。例如，下面的HTML代码创建了一个简单的Web页面：

＜！DOCTYPE html＞

＜html＞

＜head＞

＜meta charset=utf-8＞

＜title＞Hey, I am the title of this web page.＜/title＞

＜/head＞

＜body＞

Hey, I am some body text on this web page.

＜/body＞

＜/html＞

这个示例可能不是很令人兴奋，但是它具备了一个Web页面所需的所有基本元素。这个页面开始的第一行是文档类型声明，或者简称为doctype，它说明了页面的文档类型以及它符合哪个标准。HTML实际上有几种不同的版本，并且对于每个版本使用不同的doctype。在这个例子中，该页面的doctype是HTML 5；一个HTML 4.01或XHTML文档doctype更长一些，并且还包含了一个URL，它指向互联网行的一个文件，其中包含了该类型文件的定义。

实际上，doctype告诉Web浏览器如何显示该页面。在Internet Explorer中，doctype甚至可以影响到CSS和JavaScript如何工作。如果doctype不正确或者缺少doctype，我们可能会发现脚本有很多跨浏览器的差别而最终四处碰壁。换句话说，总是要在HTML中包含一个doctype。

当今有5种常用的HTML类型：HTML 4.01 Transitional、HTML 4.01 Strict、XHTML 1.0 Transitional、XHTML 1.0 Strict和HTML5（在发展中的新类型）。这5种类型都很相似，只是在如何书写标签以及允许哪些标签和属性方面有些细微区别。大多数Web页面编辑程序都在创建新的Web页面的时候加入了相应的doctype，但是，如果你需要了解每种doctype如何编写的示例，可以在www.webstandards.org/learn/reference/templates看到不同类型的页面的模板。

使用哪种类型的HTML并不重要。所有当前的Web浏览器都理解每种doctype，并且可以使用5种文档类型中的任何一种来显示Web页面而不会有任何问题。使用哪种doctype，不会像确保页面验证（参见后面的“快速熟悉：验证Web页面”部分）正确那么重要。

注意：XHTML曾经作为Web设计师的下一件大事宣布。尽管我们仍然发现有人认为只应该使用XHTML，但是，风向已经开始转换了。W3C已经停止了XHTML的开发以支持HTML 5。你可以通过阅读《HTML 5：The Missing Manual》（Matthew MacDonald著）或《HTML 5：Up and Running》（Mark Pilgrim著）（都由O'Reilly出版）来了解HTML 5。

HTML标签如何工作

在前面的示例中，和我们所能看到的任何Web页面的HTML代码一样，可以看到大多数的命令都成对地出现，并且包含一段文本或其他命令。尖括号之间的内容，也就是那些标签，都是用于告诉Web浏览器如何显示页面的指令。标签就是超文本标记语言的“标记”部分。

开始标签告诉浏览器指令从哪里开始，而结束的标签告诉指令从哪里结束。结束标签总是在第一个尖括号符号（＜）之后包含一个斜杠（/）。例如，标签＜p＞表示一段文本的开始，而＜/p＞表示其结束。

对于功能正常的Web页面，至少必须包含如下3个标签：

·＜html＞标签出现在Web页面刚开始的地方（在doctype之后），并且在Web页面末尾再次出现（带有一个斜杠）。这个标签告诉Web浏览器，这个文档中包含的信息是使用HTML编写的，而不是用其他某种语言编写的。页面的所有内容，包括其他的标签，都出现在开始和结束＜html＞标签中。

如果把Web页面看做一棵树，＜html＞标签就是它的树干。从树干伸展出来的是两个分支，分别代表任何Web页面的两个主要部分，即标题和正文。

·Web页面的标题包围在＜head＞标签中，它包含了页面的标题。它还提供了可供浏览器和Web搜索引擎使用的其他不可见的信息（例如，搜索关键字）。此外，标题还可以包含Web浏览器用来显示Web页面和增加交互性的信息。例如，把CSS放入到文档的标题部分。文档的标题也经常包含到JavaScript程序的链接和到其他JavaScript文件的链接。

·Web页面的正文包含在一对＜body＞标签中，其中包括了出现在浏览器窗口中的所有信息，包括标题、文本、图片等。

在＜body＞标签内，我们通常可以看到如下的标签：

·用＜p＞标签告诉Web浏览器一段文本开始，而用＜/p＞表示一段文本结束。

·＜strong＞标签突出显示文本。如果把某段文本包含在其相对的标签＜/strong＞中，将会得到粗体字体。HTML代码段＜strong＞Warning！＜/strong＞告诉Web浏览器以粗体显示单词“Warning！”。

·＜a＞标签或者叫做锚点标签，在Web页面中创建了一个超链接。当单击的时候，超链接可以导向Web上的任何地方。通过在＜a＞标签中放置一个Web地址，告诉浏览器该链接指向何处。例如，可以输入＜a href="http：//www.missingmanuals.com/"＞Click here！＜/a＞。

浏览器知道，当访问者单击了“Click here！”的时候，它应该跳转到Missing＆nbsp；

Manual Web站点。这个标签的href部分叫做属性，而这个URL（Uniform Resource Locator，统一资源定位符或Web地址）是值。在这个例子中，http：//www.missingmanuals.com是href属性的值。

快速熟悉验证Web页面

正如前面所介绍的，Web页面的doctype表示用来创建Web页面的HTML或XHTML的类型。规则根据类型而略有不同，例如，和HTML 4.01不同，XHTML不允许拥有一个未结束的＜p＞标签，并且要求所有的标签名和属性都是小写的（例如，＜a＞而不是＜A＞）。HTML 5包含了新的标签，并且允许你使用HTML或XHTML语法。由于每个HTML版本使用不同的规则，因此总是需要验证Web页面。

HTML验证器是确保Web页面编写正确的一个程序。它检查页面的doctype，然后分析页面中的代码看它是否符合doctype定义的规则。例如，验证器会标记出错误拼写的标签名或未结束的标签等错误。万维网联盟（World Wide Web Consortium, W3C）是负责Web上使用的众多技术的组织，它有一个免费的在线验证器，位于http：//validator.w3.org。我们可以把自己的HTML代码复制并粘贴到一个Web表单，上传到一个Web页面，或者把验证器指向Web上已经存在的一个页面，验证器会分析HTML并报告该页面是否有效。如果有任何错误，验证器告诉你错误是什么以及错误发生在HTML文件的哪一行。

如果使用Firefox，可以从http：//users.skynet.be/mgueury/mozilla下载HTML Validator插件。这个插件允许我们直接在Web浏览器中验证一个页面，只要打开这个页面（甚至直接位于计算机上的一个页面），验证器就会指出HTML中的错误。在Safari中也有一个类似的插件，叫做Safari Tidy，可以在http：//zappatic.net/projects/safarivalidator.找到。

验证HTML不只是为了保证良好的形式，它也是确保JavaScript程序正确工作所必需的。很多JavaScript涉及操作一个Web页面的HTML：例如，标识一个特定的表单字段，或者在特定位置放置新的HTML（例如一条错误消息）。为了让JavaScript能够访问和操作Web页面，HTML必须保持正确的工作顺序。忘了结束一个标签，多次使用同样的ID名，或者不正确地嵌套HTML标签，都可能使得JavaScript代码不能正确工作或者根本不工作。

CSS：为页面添加样式

HTML曾经一度是需要掌握的唯一语言。我们可以构建带有彩色文本和图形的页面，并且使得单词以不同的大小、字体和颜色显示。但如今，访问者期待从Web站点得到更多，因此，我们需要使用一种新的、更为灵活的技术，即CSS，从而给页面更高级的视觉效果。CSS是一种格式化语言，可用于美化文本、构建复杂的页面布局以及为站点广泛添加样式。

把HTML仅仅看做构建一个Web页面的语言。它帮助我们标识出想要让世界知道的内容。像＜h1＞和＜h2＞这样的标签表示标题，并且赋予了相关的重要性：1级标题比2级标题更为重要。＜p＞标签表示一段基本的信息。其他的标签也提供了进一步的结构化线索，例如，＜ul＞标签表示一个符号列表（例如，生成一个更为智能的配方成分列表）。

另一方面，CSS为组织良好的HTML内容添加了设计才能，使得它更美丽并且更容易阅读。基本上，CSS样式只是告诉Web浏览器如何在页面上显示特定元素的一条规则。例如，可以创建一条CSS规则来使得所有的＜h1＞标签以36像素的高度显示，字体为Verdana，颜色为橙色。CSS可以做很多功能强大的事情，例如，添加边框、改变页内边距，甚至控制一个页面元素的确切位置。

说到JavaScript，对页面做出的一些最有价值的改变涉及了CSS。我们可以使用JavaScript来给一个HTML标签添加CSS样式，或者从该标签删除一个CSS样式，或者根据访问者的输入或鼠标单击来动态地改变CSS属性。我们甚至可以从一种样式的一个属性动画地变换为另一个样式的该属性（例如，将背景颜色动态地从黄色修改为红色）。例如，可以通过直接修改CSS的display属性来让一个页面元素显示或隐藏。要实现一个项目在屏幕上的动画，可以使用JavaScript来动态地修改CSS position属性。

样式剖析

单个样式定义的一个元素的外观是很基础的常见用法。它实际上是一条规则，告诉Web浏览器如何格式化某内容，把标题变蓝色、围绕照片绘制一个红色边框，或者创建一个150像素宽的边栏来容纳链接的列表。如果样式可以说话，它会说：“嗨，浏览器，让这个看起来像这样。”样式实际上由两个元素组成：浏览器要格式化的Web页面元素（选择器），以及实际的格式化指令（声明块）。例如，选择器可以是一个标题、一段文本、一张照片等。声明块可以把文本变为蓝色、围绕段落添加一个红色边框，或者把照片放置到页面的中央，其可能性是无限的。

注意：技术类型通常遵从W3C的领导，并且叫做CSS样式规则。本书交叉地使用术语“样式”和“规则”。

当然，CSS样式不会用漂亮的英语来交流。它们有自己的语言。例如，要设置Web页面上所有段落的字体颜色和字体大小，应该这样编写：

p{color：red；font-size：1.5em；}

这个样式只是说：“让所有段落的文本（以＜p＞标签标记的内容）显示为红色和1.5　em高”（em是基于浏览器的常规文本大小的一个单位或度量）。如图I-1所示，即便像这样的一个简单样式也包含几个元素：

选择器·。选择器告诉浏览器要样式化页面上的哪一个或哪几个元素，例如标题、段落、图像或链接。在图I-1中，选择器（p）引用所有的＜p＞标签，这让Web浏览器使用这个样式的格式化指令来格式化所有的＜p＞标签。使用CSS所提供的范围广泛的选择器，加上一点儿创新性，我们就可以实现对页面格式化的很好控制（选择器是使用jQuery的一个重要的部分，将从4.6节开始详细介绍它）。

·声明块。跟在选择器后面的代码包含了我们想要对选择器应用的所有格式化选项。这个块以一个左花括号（{）开头，以一个右花括号（}）结束。

·声明。在声明的左花括号和右花括号之间，添加一个或多个声明，或者格式化指令。每个声明有两个部分，一个属性和一个值，然后以一个分号结束。

·属性。CSS提供了范围广泛的格式化选项，叫做属性。属性是一个单词，或者连字符连接的几个单词，表示某种样式效果。大多数属性拥有一个直白的名字，例如，font-size、margin-top和background-color。例如，background-color属性设置背景颜色（可以猜想得到）。

注意：如果需要复习有关CSS的知识，请阅读《CSS：The Missing Manual》。

·值。最后，将通过给一个CSS属性赋值来表现自己的创新能力，例如，使得背景为蓝色、红色、紫色或黄绿色。不同的CSS属性需要特定类型的值，包括颜色（例如，红色或#FF0000）、长度（例如，18px、2in或5em）、URL（例如，images/background.gif）或者一个特定关键字（例如，top、center或bottom）等。

图　I-1：样式（或规则）由两个主要部分组成：一个是选择器，告诉Web浏览器要格式化什么；另一个是声明块，列出了浏览器用来样式化选择器的格式化指令

我们不需要像图I-1那样把样式写成单独一行。很多样式有多个格式化属性，因此，将它们划分为多行可以使其更容易阅读。例如，我们可能想要把选择器和开始花括号放在第一行，每个声明独占一行，结束花括号独自在最后一行，如下所示：

p{

color：red；

font-size：1.5em；

}

缩进格式也有帮助，可以缩进一个制表符或者两个空格，从而在视觉上把选择器和声明区分开来，使得很容易搞清楚哪个是哪个。最后，在冒号和属性值之间添加一个空格是可选的，但是添加空格的话，可以增加样式的可读性。实际上，可以在二者之间添加任意多个空格。例如，color：red、color：red和color：red都有效。

用于JavaScript编程的软件

要创建HTML、CSS和JavaScript组成的Web页面，只需要像Notepad（Windows）或TextEdit（Mac）这样的一个基本的文本编辑器就可以了。但是，在输入了数百行JavaScript代码之后，你可能想要尝试一个更适合于编写Web页面的程序。这里列出一些常用的程序，既包括免费的也包括需要付费购买的。

注意：有数百种工具可以帮助我们开发Web页面和编写JavaScript程序，因此，下面并不是一个完整的列表。可以把它们看做当前JavaScript爱好者最棒的选择。

免费程序

除了这里列出的外，还有很多免费的程序可以用来编辑Web页面和样式表。如果你仍然使用Notepad和TextEdit，不妨试试这里列出的工具。下面是你可以选择开始尝试的工具列表：

·Notepad++（Windows, http：//notepad-plus. sourceforge.net）是程序员的朋友。它可以突出显示JavaScript和HTML代码的语法，并且允许我们保存宏并为其指定快捷键，以便可以自动地插入最经常使用的代码段。

·HTML-Kit（Windows, www. chami.com/html-kit）是一款功能强大的HTML/XHTML编辑器，包含了很多有用的功能，例如，直接在程序中预览一个Web页面（从而不必在浏览器和编辑器之间来回切换），添加HTML标签的快捷键等。

·CoffeeCup Free HTML Editor（Windows, www. coffeecup.com/free-editor）是商业软件CoffeeCup HTML编辑器（售价49美元）的免费版本。

·TextWrangler（Mac, www. barebones.com/products/textwrangler）是一款免费软件，实际上是BBEdit的配套下沿版本。BBEdit是一款用于Mac的高级的、知名的文本编辑器。TextWrangler不具备BBEdit的所有内置的HTML工具，但它确实包括语法着色（用不同的颜色突出显示标签和属性，以便很容易扫描页面并区分开各个部分），支持FTP（因此，可以把文件上传到Web服务器）等。

·Eclipse（适用于Windows、Linux、Mac，网站是www.eclipse.org）是针对Java开发者的一款免费的、流行的软件，它包含了使用HTML、CSS和JavaScript的工具。还有专门针对JavaScript的一个版本（www.eclipse.org/downloads/packages/eclipse-ide-javascript-web-developers/indigor），以及为jQuery添加自动填充功能的Eclipse插件（http：//marketplace.eclipse.org/category/free-tagging/jquery）。

·Aptana Studio（适用于Windows、Linux、Mac，网站是www.aptana.org）是一款强大的、免费的编程环境，带有使用HTML、CSS、JavaScript、PHP和Ruby on Rails的工具。

商业软件

商业Web站点开发程序有很多，从便宜的文本编辑器到具备所有配件的、完整的Web站点构建工具，以下是其中一部分：

·EditPlus（Windows, www. editplus.com）是一款便宜的文本编辑器（售价35美元），包括语法着色、FTP、自动完成和其他节省录入的功能。

·CoffeCup（Windows, www. coffeecup.com）是文本编辑器和可视化编辑器的组合（售价49美元）。我们可以直接编写HTML代码或者使用一个可视化界面来构建页面。

·textMate（Mac, http：//macromates. com）是Mac程序员的一款热门软件。这个文本编辑器（售价63美元）包括很多节省JavaScript程序员时间的功能，包括“自动匹配字符”，它会自动插入一对标点标签的第二个字符（例如，在输入了一个开始圆括号后，该程序自动插入一个结束圆括号）。

·BBEdit（Mac, www. barebones.com/products/bbedit）。这是很多人喜欢的Mac文本编辑器（售价99.99美元），具有很多操作HTML、XHTML、CSS、JavaScript的工具。包括很多有用的Web构建工具和快捷方式。

·Dreamweaver（M ac和Windows, www.macromedia.com/software/dreamweaver）是可视化的Web页面编辑器（售价399美元）。它允许我们看到页面在Web浏览器中的外观。该程序还包括一款强大的文本编辑器，可用于编写JavaScript程序；还包括优秀的CSS创建和管理工具。要全面学习如何使用这一功能强大的软件，请阅读《Dreamweaver：The Missing Manual》一书。

·Expression Web Designer（Windows, www. microsoft.com）是Microsoft的最新的Web设计工具（售价149美元）。它替代了FrontPage并且包含了很多专业化的Web设计工具，包括优秀的CSS功能。

关于本书

和Microsoft Word或Dreamweaver这样的软件不同，JavaScript不是单独一家公司所开发的单独产品。JavaScript没有开发支持人员来为普通Web开发者编写一本易于阅读的手册。尽管你可以在Mozilla.org（例如，https：//developer.mozilla.org/en/JavaScript/Reference）或Ecmascript.org（www.ecmascript.org/docs.php）这样的站点上找到很多的信息，但没有关于JavaScript编程语言的权威资源。

由于没有关于JavaScript的手册，人们常常不知道从何处开始着手学习JavaScript。并且JavaScript的出色之处甚至常常难倒有经验的Web专业人员。本书可以作为JavaScript的配套手册。在本书中，读者可以看到关于使用JavaScript来创建高度交互的Web页面的按部就班的说明。

同样，可以在http：//docs.jquery.com/Main_Page找到关于jQuery的详细文档。但是，这是程序员写给程序员的，因此，说明比较简短而且偏技术化。尽管jQuery比常规的JavaScript编程要更加容易，本书将教你一些基本的jQuery原理和技术，以便当你使用jQuery扩展自己的Web站点的时候可以从正确的路径出发。

本书专为那些拥有一些Web页面构建经验的读者而编写。你需要熟悉HTML和CSS，才能更好地阅读和利用本书，因为JavaScript通常和HTML和CSS紧密协作来实现其魔力。本书主要的内容是为高级初学者或中级计算机用户编写的。但是，如果你是构建网页的新手，本书中名为“快速熟悉”的部分将提供你需要理解的话题的介绍性信息。相反，如果你是一名高级Web页面设计者，那么，请注意那些名为“高级用户提示”的部分。它们提供了对于有经验的计算机爱好者的更多技术提示、技巧和快捷方式。

注意：本书还适时地推荐其他的图书，这些图书介绍了超出了JavaScript的使用手册之外的更为专门的主题。有时候推荐的图书来自于O'Reilly Media的“The Missing Manual”系列，但并不总是如此。如果有些O'Reilly图书之外的好书，我们也会告诉你。

本书介绍JavaScript的方法

JavaScript是一种真正的编程语言：它不像HTML或CSS那样工作，但是，它有自己的一套（常常是复杂的）规则。对于Web设计师来说，调整思维并开始像计算机程序员那样思考问题，这并不总是很容易的事情，并且也没有一本图书可以教给你关于JavaScript的所有知识。

本书旨在将你变成下一位伟大的程序员。本书要帮助Web设计师熟悉JavaScript的点点滴滴，进而开始学习jQuery，以便用它来尽可能快速而容易地为Web站点添加真正有用的交互。

在本书中，我们学习了JavaScript和编程的基础知识，但是只是基础知识还不够创建令人激动的Web页面。要介绍构建高级的、可交互Web页面所需的所有JavaScript知识，本书的篇幅都是不够的。相反，本书介绍了如何使用专业的（并且免费的）JavaScript代码，从而将你从细小的、耗费时间的细节中解放出来，并能够创建出在不同浏览器中运行良好的JavaScript程序。

我们学习了JavaScript的基础知识，然后直接学习如何在一些非常高级但易于使用的JavaScript辅助程序的一点儿帮助（是很多帮助）下，开发具有交互性的高级页面。考虑一下这种方式：我们自己砍伐树木和量制木材，自己建造窗户、门和门框，自己贴瓷砖等来盖一座房子。这种DIY的方法是很多JavaScript图书中常见的方法。但是，谁有那么多的时间呢？本书的方法更像是利用一些已有的构件并且使用基本的技能将它们组合起来，从而盖一座房子。最终的结果是，用零散的时间盖起了一栋漂亮而功能齐全的房屋，并且我们不必花时间来学习这个过程的每一个步骤。

内容梗概

本书划分为5个部分，每个部分各包含几章。

·第一部分，JavaScript入门，从非常基础的内容开始。我们将学习JavaScript的基本构建块，并且得到一些关于计算机编程的有用的通用提示。这一部分教你如何为Web页面添加脚本、存储和操作信息，并且给程序添加智能以使它能够对不同的条件做出响应。我们还学习了如何和浏览器窗口通信，存储和读取cookie，对鼠标单击和表单提交等各种事件做出响应，以及修改Web页面的HTML。

·第二部分，jQuery入门，介绍了Web最流行的JavaScript库：jQuery。这里，我们将学习这一惊人的编程工具的基础知识，该工具将使你成为一个更有生产力和能力的JavaScript程序员。我们将学习如何选择和操作页面元素，通过让页面元素响应访问者，从而增加交互性；并且添加漂亮的视觉效果和动画。

·第三部分，构建页面功能，提供了关于JavaScript实际应用的很多真实的示例。我们学习了如何创建弹出式导航栏，以及构建一个可交互的照片集。

我们通过使用添加表单验证（以使访问者不会提交遗漏信息的表单），添加一个日历挂件来更容易地选择日期，以及根据Web访问者做出的选择来改变表单选项等方法，使得表单更为有用。最后，我们还使用标签页面板、折叠面板和弹出式对话框来创建有趣的用户界面，这些界面外观很好看而且功能完备。

·第四部分，Ajax与Web服务器通信，介绍了凭借一己之力就使得JavaScript成为最耀眼的We b语言的一种技术。在该部分中，我们学习了如何使用JavaScript与Web服务器通信，以便页面能够接收信息并根据Web服务器提供的信息来更新自己，而不必载入一个新的Web页面。

注意：你将会看到按部就班的说明，介绍如何在你的计算机上安装一个Web服务器，以便可以利用本书配套Web页面上的（第三部分介绍的）漂亮的技术（参见后面的“生动示例”了解更多细节）。

·第五部分，提示、技巧和除错，在似乎什么也无效的时候给你提供帮助：可能完美地编写了JavaScript程序却没有看到它达到我们所预期的效果（或者情况更糟，它根本不工作）。你将了解程序员新手最常犯的一些错误，并学习发现程序中的错误并进行修正的技术。

在本书的最后有一个附录，给出了帮助你进一步学习JavaScript编程语言的参考书目列表。

基础知识

要使用本书，并且真正地使用计算机，需要知道一些基础知识。本书假设你已经熟悉了一些术语和概念：

·单击。本书使用3种指示来要求你使用计算机鼠标或者触摸板。单击意味着把鼠标光标移动到屏幕上的某处，然后（不要再移动鼠标）按下并释放鼠标上的单击按钮（或者笔记本的触摸板）。右键单击意味着用鼠标右键做同样的事情。双击当然意味着快速连续单击两次，而根本不移动鼠标光标。拖动意味着在按下鼠标按钮的同时移动鼠标。

提示：如果使用Mac，并且没有鼠标按钮右键，可以通过按下Control键同时单击一个鼠标按钮来做到同样的事情。

当告诉你在Mac上æ-click某内容，或者在PC上Ctrl-click某内容，你可以在按下⌘或Ctrl按键的同时单击（这两个按钮都在空格键附近）。

·菜单。菜单是屏幕或窗口顶部的单词：File、Edit等。单击一个按钮就会出现一个命令列表，就好像这个命令列表已经在窗口的隐藏处写好了，我们只是将其拉下来。

·键盘快捷方式。如果试图以一种创新的方式来录入，那么，必须手离开键盘，抓起鼠标然后使用一个菜单（例如，使用Bold命令），这对你来说太具有干扰性了。这就是为什么很多有经验的计算机行家宁愿通过某种组合键来触发菜单命令。例如，在Firefox Web浏览器中，可以按下Ctrl-+（Windows）或æ-+（Mac）来使得Web页面上的文本变大（并且更易阅读）。当我们读到“按下æ-B”这样的一条指令的时候，首先按下æ键，然后在æ按下的时候，输入B，然后释放着两个键。

·操作系统基础知识。本书假设你知道如何打开一个程序，在Web上冲浪和下载文件。你应该知道如何使用开始菜单（Windows）和Dock或Apple菜单（Macintosh），以及控制面板（Windows）或系统偏好设置（Mac OS X）。

如果你已经掌握了这些信息，就具备了阅读本书所需的所有技术背景。

关于这些箭头

在整本书中，如果你看到这样的一个句子“打开System→Librar y→Fonts文件夹”。这只是一条较长的指令的简写方式，这条指令告诉你依次打开3个嵌套的文件夹，例如，“在你的硬盘上，找到一个名为System的文件夹，打开它。在System目录窗口中有一个名为Library的目录，双击它打开。在这个目录下还有一个叫做Fonts的目录，双击它打开”。

同样，这种箭头帮助我们简化了在菜单中选择命令的表达过程，如图I-2所示。

图　I-2：在本书中，箭头表示法帮助我们简化了菜单指令。例如View→Text Size→Increase是“在View菜单下，选择Text Size，然后从出现的子菜单中选择Increase”

在线资源

本书设计用来让你能够在Web上更快、更专业地工作，那么，自然，本书的价值的很大一部分也依赖于网络。在网上，你可以找到示例文件，方便动手实验。你还可以与“The Missing Manual”团队交流并告诉我们你喜欢（或不喜欢）本书的哪些内容。请访问www.missingmanuals.com，或者直接进入后续的某一节。

生动示例

在你阅读本书的过程中，会遇到很多生动的示例——你可以直接照着做，用到的原始素材（例如，图像和完成一半的Web页面）可以从www.sawmac.com/js2e或www.missingmanuals.com/cds的本书Missing CD页面下载。在吊床上休息的时候，直接阅读这些示例你将获益匪浅；但是如果能够花时间在计算机上亲自尝试，你会发现专业设计师构建Web页面的方式，并产生异常深刻的认识。

你还会发现，在本书的教程中，有完成的页面的URL，你可以将自己的工作和最终的结果进行比较。换句话说，你不会只看到本书的页面中的JavaScript代码的图片，而且还会看到Internet上实际的、可以使用的Web页面。

注册

如果你在oreilly.com注册了本书，将得到相应的优惠，例如，购买本书的未来新版本的折扣。注册只需要几次鼠标单击。要开始注册，在浏览器中输入www.oreilly.com/register进入注册页面。

反馈

有问题？需要更多的信息？想要做一个图书评论者？在我们的反馈页面上，你可以得到专家对你在阅读本书时遇到的问题的解答，分享你对于“The Missing Manual”系列图书的看法，并找到和你一样对JavaScript和jQuery感兴趣的社群。访问www.missingmanuals.com/feedback就可以了。

勘误

为了努力让本书内容尽可能地保持及时和准确，每次印刷的时候，我们都对读者提出的勘误进行确认。我们还会注意在本书Web站点上进行修改，以便你可以将重要的更正在你的图书中进行标注（如果你愿意的话）。访问http：//tinyurl.com/jsjqtmm以报告勘误，并且查看已有的勘误。

第一部分：JavaScript入门

第1章　编写第一个JavaScript程序

HTML自身并没有太多智能：它不能做数学运算，不能判断某人是否正确填写了一个表单，而且不能根据Web访问者的交互来做出判断。基本上，HTML让人们阅读文本、观看图片，并且单击链接转向拥有更多文本和图片的下一个Web页面。若要给Web页面添加智能，使其可以对站点的访问者做出响应，则需要使用JavaScript。

JavaScript允许Web页面智能地反应。有了它，就可以创建智能的Web表单，当访问者忘了包含必需的信息时，表单可以告知访问者；可以让元素显示、消失，或者在页面上来回移动（如图1-1所示）；甚至可以用从Web服务器获取的信息来更新Web页面的内容（而不必载入一个新的Web页面）。简而言之，JavaScript使得Web站点更加动人和高效。

图　1-1　JavaScript允许Web页面对访问者做出响应：在Amazon.com上，把鼠标光标悬停在“Gifts and Wish Lists”链接上，会打开一个标签页，它浮动在页面的其他元素之上并且提供额外的选项

注意：实际上，HTML 5确实为HTML添加了一些智能，包括基本的表单验证。但是，由于并非所有的浏览器都支持这些漂亮的功能（以及由于你可以使用表单和JavaScript而实现更多的功能），你仍然需要用JavaScript来构建最好的、更为用户友好的和交互性的表单。你可以通过Mark Pilgrim的《HTML5：Up and Running》（O'Reilly）一书了解关于HTML5和Web表单的更多内容。

1.1　编程简介

对于很多人来说，“计算机编程”使他们脑海里浮现出这样的情景：拥有超常智慧的家伙在键盘前弯腰而坐，连续数小时飞快地敲击着几乎难以理解的、含混不清的语言。确实，某些编程工作就是那样的。编程可能像是非凡之人表演的复杂魔术。虽然很多编程概念很难掌握，但是，在编程语言中，JavaScript对于非程序员来说算是相对友好的。

然而，JavaScript比HTML或CSS都要复杂，并且，对于Web设计者来说，编程往往是一个陌生的世界。因此，本书的目标之一是帮助你像一个程序员一样思考。在本书中，你将学习基本的编程概念，不管你是编写JavaScript、ActionScript或者甚至使用C++编写桌面程序，这些概念都适用。更重要的是，你将学习如何完成一个编程任务，从而在把JavaScript添加到Web页面之前，就确切地知道自己想要做什么。

很多Web设计师受到JavaScript中用到的奇怪符号和单词的影响而望而却步。常见的JavaScript程序中充满了符号（{}[]；，（）！=）和不熟悉的单词（var、null、else if）。这看上去就像是一门外语，并且很多时候，学习编程语言和学习其他语言很相似。你需要学习新单词、新标点，并且理解如何把它们组合起来以便成功沟通。实际上，每种编程语言都有它自己的一组关键字和字符，以及组合这些关键字和字符的一组规则，即语言的语法。学习JavaScript的语法就像学习其他语言的词汇表和语法一样。我们需要记住这种语言的单词和规则（或者至少有一本书在手边作为参考）。当学习一门新的语言的时候，我们会很快意识到，如果在错误的音节加了重音，可能使单词变得无法理解。同样，一个简单的输入错误或者甚至是遗漏了标点符号，都可能使得JavaScript程序无法运行，或者在Web浏览器中产生一个错误。在开始学习编程的时候，我们会犯很多的错误，这只是编程的本质所导致的。

快速熟悉客户端与服务器端

JavaScript是一种客户端语言，这意味着它在Web浏览器中工作。Web编程语言的另一种类型叫做服务器端语言，可以在PHP、.NET、ASP、ColdFusion、Ruby on Rails以及其他Web服务器技术构建的页面中看到。正如其名字的含义，服务器端语言在Web服务器上运行。它们可以通过访问数据库、处理信用卡并在全球范围内发送E-mail而展示出很多的智能。服务器端语言的问题在于，它们需要Web浏览器向Web服务器发送请求，强迫访问者等待带有新信息的新页面的到来。

另外，客户端语言可以立即做出反应并且改变访问者在他的Web浏览器中所看到的内容，而不需要下载一个新的页面。根据访问者和页面如何交互，内容可能出现或消失，在屏幕上移动或者自动更新。这种响应性使得创建出的Web站点更像是桌面程序，而不是静态Web页面。也可以使用插件来为Web页面增加编程的智能。Java applet就是一个例子。这些使用Java编程语言编写的小程序在Web浏览器中运行。它们往往启动较慢并且由于造成浏览器崩溃而为人所知。

Flash是另外一种基于插件的技术，它提供复杂的动画、视频、音频和很多交互性潜力。实际上，有时候很难分辨一个交互性的Web页面是使用JavaScript还是Flash。例如，Google Maps也可以用Flash来创建（实际上，Yahoo Maps曾经是Flash应用程序，直到Yahoo使用JavaScript重新开发它）。区分二者的一种快速方法是：在页面上你认为可能是Flash的部分单击鼠标右键（在这个例子中，就是在地图上单击），如果是Flash，将会看到包含了“About the Flash Player”的一个弹出式菜单。

我们将在本书第三部分中学习Ajax, Ajax把客户端和服务器端融合到了一起。Ajax是使用JavaScript和服务器交互的一种方法，它从服务器获取信息并更新Web页面，而不需要载入新的Web页面。Google Maps使用这种技术使得我们能够在地图上移动而不必载入一个新的Web页面。

实际上，JavaScript也是一种服务器端编程语言。例如，node.js Web服务器（http：//nodejs.org/）使用JavaScript作为一种服务器端编程语言，以连接到数据库、访问Web服务器的文件系统，并且在Web服务器上执行很多其他的任务。然而，本书不会讨论JavaScript编程的这方面话题。

首先，你可能会发现用JavaScript编程令人沮丧，你要花很多的时间去找出自己在录入脚本时所犯的错误。此外，你可能会发现刚开始的时候一些和编程相关的概念有些难以理解。但是，不要担心，如果你过去尝试学习过JavaScript并且因为觉得它太难而放弃，本书将帮助你越过那些经常绊倒编程新手的障碍。（并且，如果你已经有了编程经验，本书将教会你针对Web浏览器编程时用到的JavaScript的特性以及独特概念。）

此外，本书不只是介绍JavaScript，还介绍了jQuery——世界上最流行的JavaScript库。jQuery使得复杂的JavaScript编程变得更加容易，而且容易很多。因此，只需要有一些JavaScript的知识，再加上jQuery的帮助，你将能够立刻创建高级的、可交互性的Web站点。

计算机程序

当你为Web页面添加JavaScript的时候，就是在编写一个计算机程序。当然，大多数JavaScript程序要比用来阅读E-mail、修复照片和构建Web页面的程序简单得多。但是，即便JavaScript程序（也叫做脚本）较简单并且较短，但是它们还是具有很多和较复杂的程序相同的属性。

简而言之，任何计算机程序都是以指定的顺序完成的一系列步骤。假设你想要用浏览Web页面的人的名字来显示一条欢迎消息，例如，“Welcome, Bob！”。要完成这个任务需要做几件事情：

1.询问访问者的名字。

2.获得访问者的响应。

3.在Web页面上打印（即显示）消息。

虽然你可能不想在Web页面上显示一条欢迎消息，但这个例子展示了编程的基本过程：确定想要做什么，然后把任务分解为完成它所必须经过的每个步骤。每次想要创建一个JavaScript程序，你必须经历这个过程来确定实现目标所需的步骤。一旦知道了这些步骤，你就准备好编写程序了。换句话说，把思路翻译成程序代码（单词和字符），这些代码让Web浏览器去做我们想要的事情。

常见问题编译型语言与脚本编程语言

JavaScript是一种脚本编程语言。这个术语也用来描述PHP和ColdFusion这样的语言。那么，什么是脚本编程语言呢？

在计算机上运行的大多数程序都是使用编译型语言编写的。编译是指把程序员编写的代码转换为计算机能够理解的指令的过程。一旦编译了程序，就可以在计算机上运行它，既然编译后的程序已经直接转换为计算机能够理解的指令了，那么，它比脚本编程语言所编写的程序运行得要快。遗憾的是，编译一个程序是耗费时间的过程，你必须编写程序、编译它，然后测试它。如果程序不工作，还必须重复整个过程。

另外，脚本编程语言只有在解释器（这是可以把脚本转换为计算机能够理解的内容的另外一个程序）读取它的时候才编译。以JavaScript为例，解释器内置到了Web浏览器中。因此，当Web浏览器把带有JavaScript程序的一个Web页面读入其中的时候，Web浏览器就把JavaScript翻译成计算机能够理解的内容。因此，脚本编程语言运行起来比编译型语言要更慢些，因为每次脚本程序运行的时候，都要为计算机进行翻译。脚本编程语言对于Web开发者来说是很好的，脚本通常要比桌面程序规模小而且结构没有那么复杂，因此，速度上的缺失并不是那么重要。此外，既然它们不需要编译，那么创建和测试使用脚本编程语言的程序就是一个较快的过程。

1.2　如何把JavaScript添加到页面

Web浏览器可以理解HTML和CSS，并且能够把这些语言转换为屏幕上可视化的显示。Web浏览器中能够理解HTML和CSS的部分叫做布局或渲染引擎。但大多数浏览器还拥有一种叫做JavaScript解释器的工具。它是浏览器的一部分，可以理解JavaScript并且执行一个JavaScript程序的步骤。既然Web浏览器通常期待HTML，当JavaScript出现的时候，必须使用＜script＞标签明确地告诉浏览器。

＜script＞标签是常规的HTML。它的作用就像是一个开关，其效果就是表示“嗨，Web浏览器，接下来是一些JavaScript代码，你不知道该对它们做什么，因此把它们交给JavaScript解释器处理吧”。当Web浏览器遇到了代表结束的＜/script＞标签，它知道到了JavaScript程序的末尾，并且自己可以恢复履行正常的职责了。

很多时候，可以像下面这样在Web页面的＜head＞部分添加＜script＞标签：

＜！DOCTYPE HTML PUBLIC"-//W3C//DTD HTML 4.01//EN""http：//www.w3.org/TR/

html4/strict.dtd"＞

＜html＞

＜head＞

＜title＞My Web Page＜/title＞

＜script type="text/javascript"＞

＜/script＞

＜/head＞

＜script＞标签的type属性表示脚本所遵从的格式和类型。在这个例子中，type="text/javascript"意味着这段脚本是常规的文本（就像HTML一样），并且它是以JavaScript编写的。

如果你使用HTML 5，脚本会更加简单。可以完全省略type属性：

＜！doctype html＞

＜html＞

＜head＞

＜meta charset="UTF-8"＞

＜title＞My Web Page＜/title＞

＜script＞

＜/script＞

＜/head＞

实际上，Web浏览器也允许你在HTML 4.01和XHTML 1.0文件中省略type属性——脚本还是一样会运行。然而，若没有type属性，页面将不会正确地验证（参见本章后面部分以了解关于验证的更多内容）。本书使用HTML5作为doctype，但是，JavaScript代码在HTML 4.01和XHTML 1.0中也是一样的，并且同样有效。

然后，我们可以在开始＜script＞标签和结束＜script＞标签之间添加JavaScript代码：

＜！doctype html＞

＜html＞

＜head＞

＜meta charset="UTF-8"＞

＜title＞My Web Page＜/title＞

＜script＞

alert（'hello world！'）；

＜/script＞

＜/head＞

稍后，你将会发现这段JavaScript代码实际上做什么。现在，把注意力放在开始＜script＞标签和结束＜script＞标签上。要在页面中添加一段脚本，首先要插入这些标签。在大多数情况下，要把＜script＞标签放到页面的＜head＞中，以便JavaScript代码可以整洁地放置在Web页面的一个区域。

然而，把＜script＞标签放入页面的HTML的任何地方绝对都是有效的。实际上，在本章后面你会将看到，有一条JavaScript命令允许直接把信息写入Web页面。使用该命令，可以把＜script＞标签放置在想要脚本在页面上显示消息的某个位置（这是在HTML正文中的某个地方）。将＜script＞标签放置在结束＜/body＞标签的下面也很常见，这种方法确保了在运行任何JavaScript之前先载入页面，以便访问者可以看到页面。

外部JavaScript文件

像前面小节那样使用＜script＞标签，使得我们可以把JavaScript添加到一个单独的页面。但是，很多时候需要创建供站点的所有页面共享的脚本。例如想要使用JavaScript程序为Web页面添加动画的、下拉式的导航菜单。我们希望在站点的每个页面上都能看到同样有趣的导航栏，但是，把同样的JavaScript代码复制和粘贴到站点的每个页面确实不是个好办法，这有几个方面的原因。

首先，不断地复制和粘贴同样代码的工作量很大，尤其是如果站点有数百个页面更是如此。其次，如果你决定修改或扩展JavaScript代码，将需要找到使用这段JavaScript代码的每个页面并更新代码。最后，既然这段JavaScript程序的所有代码放置在每个页面之中，每个页面都会变得更大而且载入更慢。

一种更好的方法是使用外部JavaScript文件。如果你为Web页面使用过外部CSS文件，那么，你应该熟悉这一技术。外部JavaScript文件是一个简单的文本文件，其文件扩展名为.js，例如navigation.js。该文件只包含JavaScript代码，并且使用＜script＞标签将其链接到一个Web页面。例如，要把一个名为navigation.js的JavaScript文件添加到主页中，可以像下面这样编写代码：

＜！doctype html＞

＜html＞

＜head＞

＜meta charset="UTF-8"＞

＜title＞My Web Page＜/title＞

＜script src="navigation.js"＞＜/script＞

＜/head＞

＜script＞标签的src属性的作用和＜img＞标签的src属性或＜a＞标签的href属性的作用一样。换句话说，它指向你自己的Web站点上的一个文件或者其他Web站点上的一个文件（参见后面的“快速熟悉：URL类型”部分）。

注意：当添加src属性来链接到外部JavaScript文件的时候，不要在开始＜script＞标签和结束＜script＞标签之间添加任何JavaScript代码。如果想要连接到一个外部JavaScript文件并且为页面添加定制的JavaScript代码，再多使用一组＜script＞标签。例如：

＜script src="navigation.js"＞＜/script＞

＜script＞

alert（'Hello world！'）；

＜/script＞

快速熟悉URL类型

在把外部JavaScript文件附加到Web页面的时候，需要为＜script＞标签的src属性指定一个URL。URL也称作统一资源定位符（Uniform Resource Locator），它是到位于Web上的一个文件的路径。有3种类型的路径：绝对路径、根相对路径和文档相对路径。所有这3种路径都指明了Web浏览器可以在哪里找到一个特定文件（例如，其他的Web页面、图像文件或JavaScript文件）。

绝对路径就像邮政地址，它包含了位于世界任何地方的Web浏览器要找到该文件所需的所有信息。绝对路径包括http：//、主机名、文件夹以及文件的名字。例如，http：//www.cosmofarmer.com/scripts/site.js。

根相对路径表示文件相对于站点的顶级目录（站点的根目录）的位置。根相对路径不包括http：//或域名。它以一个/（斜杠）开始，该斜杠表示站点的根目录，即主页所在的目录。例如，/scripts/site.js表示文件site.js位于一个名为scripts的目录下，而这个目录本身位于站点的顶级目录之中。创建根相对路径的一种简单方法是，从绝对路径中去除http：//和主机名。例如，http：//www.sawmac.com/index.html表示为根相对路径的URL就是/index.html。

文档相对路径指明了从Web页面到该JavaScript文件的路径。如果Web站点上有多个层级的目录，需要使用指向同一个JavaScript文件的不同路径。例如，假设有一个名为site.js的JavaScript文件，它位于Web站点的主目录中一个名为script的目录中。针对主页来说，该文件的文档相对路径就是scripts/site.js，但是，对于about目录中的一个页面来说，同样的文件的路径将会有所不同：../scripts/site.js；../的意思是从

about目录向上，而/scripts/site.js的意思是到scripts目录下获取文件site.js。

使用哪种类型的URL有如下一些技巧：

·如果要指定的文件和Web页面不在同一个服

务器上，必须使用绝对路径。这是能够指向另一个Web站点的唯一类型。

·根相对路径对于存储在自己的站点上的

JavaScript文件很好用。既然它们总是从根目录开始，对于Web站点上的每个页面来说，JavaScript文件的URL都是相同的，即使Web页面位于站点中的目录或子目录中。然而，除非你通过一个Web服务器（要么是Internet上外部的Web服务器，要么是你在自己的计算机上为了测试而安装的Web服务器）来浏览Web页面，根相对路径是无效的。换句话说，如果你在自己的计算机之外使用浏览器的File→Open命令打开一个Web页面，Web浏览器无法定位、载入或运行使用一个根相对路径附加的JavaScript文件。

当·你在自己的计算机上设计站点而没有Web

服务器的辅助时，文档相对路径是最好的选择。你可以创建一个外部JavaScript文件，将其附加到Web页面，然后直接打开硬盘上的Web页面，从而在Web浏览器中检查该JavaScript文件。当把实际的、生动而逼真的Web站点移到Internet上时，文档相对路径能够很好地工作，但是，如果你把Web页面移动到服务器的另一个位置，则需要重写JavaScript文件的URL。在本书中，将使用文档相对路径，因为这种路径允许你在自己的计算机上执行和测试教程，而不需要Web服务器。

你可能（并且将常常会）把多个外部JavaScript文件附加到单个Web页面。例如，你可能创建一个外部JavaScript文件来控制下拉式导航栏，并且还有另一个外部JavaScript文件使得能够为照片页面添加漂亮的幻灯播放效果（将在第7章学习如何做到这点）。在照片集页面中，我们希望把这两个JavaScript程序都用上，因此，要把两个文件都附加上。

此外，可以像下面这样给同一个页面附加外部JavaScript文件并且添加一个JavaScript程序：

＜！doctype html＞

＜html＞

＜head＞

＜meta charset="UTF-8"＞

＜title＞My Web Page＜/title＞

＜script src="navigation.js"＞＜/script＞

＜script src="slideshow.js"＞＜/script＞

＜script＞

alert（'hello world！'）；

＜/script＞

＜/head＞

别忘了，必须对每个外部JavaScript都使用一对开始＜script＞标签和结束＜script＞标签。我们将在后面小节介绍的教程中创建一个外部JavaScript文件。

可以把外部JavaScript文件保存在Web站点的根目录（或根目录下的任何子目录）中的任何地方。很多开发者在站点的根目录下创建一个专门的目录，用来放置外部JavaScript文件，通常这个目录名为js（表示JavaScript）或libs（表示库）。

注意：有时候，要附加外部JavaScript文件的顺序。你将在本书后面看到，有时候编写的脚本依赖于外部文件中的代码。在使用JavaScript库（简化复杂的编程任务的JavaScript代码）时，这是常见的情况。在本书1.5节的教程中，我们将看到真正使用JavaScript库的一个例子。

1.3　第一个JavaScript程序

学习JavaScript的最佳方式是进行实际的编程。在本书中，你将会看到很多动手实践的教程，它们带领你一步一步地经过创建JavaScript程序的过程。要开始练习教程，我们需要一个文本编辑器（参见前言的“用于JavaScript编程的软件”部分的介绍）、一个Web浏览器以及位于www.sawmac.com/js2e的练习文件（参见下面的“注意”部分的完整说明）。

注意：本章的教程需要本书We b站点www.sawmac.com/js2e/上的示例文件。单击“Download the tutorials files”链接来下载它们（教程文件存储为一个单独的Zip文件）。

Windows用户应该下载Zip文件并且双击它以打开压缩包。单击Extract All Files选项，然后按照Extraction向导的指示解压缩文件，并将其放置到计算机上。Mac用户只需要双击文件就可以解压缩。在下载并解压缩文件之后，计算机上应该有了一个名为MM_JAVASCRIPT2E的文件夹，其中包含了本书的所有教程文件。

为了让你开始尝试并初步了解JavaScript，第一个JavaScript程序将会非常简单：

1.在喜欢的文本编辑器中打开文件hello.html。

这个文件放置在从www.sawmac.com/js2e下载的MM_JAVASCRIPT2E目录下的chapter01目录中。这是一个非常简单的HTML页面，拥有一个用来添加一些视觉效果的外部层叠样式表。

2.单击结束＜/head＞标签之前的空行并录入：

＜script＞

这段代码实际上是HTML而不是JavaScript。它告知Web浏览器，这个标签之后的内容是JavaScript。

3.按下返回键换到一个新行，并且录入：

alert（'hello world'）；

我们刚刚输入了第一行JavaScript代码。JavaScript函数alert（）是这样的一条命令，它弹出一个警告框并且显示出现在圆括号内的消息，这个例子中的消息是hello world。现在先不要管所有的标点符号（圆括号、引号和分号），在下一章将会了解它们的作用。

4.再次按下回车键，并且输入＜/script＞。现在的代码应该如下所示：

＜link href="../_css/site.css"rel="stylesheet"＞

＜script＞

alert（'hello world'）；

＜/script＞

＜/head＞

在这个例子中，刚才输入的内容以粗体显示。文件中已经有了两个HTML标签，确保你输入的代码和刚才展示的完全相同。

5.启动Web浏览器并打开hello.html文件进行预览。

Web浏览器弹出一个JavaScript警告框（如图1-2所示）。注意，当警告框出现的时候，这个页面是空白的（如果没有看到图1-2所示的警告框，可能在前面的步骤中输入代码有误。仔细检查你输入的代码并阅读下面的“提示”部分）。

图　1-2　JavaScript警告框是吸引人们注意力的一种快捷方式。这是我们学习并使用的最简单的一条JavaScript命令

6.单击警告框的OK按钮并关闭它。

当警告框消失的时候，Web页面出现在浏览器窗口中。

提示：当你第一次开始编程时，可能会对JavaScript如此频繁地根本不工作而感到惊讶。对于程序员新手，不工作的程序的最常见原因只是简单的录入错误。要仔细地检查，确保命令（例如，第一个脚本中的alert）的拼写是正确的。此外，需要注意的是，标点符号通常是成对出现的（例如，第一个脚本中的开始圆括号和结束圆括号以及单引号）。确保在需要使用标点符号的时候，包含了开始的标点符号和结束的标点符号。

尽管第一个程序不是惊天动地般的复杂（甚至没有太多意思），但它确实展示了一个重要的概念：Web浏览器将会在读入JavaScript代码的时候执行JavaScript程序。在这个例子中，alert（）命令先出现，然后Web浏览器才显示Web页面，因为JavaScript代码出现在＜body＞标签中的HTML之前。当开始编写程序操作Web页面的HTML时，这一概念有了用武之地，我们将在第3章学习这一点。

注意：Internet Explorer（IE）不喜欢运行你直接从硬盘打开的Web页面中所包含的JavaScript程序，因为担心这样的JavaScript程序可能会带来某些危害。因此，当你试图在Internet Explorer中预览本书的教程文件时，将会看到一条消息，告诉你IE已经阻止了该脚本。单击“Allow blocked content”来查看该程序的运行效果。这一恼人的行为只是适用于你预览自己的计算机上的Web页面时，当预览存储在Web服务器上的文件时，不会出现这种情况。当你使用本书中的教程时，最好使用Firefox、Chrome或Safari等其他的Web浏览器来预览页面，这样就可以避免在每次浏览页面的时候都需要单击“Allow blocked content”按钮。

1.4　把文本写到Web页面上

前一个脚本在显示器的中间弹出一个对话框。如果想要使用JavaScript直接在Web页面上显示消息，该怎么办呢？有很多方法可以做到这一点，并且我们将在本书后面内容中学习一些高级技术。然而，我们可以使用一条内置JavaScript命令来实现这个简单的目标，这就是第二个脚本所要做的事情：

1.在文本编辑器中打开hello2.html文件。

尽管＜script＞标签通常出现在Web页面的＜head＞中，但是可以把它们和JavaScript程序直接放置到Web页面的正文中。

2.直接在“＜h1＞Writing to the document window＜/h1＞”的下面输入如下代码：

＜script＞

document.write（'＜p＞Hello world！＜/p＞'）；

＜/script＞

和alert（）函数一样，document.write（）也是一条JavaScript命令，它把放置在开始圆括号和结束圆括号之间的内容直接显示出来。在这个例子中，HTML＜p＞Hello world！＜/p＞添加到了页面中，这是一个段落标签和两个单词。

3.保存页面，并且在一个Web浏览器中打开它。

页面打开并且单词“Hello wor ld！”出现在蓝色标题栏的下方（如图1-3所示）。

图　1-3：哇。这个脚本可能并非document.write值得大书特书的功能。哈哈，这是JavaScript式的幽默，但是它确实展示了我们可以使用JavaScript来为Web页面添加内容。当你想要在W e b页面下载之后显示消息（例如，'Welcome back to the site, Dave'），这是一种方便的技巧。

注意：下载的教程文件也包含了每个教程的完整版本。如果你的JavaScript无法工作，则将你的成果和同一目录下文件名以complete_开头的教程文件进行比较。例如，文件complete_hello2.html包含了我们添加到文件hello2.html的脚本的一个有效版本。

刚刚创建的两段脚本可能并没有使你对JavaScript或者本书留下深刻印象。不必担心。从完全理解基础知识开始是很重要的。只需要几章内容，你将能够使用JavaScript做一些非常有用且复杂的事情。实际上，在本章剩下的内容中，我们将初步尝试一些高级功能，在学习完本书的前两个部分之后，我们就能够把这些高级功能添加到Web页面。

1.5　附加外部JavaScript文件

正如前面所介绍的，如果想要在多个W e b页面中使用同样的脚本，通常把JavaScript代码放在单独的一个文件中。然后可以让Web页面载入这个文件并使用其中的JavaScript。当我们使用他人的JavaScript代码的时候，外部JavaScript文件也很方便。特别是，JavaScript代码的集合叫做库，它们提供了有用的JavaScript程序。通常，这些库使得我们可以很容易地完成一些一般来说相当难的工作。我们将在第4章学习有关JavaScript库的更多内容，特别是本书所使用的JavaScript库jQuery。

但是现在体验一下把一个外部JavaScript文件附加到页面，并且编写一个简短的程序来做一些令人惊讶的事情：

1.在文本编辑器中打开文件fadeIn.html。

这个页面只包含了一些简单的HTML：几个＜div＞标签、一个标题栏和几个段落。你可以为这个页面添加一个简单的视觉效果，它会缓慢地淡入显示所有的内容。

2.单击靠近页面顶端的＜link＞和结束＜/head＞标签之间的空白行，并且输入：

＜script src="../_js/jquery-1. 6.3.min.js"＞＜/script＞

这段代码把一个名为jquery-1.6.3.min.js的文件链接到这个Web页面，而该文件包含在名为_js的目录下。当Web浏览器载入这个页面的时候，它还会下载jquery-1.6.3.min.jsJavaScript文件并运行其中的代码。

接下来，我们将把自己的JavaScript程序添加到这个页面。

注意：在JavaScript文件的名称中包含一个版本号，这是很常见的。在这个例子中，文件名是jquery-1.6 3.min.js。1.6.3表示jQuery的版本是1.6.3。min部分表示文件是最小化的，这会使得文件较小以便可以更快地下载。

3.按下回车键产生一个新的空白行，然后输入：

＜script＞

HTML标签通常成对出现，有一个开始标签和一个结束标签。要确保不会忘记添加结束标签，可以在输入了开始标签之后立即输入结束标签，然后再填写两个标签之间的内容，这么做能够帮助你避免遗忘。

4.按下回车键两次，产生两个新的空白行，然后输入：

＜/script＞

这就结束了JavaScript代码段。现在添加一些程序。

5.单击开始script标签和结束script标签之间的空白行，并输入：

$（document）.ready（function（）{

你可能会问这究竟要干什么。我们将会在第6章了解这段代码的所有细节，但是基本上，这行代码利用jquery-1.6.3.min.js文件中的程序，确保浏览器在适当的时候执行下一行代码。

6.按下回车键创建一个新行，然后输入：

$（'body'）.hide（）.fadeIn（3000）；

这行代码完成一个背景颜色的魔术：在3秒钟（或3000 ms）的时间内，它让页面内容先消失然后缓慢地淡入显示。它是如何做到这些的？哦，这是jQuery的魔力的一部分，jQuery使得我们仅用几行代码就可以产生复杂的效果。

7.最后一次按下回车键，然后输入：

}）；

这行代码结束了JavaScript代码，它和表示到了JavaScript程序末尾的结束＜/script＞标签很相似。对于那些奇怪的标点符号，现在不必过于关心，我们将在本书后面详细了解它们是如何工作的。你需要做的主要事情是确保输入的代码和这里给出的完全一致。一个输入错误可能就会导致程序无法工作。最后添加到页面的代码如下面的粗体部分所示：

＜link href="../_css/site.css"rel="stylesheet"＞

＜script src="../_js/jquery-1.6.3.min.js"＞＜/script＞

＜script＞

$（document）.ready（function）（）{

$（'body'）.hide（）.fadeIn（3000）；

}）；

＜/script＞

＜/head＞

8.保存HTML文件，并且在Web浏览器中打开它。

你现在应该会看到页面慢慢地淡入到视线中。将数值3000修改为不同的值（例如，从250到10 000），再看看页面工作方式的变化。

注意：如果你尝试在Internet Explorer中预览这个页面，如果它似乎什么也不做，你可能需要单击在页面底部出现的“Enable blocked content”对话框（参见1.3节的“注意”部分）。

正如你所见到的，实现Web页面上的惊人效果并不需要太多的JavaScript代码。得益于像jQuery这样的JavaScript库，我们能够创建高级的、交互式的Web站点，而不需要自己是编程高手。然而，了解JavaScript和编程的基础知识确实有帮助。在接下来的3章中，我们将介绍非常基础的JavaScript知识，以帮助你熟悉构成这个语言的基本概念和语法。

1.6　追踪错误

当你试图在Web浏览器中查看带有JavaScript功能的页面而没有什么变化发生的时候，这是最令人沮丧的时刻。这对程序员来说是非常普遍的体验。即便很有经验的程序员，初次编写程序的时候也并不总是就做对了，因此，搞清楚哪里出错了只是游戏的一部分。

大多数Web浏览器都设置为默默地忽略JavaScript错误，因此，你通常不会看到“Hey this program doesn't work！”的对话框（一般来讲，这是好事情，因为你不希望一个JavaScript错误打断你浏览Web页面的体验）。

那么，如何搞清楚哪里出错了呢？在JavaScript中，追踪错误的方式有很多种。在第15章中，我们将学习一些高级调试技术，但是，最基本的方法还是依靠Web浏览器。大多数Web浏览器都能发现JavaScript错误，并且在一个叫做错误控制台的单独窗口中保存它们的记录。当你载入包含错误的Web页面的时候，可以查看控制台来获得有关错误的帮助信息，例如，错误发生在Web页面的哪一行以及对错误的描述。

通常，你可以在错误控制台中找到问题的解决方案，修改JavaScript，然后页面就可以工作了。当你初次编程的时候，控制台可以帮助你扫除基本的录入错误，例如，忘记添加结束的标点符号，或者输错了JavaScript命令的名称。你可以在自己喜欢的浏览器中使用错误控制台，但是，由于脚本有时候在一种浏览器中有效，而在另一种浏览器中会失效，因此本节将介绍如何在所有主流的浏览器中打开JavaScript控制台，以便你在每种浏览器中都可以跟踪错误。

1.6.1　Firefox JavaScript控制台

Firefox JavaScript控制台是开始追踪代码中错误的最佳地方。控制台不仅提供了对错误相当清晰的描述（在编程的时候，没有比这里的描述更清晰的了），还指出错误出现在代码中的哪一行。

例如，在图1-4中，控制台指出错误是“unterminated string literal”，表示在“slow”的前面有一个开始的单引号，却没有一个结束的单引号。控制台还指出了错误所在的文件名字（在这个例子中是fadeIn.html）以及错误发生的行号（第11行）。最好的一点是，它还用一个箭头指示出包含错误的行，在这个例子中，突出显示了开始的引号。

警告：尽管错误控制台绘制了一个箭头指向Firefox遇到错误的位置，但这并不总是我们犯错误的地方。有时候，我们需要修改箭头之前或箭头之后的代码。

图　1-4　Firefox JavaScript控制台指出了程序中的错误。控制台还保持了之前页面的错误的一个列表，因此，这个列表可能很快就变得很长。只需要单击Clear按钮就可以清除控制台中的所有错误

要显示JavaScript控制台，单击Firefox菜单并选择Web Developer→Error Console（在Windows系统上）或者Tools→Error Console（在Mac系统上）。控制台是一个自由浮动的窗口，我们可以将它来回移动。它不仅显示JavaScript错误，而且也显示CSS错误，因此，如果你在层叠样式表中犯了错误，也会在这里找到（请在控制台的顶部选择Errors按钮，否则你可能会看到和JavaScript错误不相关的警告和消息）。

提示：既然错误控制台显示了错误发生的行号，你可能想使用一款可以显示行号的文本编辑器。这样，你就可以很容易地从错误控制台跳转到文本编辑器并找到要修改的代码行。

遗憾的是，脚本中可能出错的内容太多了，从简单的录入错误到复杂的逻辑错误。当你刚刚开始JavaScript编程的时候，很多错误都只是简单的输入错误。例如，你可能漏掉了一个分号、一个引号或一个圆括号，或者错误地拼写了一条JavaScript命令。当运行本书中的例子的时候（就像这个例子），你很容易产生输入错误。下面是当你第一次开始录入本书的代码的时候，可能会常常见到的一些错误：

·在参数列表的后面漏掉了。在一条命令的末尾忘记输入一个结束圆括号。以这段代码为例，alert（'hello'；，在'hello'后面漏掉了一个结束圆括号。

·未结束的字符串直接量。字符串是引号括起来的一系列字符（我们将在第2章详细了解这点）。例如，在代码alert（'hello'）；中'hello'是一个字符串。很容易漏掉开始的引号或结束的引号。

·XXX未定义。如果你错误地拼写了一条JavaScript命令，例如，aler（'hello'）；，你将会得到一条表示这条（拼写错误的）命令未定义的错误，例如，"aler is not defined."。

·语法错误。有时候，Firefox不了解你想要做什么，就会给出这种通用错误消息。语法错误表示代码中存在某些错误。这可能是输入错误，但是，你也可能把一条或多条JavaScript语句以一种不允许的方式组合到一起。在这种情况下，你需要仔细查看发现错误的每一行，并且尝试搞清楚到底犯了什么错误；遗憾的是，这类错误往往需要对JavaScript语言的经验和理解才能修正。

从上面的列表中可以看出，很多错误仅仅是忘记输入一对标点符号中的某一个，例如引号或圆括号。好在这些错误都很容易修正，并且，随着你拥有更多的编程经验，最终几乎能够完全不犯这样的错误（没有程序员能够完全不犯这些错误）。

注意：Firefox的插件Firebug（http：//getfirebug.com/）很好地扩展了Firefox的错误控制台的功能。实际上，它也为IE 9、Chrome和Safari（后面介绍）等浏览器中所用到的其他开发工具提供了一种模型。本书第15章将会介绍Firebug。

1.6.2　显示InternetExplorer9控制台

InternetExplorer9提供了一组高级的开发者工具，不仅可以用来查看JavaScript错误，而且可以分析CSS、HTML以及跨网络的信息传输。当打开这一开发者工具的时候，它会出现在浏览器窗口的底部（如图1-5所示）。按F12键可以打开该开发者工具，并且再次按F12键可以关闭它。你会发现，JavaScript错误在Console标签页下列出（图1-5中带圆圈的地方）。和Firefox错误控制台不同（它记录了你访问的所有页面的JavaScript错误的汇总），你需要打开IE 9控制台，然后重新加载页面以查看错误。

图　1-5　Internet Explorer开发者工具允许访问页面上发生的JavaScript错误，以及很多其他的信息

IE9的控制台所显示的错误消息和前面介绍的Firefox的错误消息类似。例如，“Unterminated string constant”和Firefox的“Unterminated string literal”相同，表示漏掉了一个引号。此外，Internet Explorer指出了HTML文件中发生错误的代码行。

1.6.3　打开ChromeJavaScript控制台

Google的Chrome浏览器也允许你从其JavaScript控制台查看JavaScript错误。要打开该控制台，可以单击工具图标（图1-6中圆圈所示），选择Tools菜单，然后从弹出菜单中选择JavaScript console。一旦打开了控制台，单击Errors即可查看你要查找的JavaScript错误。遗憾的是，Chrome的错误消息有点不好理解。例如，漏掉了结束的引号的错误消息是“UncaughtSyntaxError：Unexpected token ILLEGAL”。这不是很清楚或很友好。实际上，它给人的感觉有点像是你制造了多个错误让Chrome发出尖叫“放机器狗狗！”

1.6.4　访问Safari错误控制台

Safari的错误控制台可以通过Develop菜单打开：⌘Develop→Show Error Console（在Mac上，可以使用快捷键Option-æ-C来打开）。然而，在安装Safari的时候，Debug菜单通常不是打开的，因此，需要几个步骤来打开JavaScript控制台。

图　1-6　Chrome发布了一组强大的开发者工具，这些工具与在Internet Explorer 9和Safari中看到的类似

图　1-7　Safari Error Console显示了JavaScript错误的名字、文件名（和位置），以及Safari所遇到的错误的行号。每个标签或浏览器窗口都有自己的错误控制台，因此，如果已经为一个标签打开了错误控制台，而希望看到另一个标签页或窗口的错误控制台，你需要选择Develop→Error Console

要打开Develop菜单，首先需要访问Preferences窗口。在Mac中，选择Safari→Preferences；在Windows中，单击浏览器右上角的齿轮图标，并选择Preferences。一旦打开Preferences窗口，单击Advanced按钮。选中“Show Develop menu in menu bar”复选框并关闭Preferences窗口。

当重启Safari时，在Mac中，Develop菜单将在屏幕顶部的菜单栏中的Bookmarks和Window菜单中间出现；在Windows中，可以在浏览器的右上角的页面图标下面找到它。选择Develop→Show Error Console来打开控制台（如图1-7所示）。

学习一种编程语言和学习任何新的语言非常相似。要学习单词、理解标点符号，还要掌握一组新的规则。就像说法语的时候要学习法语的语法一样，要使用JavaScript编程，我们必须熟悉JavaScript的语法。本章介绍所有JavaScript程序所依赖的概念。

如果你已经有了JavaScript编程经验，这些概念中的很多对你来说是老生常谈了，因此，你可以直接跳过本章。但是，如果你初次接触JavaScript，或者还不确定很熟悉这些基础知识，本章将向你介绍这些基本的（但是重要的）话题。

第2章　JavaScript的语法

注意：正式语法中，在语句的末尾放置一个分号是可选的，并且，一些程序员省略分号以使得代码较短。但不推荐这么做。省略分号会使得代码更加难以阅读，在某些情况下，还会引起JavaScript错误。如果你想要使JavaScript代码更为紧凑以便可以更快地下载它，请参考第14章。

编写JavaScript程序的一般步骤是，录入一条语句，输入一个分号，按下回车键创建一个新的空白行，录入另一条语句，跟着一个分号。以此类推，直到程序完成。

2.1　语句

JavaScript语句是基本的编程单元，通常表示JavaScript程序中的单个步骤。可以把一条语句看做一个句子一样，就好像成串的句子一起组成一个段落（或一章，或一本书）一样，把语句组合起来得到JavaScript程序。在第1章中，我们见到了语句的几个例子。例如：

alert（'Hello World！'）；

这条语句打开了一个警告窗口，其中显示消息“Hello World！”。在很多情况下，一条语句只是单独的一行代码。每条语句都以一个分号结束，它就像是英语句末的点号。分号表明这个步骤结束，并且JavaScript解释器应该继续下一个操作。

2.2　内置函数

JavaScript和Web浏览器允许在程序中和页面上使用各种命令进行操作。这些命令通常叫做函数（或方法），就像句子中的动词一样。函数使得操作发生。例如，前面输入的alert（）函数就会使得Web浏览器打开一个对话框并显示一条消息。

一些函数是特定于浏览器的，例如在第1章中遇到的alert（）或document.write（）。换句话说，它们只能用于Web页面，因此，在其他使用JavaScript的环境中（例如，当使用脚本编写Adobe应用程序Acrobat、Dreamweaver等时，或者使用Flash的基于JavaScript的ActionScript时），我们不会见到它们。

其他的函数是JavaScript通用的，并且在JavaScript工作的任何地方都有效。例如，isNaN（）是检查一个特定的值是否是数值的函数，当你想要查看访问者是否对一个需要数值答案的问题给出一个数字作为回答的时候（例如，“你希望有多少部件？”），这个函数就派上了用场。我们将在第14章中学习isNaN（）以及如何使用它。

JavaScript有很多不同的命令，我们将会在本书中学习它们。在程序中表示一个函数的快速方法就是使用圆括号。例如，我们可以看出isNaN（）是一条命令，因为isNaN的后面跟着一对圆括号。

此外，JavaScript允许我们创建自己的函数，因此，可以让脚本去完成那些标准的JavaScript命令所没有提供的操作。我们将在第3章学习函数。

2.3　数据类型

我们每天处理不同类型的信息。我们的姓名、食品的价格、医生的办公地址，以及下一个生日的日期，这些对我们来说都是很重要的信息。我们根据所拥有的这些信息来决定做些什么以及如何过日子。计算机程序也一样。它们也依赖信息来做事情。例如，要计算购物车的总金额，就需要知道订购的每一项的价格和数量；而要定制带有访问者姓名的一个页面（“Welcome Back, Kotter”），则需要知道他的名字。

编程语言通常把信息划分为不同的类型，并且以不同的方式来处理每种类型。在JavaScript中，最基本的数据类型是数值、字符串和Boolean（布尔值）。

2.3.1　数值

数值用来计数和计算，可以记录距离暑假还有多少天，或者计算购买两张电影票的总价。数值在JavaScript编程中非常重要：可以使用数值来统计一个访问者访问了Web页面多少次，指定Web页面上的一个项目的具体像素位置，或者确定一个访问者想要订购多少产品。

在JavaScript中，用一个数字字符来表示一个数值，例如5，就是数值5。也可以使用带有小数点的小数，例如5.25和10.3333333。JavaScript甚至允许使用负数，例如—130。

既然数值通常用于计算，那么程序就经常会包含数学运算。我们将在后面的内容中学习操作符，这里只是给出使用JavaScript数值的一个例子。假设要在Web页面上显示值5和15相加的和，可以使用如下的一行代码：

document.write（5+15）；

这行JavaScript代码把两个数相加，并且在Web页面上显示其和（20）。有很多不同的方法来操作数值，我们将从14.3.1节开始学习它们。

2.3.2　字符串

要显示一个名字、一个句子或者任何一串字符，都要使用字符串。字符串只是包含在引号中的一系列字母和其他的符号。例如，“Welcome Hal”和“You are here”都是字符串。第1章把字符串和alert命令一起使用：alert（'Hello World！'）；。

字符串的开始引号告诉JavaScript解释器，接下来的是一个字符串，即一系列的符号。解释器按照字面意义接受符号，而不是试图将它们解释为对JavaScript来说特殊的任何内容（例如命令）。当解释器遇到了最后的引号，它知道已经到达了字符串的末尾，并且继续程序的下一部分。

我们可以使用双引号（"hello world"）或者单引号（'hello world'）把字符串括起来，但是，必须确保在字符串的开始和末尾使用同一种引号（例如，"this is not right'不是一个有效的字符串，因为它以双引号开始，却以单引号结束）。

注意：你会注意到，在本书大部分内容中，我们使用弯引号（""和’'），但是当编写JavaScript代码时，你只需用规范的直引号（""和’'）。代码示例（例如aler t（'Warning, warning！'）；below）使用了不适当的引号类型。要怪就怪Gutenberg——他不知道如何编程。

因此，要弹出一个带有消息“Warning, warning！”的警告框，应该这样写：

alert（'Warning, warning！'）；

或者这样写：

alert（"Warning, warning！"）；

我们经常在程序中使用字符串：当添加警告消息的时候，在处理Web表单上的用户输入的时候，以及操作Web页面的内容的时候。字符串是如此重要，以至于我们从14.1.1节开始专门学习如何使用它们。

常见问题把引号放入字符串

当我们试图创建其中包含引号的一个字符串的时候，程序无法工作。为什么会这样？

在JavaScript中，引号表示一个字符串的开始和结束，即便你不希望它们这么做。当JavaScript解释器遇到第一个引号时，它就对自己说：“哈，这是一个字符串。”当它遇到一个匹配的引号，它就认为已经到达了字符串的末尾。这就是为什么不能创建一个像"He said，"Hello.""这样的字符串。在这个例子中，第一个引号（在单词"He"的前面）表示字符串的开始，但是，一旦JavaScript解释器遇到第二个引号（在单词"Hello"的前面），它就认为这个字符串结束了，因此，最终得到字符串"He said，"，而Hello.部分则导致一个JavaScript错误。

有几种方式可以避免这个难题。最容易的方法就是使用单引号来包围其中带有一个或多个双引号的字符串。例如，'He said，"Hello."’是一个有效的字符串，单引号创建了字符串，其中的双引号则是字符串的一部分。同样，我们可以

使用双引号来包围其中带有一个单引号的字符串，例如"This isn't fair"。

另一种方法是，告诉JavaScript解释器：按照字面意义处理字符串中的引号，即把引号当做该字符串的一部分，而不是字符串的结束。我们使用转义字符来做到这点。如果在引号的前面使用一个反斜杠（\），引号就会当做字符串的一部分来处理。我们可以像这样重写上面的例子："He said，"Hello.\""。在一些情况下，转义字符是唯一的选择。例如，'He said，"This isn\'t fair."’。由于字符串包围在单引号中，单词"isn't"中独立的单引号的前面必须使用一个反斜杠，即isn\'t。

在并非必需的情况下也可以转义引号，作为澄清引号应该按照字面意义处理的一种方式。例如，'He said，"Hello."’。即便不需要转义双引号（因为单引号包围了整个字符串），一些程序员还是这么做了，以便清楚地表示双引号只是一个引号。

2.3.3　布尔值

数值和字符串提供了无限的可能性，而Boolean（布尔值）数据类型则比较简单，只是true或false两个值之一。当创建一个JavaScript程序来智能地响应用户的输入和操作的时候，会遇到Boolean数据类型。例如，如果想要在提交一个表单之前确保访问者提供了一个E-mail地址，可以通过问一个简单的问题来向页面添加逻辑：“Did the user type in a valid email address?”。这个问题的答案是一个Boolean值：要么E-mail地址是有效的（true），要么是无效的（false）。根据这个问题的答案，页面以不同的方式来响应。例如，如果E-mail地址是有效的（true），那么提交表单；如果地址是无效的（false），那么显示一条错误消息并阻止表单提交。实际上，Boolean值如此重要，以至于JavaScript包含了两个专门的关键字来表示这些值，它们是true和false。

我们将在3.1.2节学习在给程序添加逻辑的时候Boolean值是如何发挥作用的。

2.4　变量

可以直接在JavaScript程序中输入数值、字符串或Boolean值，但是只有当我们已经有了所需的信息的时候，这些数据类型才有效。例如，可以让字符串“Hi Bob”出现在一个警告框中，如下所示：

alert（'Hi Bob'）；

但是，只有在访问页面的每个人名字都叫Bob的时候，这条语句才有意义。如果你想要为不同的访问者显示一条个性化信息，名字就要根据访问页面的人而有所不同，例如'Hi Mary'、'Hi Joseph'、'Hi Ezra'等。基本上所有的编程语言都提供了所谓的变量来处理这种情况。

变量是存储信息以便稍后使用或操作它们的一种方式。例如，假设有一个基于JavaScript的弹球游戏，其目标是获取最高分。当一个玩家初次开始游戏，他的分数是0，但是，随着弹球击中目标，得分逐渐增加。在这个例子中，分数（score）是一个变量，因为它从0开始但是随着游戏的进行而变化，换句话说，变量保存可以变化的信息。使用变量的另一个程序的例子如图2-1所示。

把变量看做一种篮子，我们可以在篮子中放置一个物品，看看篮子里的内容，把篮子里的东西倒出来，或者甚至用其他的物品替换篮子中的物品。然而，即便可以改变篮子里的内容，它仍然是同一个篮子。

图　2-1　World's Biggest Pac-Man（http：//worldsbiggest-pacman.com/）网站使用JavaScript来创建一个带有数千个迷宫的Pac-Man游戏。这款游戏记录了你的当前分数和最高分（top），还有吃掉的Pac豆子的总数以及其他的游戏相关数据（right）。这些都是变量的例子，因为它们的值会随着游戏的进行而改变

2.4.1　创建变量

创建变量的过程分两步，包括声明变量和命名变量。在JavaScript中，要创建一个名为score的变量，需要输入：

var score；

第一部分var是一个JavaScript关键字，它创建了（或者用编程的术语来说，声明了）一个变量。这条语句的第二部分score是一个变量。怎么命名变量取决于你，但是，在命名变量的时候要遵守一些规则：

·变量名必须以一个字母、$或_开头。换句话说，不能以数字或标点符号开始一个变量：因此，1thing和＆thing是无效的，但是score、$score和_score都是合适的变量名。

·变量名只能包含字母、数字、$和_。在变量名中的任何地方，都不能使用空格或任何其他的特殊字符：fish＆chips以及fish and chips都是非法的变量名，但是fish_n_chips和plan9是有效的变量名。

·变量名区分大小写。JavaScript解释器把大写字母和小写字母看成是不同的，因此，变量名SCORE和score是不同的，而且它与sCoRE和Score也是不同的。

·避免使用关键字。JavaScript中的一些单词是特定于语言本身：例如，var用来创建一个变量，因此，不能把一个变量命名为var。此外，一些单词，例如alert、document和window，被看做是Web浏览器的特殊属性。如果试图用这些单词作为变量名，最终会产生一个JavaScript错误。可以在表2-1中看到一些保留字的列表。并非所有这些保留字都会在所有的浏览器中引发问题，但是，最好在命名变量的时候避开这些名字。

除了这些规则，还要以使得变量名清楚和有意义为目标。根据将要在变量中存储的数据的类型来命名变量，这会使人们更容易阅读程序，并且立即理解它要做什么。例如，对于记录玩家的游戏分数的变量来说，score是一个很好的名字。变量名s也有效，但是，单个字母“s”对于变量中存储的内容不会有任何启发。

同样，要让变量名容易阅读。当你在一个变量名中使用多个单词的时候，要么在单词之间使用一个下划线，要么把第一个单词之后的每个单词的首字母都大写。例如，imagepath不如image_path或imagePath容易阅读和理解。

2.4.2　使用变量

一旦创建了变量，就可以在其中存储任何类型的数据。为了做到这一点，可以使用=符号。例如，要把数值0存储到一个名为score的变量中，可以编写如下代码：

var score；

score=0；

上面代码的第一行创建了一个变量，第二行把数值0存储到这个变量中。等号叫做赋值操作符，因为它用来把一个值赋给变量。也可以只在一行JavaScript语句创建一个变量并把一个值存储到其中，如下所示：

var score=0；

可以在一个变量中存储字符串、数值和Boolean值：

var firstName='Peter'；

var lastName='Parker'；

var age=22；

var isSuperHero=true；

提示：为了减少录入，可以用一个var关键字声明多个变量，如下所示：

var x, y，z；

甚至可以在一条JavaScript语句中声明多个变量并在其中存储值：

var isSuperHero=true, isAfraidOfHeights=false；

一旦把值存储到一个变量中，就可以使用变量的名字直接访问值。例如，要打开一个警告对话框并且显示存储在变量score中值，应该输入：

alert（score）；

注意，不能把变量和引号一起使用，引号只是和字符串一起使用，因此，代码alert（'score'）将显示单词“score”而不是存储在变量score中的值。现在，你应该明白为什么字符串必须包围在引号中了：对于没有引号的单词，JavaScript解释器将其当做特殊的JavaScript对象（例如alert（）函数）或变量名处理。

常见问题JavaScript中的空格、制表符和回车

JavaScript似乎对录入错误很敏感。如何知道什么时候应该使用一个空格字符，以及什么时候不允许使用空格呢？

通常，JavaScript对空格、回车和制表符的使用并不是很严格。你经常可以漏掉空格，或者甚至添加额外的空格和回车，而不会有什么问题。JavaScript解释器忽略了额外的空格，因此，你可以自由地插入额外的空格、制表符和回车来格式化代码。例如，在赋值操作符的任何一端都不需要一个空格，但是，如果你觉得添加空格更容易阅读的话，也可以添加它们。下面的两行代码都有效：

var formName='signup'；

var formRegistration='newsletter'；

实际上，我们可以插入想要的任意多个空格，甚至是在一条语句中插入回车。因此，如下的两条语句都是有效的：

var formName='signup'；

var formRegistration='newsletter'；

当然，能够插入额外的空格，并不意味着应该这么做。实际上，由于存在额外的空格，前两个例子变得难以阅读。因此，首要的一般规则是，如果额外的空格使得代码容易理解，就添加它。例如，在一次声明多个变量或一次设置多个变量的值的时候，额外的回车能够使得代码更容易阅读。如下是单独一行的代码：

var score=0，highScore=0，player=’’；

然而，有些程序员发现，如果每个变量单独一行的话，阅读起来更容易一些：

var score=0，

highScore=0，

player=’’；

是否认为空格便于阅读，这取决于你，JavaScript解释器只是忽略这些换行。在2.8节和4.8.3节，你将分别看到空格使得带有数组和JavaScript对象直接量的代码更容易阅读的例子。

上述规则有几个重要的例外，例如，不能在字符串中插入回车，换句话说，不能像下面这样把字符串分隔到两行：

var name='Bob

Smith'；

像这样插入一个回车（按下回车键）将会导致一个JavaScript错误并且程序无法运行。

此外，在关键字之间必须放置一个空格：例如，varscore=0和var score=0是不同的。

后一个例子创建了一个名为score的新变量，而前者将值0存储到了一个名为varscore的变量中。JavaScript解释器需要通过var和score之间的空格来识别var关键字，即var score=0。然而，并非必须在关键字和赋值运算符（=）或结束语句的分号等符号之间使用一个空格。

注意：你只需要使用var关键字一次，即第一次创建该变量的时候。此后，可以随意地把一个新值赋给变量而不需要使用var。

2.5　操作数据类型和变量

在一个变量中存储数值或字符串这样的特定信息，通常只是程序中的第一步。大多数程序还操作数据以得到新的结果。例如，为分数增加一个数值以加分，把订购的商品的数目乘上商品的价格得到一个总金额，或者在一条通用性的消息末尾添加一个名字来把它个性化，例如，得到“Good to see you again, Igor”。JavaScript提供了各种操作符来修改数据。操作符只是一个符号或单词，用来把一个或多个值修改为其他的值。例如，可以使用+符号（加法操作符）把数值相加。对于不同的数据类型，有不同类型的操作符。

2.5.1　基本算术

JavaScript支持基本的算术运算，例如加法、除法、减法等。表2-2展示了基本的算术操作符及其使用方法。

你可能习惯了使用一个×表示乘法（例如4×5），但是在JavaScript中，使用*符号把两个数相乘。

也可以在算术运算中使用变量。既然变量只是数值或字符串这样的值的一个容器，使用变量就等同于使用变量的内容。

var price=10；

var itemsOrdered=15；

var totalCost=price*itemsOrdered；

头两行代码创建了两个变量（price和itemsOrdered），并且在每个变量中存储了一个值。第三行代码创建了另一个变量（totalCost），并且把变量price中存储的值（10）和变量itemsOrdered中存储的值（15）相乘的结果存储在其中。在这个例子中，总数（150）存储在变量totalCost中。

这段示例代码还展示了变量的有用之处。假设我们编写一个程序作为一个电子商务Web站点的购物车系统的一部分。在这个程序中，我们需要使用一种特定商品的价格来做各种计算。可以在整个程序中使用实际的价格来编码（例如，假设商品的价格是10元，那么，在用到价格的每个地方都输入10）。然而，如果价格发生变化，我们必须找到使用该价格的每一行代码并进行修改。另外，通过使用变量，可以在程序开始的某个地方设置产品的价格。随后，如果价格发生变化，我们只需要修改定义了产品价格的那一行代码，就可以在整个程序中更新价格：

var price=20；

var itemsOrdered=15；

var totalCost=price*itemsOrdered；

还有很多其他的方法来操作数值（我们将从14.3节开始学习这些方法），但是，你会发现最常用的基本算术操作符都在表2-2中列出了。

2.5.2　操作的顺序

如果同时执行几个数学运算，例如，把几个数值相加然后把总和乘以10，我们需要记住JavaScript解释器执行计算的顺序。一些操作符比其他的操作符优先级高，因此，它们会先计算。如果不注意的话，这一事实可能会产生不想要的结果。例如：

4+5*10

我们可能认为这会直接从左向右计算，4+5等9，9*10等于90。事实不是这样的。实际上先计算乘法，因此，表达式5*10等于50，加上4等于54。乘法（*符号）和除法（/符号）的优先级高于加法（+）和减法（-）。

要确保数学运算按照想要的方式进行，可以使用圆括号组合运算。例如，把上面的表达式重写为下面这样：

（4+5）*10

任何在括号中执行的数学运算都首先进行，因此，在这个例子中，4加上5首先计算并得到结果9，然后乘以10。如果你想要让乘法先进行，显然，要像下面这样编写代码：

4+（5*10）；

2.5.3　组合字符串

组合两个或多个字符串来得到一个单独的字符串，这是一项常见的编程任务。例如，如果Web页面有一个表单，它在一个字段里收集一个人的名字并且在另一个字段里收集他的姓氏，我们需要把两个字段组合起来得到他的完整名字。更进一步，如果想要显示一条消息让用户知道他的表单信息已经提交，我们需要用人的名字和通用性的消息组合起来，得到“John Smith, thanks for your order.”。

组合字符串的操作叫做连接，并且使用+操作符完成。是的，这和用来把两个数值相加的+操作符相同，但是，对于字符串，+执行不同的操作。下面是一个例子：

var firstName='John'；

var lastName='Smith'；

var fullName=firstName+lastName；

在上面代码段的最后一行中，变量的firstName内容和变量lastName的内容组合（或连接）到一起，两个字符串直接连接到一起，结果放置到变量fullName中。在这个例子里，结果字符串是“JohnSmith”，两个名字之间没有空格，因为连接只是把字符串连到一起。在很多情况下（例如这个例子），我们需要在想要组合的字符串之间添加一个空格：

var firstName='John'；

var lastName='Smith'；

var fullName=firstName+’’+lastName；

上述代码段的最后一行中的’’是一个单引号后面跟着一个空格，然后是一个结束单引号。这段代码只是一个包含了空格的字符串。在这个例子中，当把这个字符串放置于两个变量之间的时候，它产生了一个字符串“John Smith”。最后的一个例子也展示了可以一次性组合两个以上的字符串，在这个例子中，我们组合了3个字符串。

注意：记住，变量只是一个容器，它能够保存任何类型的数据，例如，一个字符串或一个数值。因此，当你组合两个字符串变量的时候（firstName+lastName），这和将两个字符串连接起来（'John'+'Smith'）是一样的。

2.5.4　组合数值和字符串

大多数算术操作符仅对数值有意义。例如，把2和字符串'eggs'相乘没有任何意义。如果尝试这个例子，最终将得到一个特殊的JavaScript值NaN，它表示“不是一个数值”。然而，有的时候，我们可能想要把一个字符串和数值组合起来。例如，假设我们想要在Web页面上显示一条消息，指出访问者已经访问Web站点多少次。他访问的次数是一个数值，但是，这条消息是一个字符串。在这种情况下，我们使用+操作符来做两件事情：把一个数值转换为一个字符串，并且将它和另一个字符串组合起来。下面是一个例子：

var numOfVisits=101；

var message='You have visited this site'+numOfVisits+'times.’；

在这个例子中，message包含了字符串“You have visited this site 101 times”。JavaScript解释器认出了这其中包含了一个字符串，因此，它意识到，这不是要做任何数学运算（不是加法）。相反，它把+当做连接操作符，并且同时意识到这个数值也应该转换为一个字符串。

这个例子似乎是在同一条消息里显示文字和数值的一种好方法。在这个例子中，数值显然是组成完整句子的一连串字母的一部分，并且，无论何时对一个字符串值和一个数值使用+操作符，JavaScript解释器都会把数值转换为一个字符串。

这种功能叫做自动类型转换，然而，它可能会引发问题。例如，如果访问者在表单上通过输入数值（例如2）来回答一个问题（“你想要多少双鞋”），输入值被当做一个字符串’2’。那么，你会遇到这样一种情况：

var numOfShoes=’2’；

var numOfSocks=4；

var totalItems=numOfShoes+numOfSocks；

我们期待totalItems中存储的结果是6（两双鞋+4双袜子）。相反，由于numOfShoes中的值是一个字符串，JavaScript解释器把变量numOfSocks中的值也转换为一个字符串，并且，我们最终在变量totalItems中得到了字符串’24’。有几种方法可以防止这种错误：

首先，把+添加到包含数值的字符串的开始，例如：

var numOfShoes=’2’；

var numOfSocks=4；

var totalItems=+numOfShoes+numOfSocks；

在一个变量的前面添加+符号（确保变量和+符号之间没有空格），这就告诉JavaScript解释器尝试把字符串转换为数值，如果字符串像’2’一样只包含数值，我们最终把这个字符串转换成了一个数值。在这个例子中，我们最终得到6（2+4）。另一种技术是使用Number（）命令，如下所示：

var numOfShoes=’2’；

var numOfSocks=4；

var totalItems=Number（numOfShoes）+numOfSocks；

Number（）在可能的情况下把一个字符串转换为数值（如果字符串只是字母且不是数值，我们会得到一个NaN值，表示不能把字母转换为一个数值）。

通常，在获取页面访问者的输入时最常遇到作为字符串的数值，例如，获取访问者输入一个表单字段中的值。因此，如果你需要使用一个表单的输入或访问者输入的其他信息来做加法运算，确保首先对它使用Number（）命令。

注意：只有在将一个数值和包含数值的一个字符串相加的时候，才会发生这个问题。如果你试图将变量numOfShoes和包含数值的一个变量（例如，shoePrice）相乘，JavaScript解释器将会把numOfShoes中的字符串转换为一个数值，然后将其与变量shoePrice相乘。

2.5.5　修改变量中的值

变量很有用，因为它们可以存储随着程序运行而变化的值，例如，随着游戏的进行而改变的分数。那么，我们怎么修改变量的值呢？如果想要替换一个变量中的值，就给该变量赋一个新值。例如：

var score=0；

score=100；

然而，我们通常想要保持变量中的值，并且只是以某种方式给它添加些东西或者改变它。例如，对于一个游戏分数，我们不想只是给一个新的分数，总是想从当前分数中增加或减去一个值。要增加变量的值，可以使用这个变量的名字作为操作的一部分，如下所示：

var score=0；

score=score+100；

最后一行代码乍看上去有些令人混淆，但是，它使用了一种非常常见的技术。它是这么工作的：=号右边的所有操作首先进行，即score+100部分先执行。翻译过来，它的含义是“取出score中当前存储的值（0）并且给它加上100”。这个操作的结果随后又存储回变量score中。这两行代码的最终结果是，变量score现在拥有了值100。

同样的逻辑也适用于其他的算术操作符，例如减法、除法或乘法：

score=score-10；

score=score*10；

score=score/10；

实际上，对一个变量中的值执行数学运算，然后把结果存储回该变量非常简单，4种主要的运算都有快捷方式来完成这一操作，参见表2-3。

当把一个字符串连接到一个变量的时候，同样的规则也适用。例如，假设有一个变量，其中有一个字符串，并且我们想要把另外一个字符串添加到该变量中：

var name='Franklin'；

var message='Hello'；

message=message+’’+name；

同样，也有把字符串连接到变量的快捷操作符。+=操作符把=号右边的字符串值添加到变量的字符串的末尾。因此，上面代码的最后一行也可以写成这样：

message+=’’+name；

在操作字符串的时候，并且在本书中，都会常常看到+=操作符。

2.6　教程：使用变量创建消息

在这个教程中，我们将使用变量在Web页面上显示一条消息。

注意：为了学习本章中的教程，你需要从本书配套Web站点http：//sawmac.com/js2e/下载教程文件。请参见1.3节中“注意”部分的详细说明。

1.在文本编辑器中打开chapter02目录下的文件use_variable.html。

这个页面只是一个基本的HTML文件，其中带有一些简单的使用CSS的设计。它还没有任何的JavaScript。我们可以使用变量在Web页面上显示消息。

2.找到＜h1＞标签（在文件中部略微偏下的位置），添加开始＜script＞标签和结束＜script＞标签，代码如下所示：

＜h1＞Using a Variable＜/h1＞

＜script＞

＜/script＞

现在，我们应该熟悉这段HTML了：它只是让页面准备好等待编写脚本。

注意：这个页面使用HTML5作为doctype。如果你使用XHTML 1.0或HTML 4.01，像下面这样把type=”javascript”添加到＜script＞标签：＜script type=”text/javascript”＞。这个步骤不是为了使脚本能够工作而必需的，只是为了让页面能够通过W3C验证器（参见本书“前言”部分以了解更多关于验证的内容）。

3.在＜script＞标签之间输入：

var firstName='Cookie'；

var lastName='Monster'；

这就创建了两个变量firstName和lastName，并且在其中存储了两个字符串值。接下来将把两个字符串加在一起，并且在Web页面上显示结果。

4.在两个变量声明下面输入：

document.write（'＜p＞'）；

第1章已介绍过，document.write（）命令直接把文本添加到Web页面。在这个例子中，我们使用它来把HTML标签显示在页面上。我们为该命令提供了一个字符串’＜p＞’，它输出的字符串正是我们在HTML代码中录入的内容。提供HTML标签作为document.write（）命令的部分也没问题。在这个例子中，JavaScript为段落添加了一个开始标签，以存储要在页面上显示的文本。

注意：有比document.write（）效率更高的方法可以把HTML添加到Web页面。我们将在本书4.7节学习。

5.按回车键后输入如下JavaScript代码：

document.write（firstName+’’+lastName）；

这里使用在步骤3中所创建的变量中所存储的值。+操作符允许把几个字符串组合到一起，产生一个较长的字符串，然后document.write（）命令把该字符串显示到页面。在这个例子中，对于存储在firstName中的值'Cookie'，先添加了一个空格字符，然后，再添加lastName的值'Monster'。结果就是字符串'Cookie Monster'。

6.再次按下回车键后输入document.write（'＜/p＞'）；。

完成后的脚本如下所示：

＜script type="text/javascript"＞

var firstName='Cookie'；

var lastName='Monster'；

document.write（'＜p＞'）；

document.write（firstName+’’+lastName）；document.write（'＜/p＞'）；

＜/script＞

7.在Web浏览器中预览页面，享受劳动成果（如图2-2所示）。

单词“Cookie Monster”应该出现在标题“Using a Variable”的下方。如果你没有看到任何内容，可能是由于代码存在录入错误。把你录入的脚本和上面的脚本进行比较，并参考1.6小节介绍的关于使用Firefox、Safari、Chrome或IE 9调试脚本的技巧。

8.返回文本编辑器，把脚本的第二行代码改为：

var lastName='Jar'；

保存页面并在We b浏览器中预览。瞧，现在的消息是CookieJar（文件complete_use_variable.html中有这段脚本的一个可工作的副本）。

图　2-2　虽然写出“Cookie Monster“可能并非我们挑选一本JavaScript图书的理由，但这段脚本确实展示了一个重要的概念：如何在JavaScript中创建和使用变量

2.7　教程：请求信息

在上面的脚本中，我们看到了如何创建变量，但是，对于变量如何响应用户并产生独特的、定制的内容，我们还没有太多经验。在本教程中，我们将学习如何使用prompt（）命令收集来自用户的信息并根据该输入改变页面的显示。

1.在文本编辑器中打开chapter02目录下的prompt.html文件。

为了让你可以更快地编程，＜script＞标签已经添加到这个文件了。你可能注意到这里有两对＜script＞标签，一个在前头，一个在正文。我们要添加的JavaScript将要做两件事情。首先，它会打开一个对话框，要求用户输入一个问题的答案；其次，在Web页面的正文中，显示使用用户响应的一条定制消息。

2.在文档前面的第一组＜script＞中输入如下粗体所示代码：

＜script＞

var name=prompt（'What is your name?’，’'）；

＜/script＞

prompt（）函数产生一个和alert（）函数类似的对话框。然而，prompt（）函数不仅显示一条消息，而且可以获取一个答案（如图2-3所示）。此外，要使用prompt（）函数，可以在圆括号中提供以逗号分开的两个字符串。图2-3显示了对这两个字符串进行了什么操作：第一个字符串作为对话框的问题显示（在这个例子中，是“What is your name?”）。

注意：如果没有在IE 7的浏览器设置中激活，IE 7不允许使用prompt（）方法。好在IE 7的用户数量越来越少了。

图　2-3　prompt（）函数是获取用户输入的一种方式。我们为该函数提供两个字符串来使其工作，一个字符串作为问题显示，另一个字符串是用来预先填满提示框的文本

第二个字符串显示访问者在字段中的输入。这个例子使用了所谓的空字符串，它只是两个单引号（''），并且产生一个空白文本字段。然而，我们可以为第二个字符串提供一个有用的说明，例如"Please type both your first and last names"，并且它将在字段中显示。遗憾的是，在输入自己的信息之前，访问者需要先删除文本字段中的文本。

prompt（）函数返回包含访问者在对话框中输入的内容的一个字符串。在这一行JavaScript代码中，结果存储在一个名为name的新变量中。

注意：很多函数会返回一个值。换句话说，这只是意味着函数在执行之后提供某些信息。我们可以选择忽略这些信息，或者将其存储在一个变量中供随后使用。在这个例子中，prompt（）函数返回一个字符串，这使我们可以将这个字符串存储在变量name中。

3.保存页面并在Web浏览器中预览。

当载入页面的时候，我们将看到对话框。注意，在填写对话框并单击OK按钮之前，没有发生其他的事情，我们甚至不会看到Web页面。我们还会注意到，在单击了OK按钮之后，也没有发生什么更多的事情，这是因为此刻我们只是收集并存储了响应，还没有在页面上使用响应。接下来实现这点。

4.返回文本编辑器，找到第二组＜script＞标签并添加粗体所示的代码：

＜script＞

document.write（'＜p＞Welcome'+name+’＜/p＞'）；

＜/script＞

这里利用了访问者提供的信息。像本书2.6节中的脚本一样，我们组合了几个字符串（一个开始的段落标签和文本、变量的值以及一个结束的段落标签），并且在Web页面上显示结果。

5.保存页面并在Web浏览器中预览。

当提示对话框出现的时候，输入一个名字并单击OK按钮。注意，输入的名字出现在Web页面中（如图2-4所示）。重新载入Web页面并输入一个新的名字，页面变化了。这正是一个好的变量应该起到的作用。

图　2-4　变量的力量：这个页面根据访问者的响应来定制消息

2.8　数组

上一小节中使用的简单变量只是保存了一段信息，例如一个数字或一个字符串值。当你只需要记录分数、年龄或总价这样的单个内容的时候，它们做得很好。然而，如果需要存储一组相关的项目，例如，一个星期中的所有的星期几名称，或者Web页面上所有图像的一个列表，此时简单的变量并不是很方便使用。

例如，假设已经创建了一个JavaScript购物车系统，它记录了访问者想要购买的商品，如果想要使用简单的变量来记录访问者添加到购物车中的所有商品，必须像下面这样编写代码：

var item1='Xbox 360’；

var item2='Tennis shoes'；

var item3='Gift certificate'；

但是，如果想要添加更多的商品，该怎么办呢？必须创建更多的变量，item4、item5等。并且，由于不知道访问者想要购买多少商品，我们确实不知道应该创建多少变量。

好在JavaScript为记录项目列表提供了一种更好的方法——数组。数组是在单个地方存储多个值的一种方式。把数组当成是一个购物列表。当需要去杂货店的时候，我们坐下来并写下要购买的商品的列表。如果此前几天刚刚去过杂货店购物，这个列表可能只包含一些商品，但是，如果储存的食品已经很少了，这个购物列表可能相当长。不管列表中有多少商品，它还都只是一个列表。

不使用数组的话，我们必须为列表中的每个商品创建一个变量。例如，想象一下，我们无法在单独的一张纸上记录下商品列表，而是必须使用一叠纸片，每张纸片用于记录要购买的一项商品。如果想要添加其他需要购买的商品，就需要一张新的纸片，然后，在购物的时候按照每个纸片去进行（如图2-5所示）。这就是简单变量的工作方式。但是，通过使用数组，我们可以创建商品的一个单独的列表，并且甚至可以随时添加、删除或修改商品。

2.8.1　创建数组

要创建数组并在数组中存储项目，首先声明数组的名称（就像我们对一个变量所做的一样），然后提供逗号分隔的值的一个列表：每个值表示列表中的一个项目。和变量一样，数组的名称也取决于我们自己，但是需要遵守2.4.1节列出的命名规则。要表示一个数组，可以把项目列表放在开始方括号和结束方括号之间：

图　2-5　数组提供了一种简单的、有组织的方式来记录相关商品的列表。向列表中添加其他商品就像在列表的下端写入一个新的商品

var days=['Mon'，'Tues'，'Wed'，'Thurs'，'Fri'，'Sat'，'Sun']；

方括号[]非常重要，它们告诉JavaScript解释器将要处理一个数组。我们可以创建一个空的没有任何元素的数组：

var playList=[]；

创建一个空的数组，等同于2.4.1小节所介绍的声明一个变量。当在程序运行时才向数组添加项目的情况下，我们将创建一个空的数组。例如，上面的数组可能用来记录某人从Web页面上的一个列表中选择的歌曲，此前我们不知道他将要选择哪些歌曲，因此声明了一个空的数组并且随后用他选择的歌曲来填充项目（向数组添加项目将在2.8.3节介绍）。

注意：在浏览其他人的JavaScript程序（或其他的JavaScript图书）的时候，我们可能会遇到另一种创建数组的方式，即使用Array关键字，如下所示：

var days=new Array（'Mon'，'Tues'，'Wed'）；

这种方法也是有效的，但是，本书中使用的方法（叫做数组直接量）需要更少的录入和更短的代码。

可以把值的任何混合形式存储到一个数组中。换句话说，数值、字符串和Boolean值都可以出现在同一个数组中：

var prefs=[1，223，'www.oreilly.com'，false]；

注意：我们甚至可以把数组和其他对象作为元素存储到数组中。这有助于存储复杂的数据。

上面这个数组的例子展示了在单独一行代码内创建数组。然而，如果我们已经有了很多要添加的项目，或者项目是较长的字符串，那么，试图在一行代码中输入所有内容会使得程序难以阅读。在这种情况下，很多程序员选择另一种方法，即通过数行代码创建一个数组，如下所示：

var authors=['Ernest Hemingway'，

'Charlotte Bronte'，

'Dante Alighieri'，

'Emily Dickinson'

]；

正如2.4.2节中的“常见问题：JavaScript中的空格、制表符和回车”部分所介绍的，JavaScript解释器会忽略额外的空格和分行符号，因此，即便这段代码分5行显示，它仍然是一条语句，最后一行的分号表明了这一点。

提示：要让名字像上面那样排列，可以输入第一行var authors=['Ernest Hemingway'，按下回车键，然后多次按下空格键直到换到新行，输入下一个值'Charlotte Bronte'，。

2.8.2　访问数组中的项目

我们可以使用变量名来访问一个简单变量的内容。例如，alert（lastName）打开一个警告框，该警告框带有变量lastName中存储的值。然而，因为数组可以存储多个值，我们不能只使用其名字来访问其中包含的项目。一个叫做索引的唯一编号表示了数组中每个项目的位置。要访问数组中的一个特定项目，我们使用项目的索引编号。例如，假设已经创建了一个数组，其中包含了星期几的缩写，并且我们想要打开一个警告框来显示第一个项目。可以这样编写代码：

var days=['Mon'，'Tues'，'Wed'，'Thurs'，'Fri'，'Sat'，'Sun']；

alert（days[0]）；

这段代码打开一个其中显示'Mon'的警告框。数组是从0开始索引的，意味着数组中的第一项的索引值为0，第二项的索引值为1，换句话说，从项目在列表中的位置减去1就得到了其索引值，第5项的索引值是5—1，即4。当你刚开始编程时，从0开始索引确实容易令人混淆，因此，表2-4展示了（上面的例子中的）数组days是如何索引的、它包含的值以及如何访问每个值。

可以通过给索引位置赋一个新值来改变数组中一个项目的值。例如，要把一个新值放入数组days的第一个项目中，可以这样编写：

days[0]='Monday'；

由于数组中的最后一项的索引编号总是比数组中的项目的总数小1，因此，要访问最后一项，只需要知道数组中有多少项目就可以了。这是一项容易的任务，因为每个数组都有一个length属性，它包含了数组中项目的总数。要访问length属性，可以在数组名称的后面添加一个点号，然后跟着length。例如，days.length返回数组days中包含的项目数（如果创建了一个不同的数组，例如playList，获得其长度的方法是：playList.length）。因此，可以使用简短的JavaScript代码来访问数组的最后一项所存储的值：

days[days.length-1]

上面的代码段说明了我们不一定必须为索引提供一个直接量数值（例如，days[0]中的0）。我们也可以提供一个返回有效数值的表达式：在这个例子中，days.length-1是一个简短的表达式，它首先获取days数组中项目的数目（在这个例子中是7），并且用它减去1。因此，在这个例子中，days[days.length-1]变成了days[6]。

还可以使用包含数值的一个变量来作为索引：

var i=0；

alert（days[i]）；

上面代码的最后一行等价于alert（days[0]）；。当在3.3节用到循环的时候，会发现这一技术特别有用。

2.8.3　向数组添加项目

假设我们创建了一个数组来记录用户在Web页面上单击的项目。每次用户单击页面，就向数组添加一个项目。JavaScript提供了向数组添加内容的几种方式。

在数组的末尾添加一个项目

要在数组的末尾添加一个项目，可以使用2.8.2节介绍的索引表示法，使用比列表中的最后一项大1的一个索引值。例如，假设已经创建了一个名为properties的数组：

var properties=['red'，’14px'，'Arial']；

此时，数组有3个项目。别忘了，可以使用比项目总数小1的一个索引来访问数组最后一项，因此，在这个例子中，数组中的最后一项是properties[2]。要添加另外一项，可以这么做：

properties[3]='bold'；

这行代码在数组的第4个位置插入了'bold'，这就创建了拥有4个元素的一个数组，即['red'，’14px'，'Arial'，'bold']。注意，当添加一个新项目的时候，我们使用了等于数组中当前元素的总数的一个索引值，因此，可以通过使用数组的length属性作为索引从而确保总是在数组的末尾添加一项。例如，可以把上面的这行代码重写成如下：

properties[properties.length]='bold'；

也可以使用数组的push（）命令，它将我们所提供的圆括号之间的内容添加到数组的末尾。和使用length属性一样，通过在数组名的后面添加一个点号再跟着push（）来使用该命令。例如，下面是在properties数组末尾添加一项的另一种方法：

properties.push（'bold'）；

在圆括号中提供的任何内容（在这个例子中是字符串'bold'），都将作为一个新项目添加到数组的末尾。我们可以使用任何一种类型的值，如字符串、数值、Boolean或者甚至一个变量。

push（）命令的一个优点是：允许向数组中添加多个项目。例如，假设想要在名为properties的数组末尾添加3个值，可以像下面这样做：

properties.push（'bold'，'italic'，'underlined'）；

在数组的开头添加一项

如果想要在数组的开头添加一项，可以使用unshift（）命令。下面是在properties数组的开头添加'bold'值的一个例子：

var properties=['red'，’14px'，'Arial']；

properties.unshift（'bold'）；

这段代码运行之后，数组properties包含4个元素：['bold'，'red'，’14px'，'Arial']。和push（）一样，我们可以使用unshift（）在数组的开始处插入多个项目：

properties.unshift（'bold'，'italic'，'underlined'）；

注意：确保使用的时候，在数组名的后面跟着一个点号和想要使用的方法。换句话说，push（'new item'）是无效的。必须先使用数组名（就是在创建数组时给数组的名字），然后跟着一个点号，然后是方法，例如：authors.push（'Stephen King'）；。

选择如何向数组添加项目

到目前为止，本章介绍了向数组添加项目的3种方法。表2-5比较了这些技术。这些命令中的每一个都完成相似的任务，因此可以根据程序的情况来选择其中一种。如果存储在数组中的项目的顺序无关紧要，那么，这些方法中的任何一种都可以使用。例如，假设有一个产品图片的页面，并且单击一个图片就会把产品添加到购物车中。可以使用一个数组存储购物车商品。商品出现在购物车（或数组）中的顺序无关紧要，因此可以使用上述技术中的任何一种。

然而，如果创建一个数组来记录某些事情发生的顺序，那么就与选择的方法有关系了。例如，假设要创建一个页面，它允许访问者通过单击页面上的歌名来创建一个歌曲播放列表。既然播放列表中的歌曲按照它们应该播放的顺序来排列，那么，顺序是很重要的。因此，访问者每次单击一首歌，这首歌曲的名字都应该出现在播放列表的末尾（因此，它将会是最后一首要播放的歌曲），那么可以使用push（）方法。

push（）和unshift（）命令返回一个值。具体来说，一旦push（）和unshift（）完成了任务，它们就可以提供数组中项目的数目。例如：

var p=[0，1，2，3]；

var totalItems=p.push（4，5）；

这段代码运行之后，totalItems中存储的值是6，因为现在p数组中有6个项目。

高级用户提示创建队列

向数组添加项目的方法（push（）和unshift（））以及从数组删除项目的方法（pop（）和shift（））通常一起使用，以提供按照项目创建的顺序来访问它们的方法。典型的例子就是音乐播放列表。歌曲按照它们在列表中出现的顺序播放，因此，先播放第一首歌并且随后从队列中删除它。这一安排类似于排队等候看电影。当你到达电影院，就在队列的末尾排队，当电影快要开始的时候，大门打开，队列中的第一个人首先进去。在编程领域，这一概念叫做FIFO（First In, First Out，先进先出）。可以使用数组以及push（）和shift（）方法来模拟这种安排。例如，假设有一个名为playlist的数组。要在队列的末尾添加一首新的歌曲，使用push（）的方法如下：

playlist.push（'Yellow Submarine'）；

要获取下一首将要播放的歌曲，可以获取列表中的第一项，如下所示：

nowPlaying=playlist.shift（）；

这段代码从数组中删除了第一个项目，并且将它存储在名为nowPlaying的变量中。FIFO概念对于创建和管理诸如播放列表、任务列表或者图像幻灯片这样的队列很有用。

2.8.4　从数组删除项目

如果想要从数组的末尾或开头删除一个项目，可以使用pop（）或shift（）命令。这两条命令都会从数组删除一个项目，pop（）命令从数组末尾删除项目，而shift（）从数组开始删除一个项目。表2-6比较了这两种方法。

与push（）和unshift（）一样，一旦pop（）和shift（）完成了从数组删除项目的任务，会返回一个值。实际上，它们返回了刚刚删除的值。例如，下面的代码删除一个值并将其存储到removedItem变量中：

var p=[0，1，2，3]；

var removedItem=p.pop（）；

代码运行后，removedItem的值是3并且数组p现在包含[0，1，2]。

注意：本章的文件包含了一个Web页面，可以用来交互地测试不同的数组命令。该文件的名字是array_methods.html，位于教程的testbed目录下。在Web浏览器中打开这个文件，并且单击Web页面上的各种按钮，看看数组方法是如何工作的（另外，页面的所有漂亮的交互功能都得益于JavaScript）。

2.9　教程：编写使用数组的页面

本书的很多脚本中用到了数组，但是，要快速体验一下创建和使用数组，请阅读这个简短的教程。

注意：参见本书1.3节的“注意”部分中关于如何下载教程文件的说明。

1.在文本编辑器中打开chapter02目录下的arrays.html文件。我们从直接创建包含4个字符串的一个数组开始。和前面的教程一样，这个文件还在头部和正文区域包含了＜script＞标签。

2.在第一组＜script＞标签中输入如下粗体所示代码：

＜script＞

var authors=['Ernest Hemingway'，

'Charlotte Bronte'，

'Dante Alighieri'，

'Emily Dickinson'

]；

＜/script＞

这段代码包含一条JavaScript语句，但是，它分为5行。要创建这条语句，输入第一行var authors=['Ernest Hemingway'，按下回车键，然后按下空格键直到对齐到’的下方（大约16个空格），然后输入'Charlotte Bronte'，。

注意：大多数HTML编辑器针对HTML和JavaScript代码使用Courier或Courier New这样的等宽字体。在等宽字体中，每个字符和其他字符都具有同样的宽度，因此，很容易对齐各列（就像本例中的所有作者名）。如果你的文本编辑器没有使用Courier或类似字体，可能无法把名字很好地排列对齐。

2.8.1节介绍过，当你创建拥有很多元素的数组时，如果将它分为数行，可以使代码更容易阅读。直到第5行末尾才有一个分号，由此可以看出这是一条语句。

这行代码创建了一个名为authors的数组，并且把4个作者的名字（4个字符串值）存储到数组中。接下来将访问该数组的一个元素。

3.找到第二对＜script＞标签，然后添加粗体所示代码：

＜script＞

document.write（'＜p＞The frst author is＜strong＞'）；

document.write（authors[0]+’＜/strong＞＜/p＞'）；

＜/script＞

第一行开始了一个新的段落，其中有一些文本以及一个开始＜strong＞标签，这只是普通的HTML。下一行显示了存储在authors数组的第一项中的值，并且显示了结束标签＜/strong＞和＜/p＞，以创建一个完整的HTML段落。要访问数组中的第一项，可以使用0而不是1作为索引：authors[0]。

此时，保存文件并在Web浏览器中预览是个好主意。你应该看到屏幕上显示了“The first author is Ernest Hemingway”。如果没有看到，那么在步骤2或步骤3创建数组的时候可能有输入错误。

注意：别忘了使用1.6节所介绍的浏览器错误控制台来帮助找到任何JavaScript错误的源头。

4.回到文本编辑器，然后把如下粗体所示的两行代码加入脚本中：

document.write（'＜p＞The last author is＜strong＞'）；

document.write（authors[4]+’＜/strong＞＜/p＞'）；

除了显示不同的数组项目，这个步骤确实和前面的相同。保存页面并在浏览器中预览。将会看到“undefined”而不是一个作者的名字（如图2-6所示）。不要担心，这是故意的。别忘了，数组的索引值是从0开始的，因此，最后一项实际上是数组中项目的总数减去1。在这个例子中，authors数组中存储了4个字符串，因此，最后一项实际上应该使用authors[3]来访问。

注意：如果试图使用不存在的一个索引值来读取一项，将会得到JavaScript的“undefined”值。这意味着，该索引位置没有存储值。

有一种简单的方法可以获取数组中的最后一项，而不管数组中存储了多少项。

图2-6：如果想要访问不存在的一个数组元素，那么，将会得到值“undefined”

5.回到文本编辑器并编辑刚刚输入的代码。删除4，并在其位置添加粗体所示的代码：

document.write（'＜p＞The last author is＜strong＞'）；

document.write（authors[authors.length-1]+’＜/strong＞＜/p＞'）；

还记得吧，2.8.3节介绍过，数组的length属性存储了数组中项目的数目。因此，authors数组中项目的总数可以通过authors.length这段代码获取。此时，在这段脚本中，结果是4。

知道了数组中最后一项的索引值总是数组中项目的总数减去1，就可以通过从总数中减去1来获得最后一项的索引值：authors.length-1。在访问一个数组的最后一项的时候，可以把这个简短的表达式作为索引值提供，即authors[authors.length-1]。

最后，在数组的开始再添加一个项目。

6.在步骤5中添加的代码之后添加另外一行代码：

authors. unshift（'Stan Lee'）；

2.8.3节介绍过，unshift（）方法在数组的开始处添加一个或多个项目。在执行这段代码后，author数组现在将是['Stan Lee'，'Ernest Hemingway'，'Charlotte Bronte'，'Dante Alighieri'，'Emily Dickinson']。

最后，我们在页面显示新添加的项。

7.再添加3行代码（如下粗体所示），以便最终的代码如下所示：

document.write（'＜p＞The first author is＜strong＞'）；

document.write（authors[0]+’＜/strong＞＜/p＞'）；

document.write（'＜p＞The last author is＜strong＞'）；

document.write（authors[authors.length-1]+’＜/strong＞＜/p＞'）；

authors.unshift（'Stan Lee'）；

document.write（'＜p＞I almost forgot＜strong＞'）；

document.write（authors[0]）；

document.write（'＜/strong＞＜/p＞'）；

保存文件并在Web浏览器中预览。将会看到如图2-7所示的内容。如果没有看到这些内容，别忘了Web浏览器的错误控制台可以帮助你找到错误（参见1.6节）。

图　2-7　好了，Stan Lee可能不是你心目中的文学巨匠，但是，至少他能帮助你学习数组是如何工作的

2.10　对象的简单介绍

到目前为止，我们在本书中了解到，可以使用document.write（）命令将一些内容写到Web页面中，可以确定一个数组中有多少个项目，只要输入数组的名字，后面跟着一个点号和单词“length”就可以了，例如，days.length。你可能会问，这些点号是干什么的？我们已经在不知道JavaScript的这一特殊语法的情况下学习3章内容了，现在该来了解一下它了。

对于JavaScript语言的很多元素，我们已经建立起了概念，包括Web页面的元素，也就是对象。当然，实际生活中也充满了对象，例如，狗和小汽车。大多数对象都由不同的部分组成：狗有尾巴、脑袋和4条腿；小汽车有车门、车轮、头灯、喇叭等。一个对象可以做一些事：例如小汽车可以运送乘客，狗可以吠叫。实际上，即便对象的一个部分也能做些事情：例如，尾巴可以摆动，喇叭可以鸣响。表2-7展示对象、对象的部分和对象的行为之间的关系。

JavaScript的世界也充满了对象：浏览器窗口、文档、字符串、数值和日期，这只是几个例子。和现实世界的对象一样，JavaScript对象也由不同的部分组成。用编程的术语来说，对象的这些部分叫做属性。对象能够执行的行为叫做方法，也就是特定于一个对象的函数（就像内置的alert（）函数），参见表2-8。

注意：你总是可以将属性和方法区分开来，因为方法以一对圆括号结束，例如write（）。

JavaScript中的每个对象都有自己的一组属性和方法。例如，数组对象有一个名为length的属性，文档对象有一个名为write（）的方法。要访问一个对象的属性，或者执行它的某个方法，使用点语法，也就是点号。点号将对象及其属性和方法连接起来。例如，document.write（）表示“运行document对象的write（）方法”。如果现实世界也如此，可以像这样让一条狗来摇尾巴：dog.tail.wag（）（当然，在实际生活中，狗干得比这好很多）。

就像在实际生活中你可能养几条狗一样，JavaScript程序也可以有同一种对象的多个版本。例如，假设创建了如下的两个简单变量：

var first_name='Jack'；

var last_name='Hearts'；

实际上，这创建了两个不同的字符串对象。字符串有自己的一组属性和方法，这些和其他对象（例如，日期对象，将在本书14.4节介绍）的方法和属性不同。当你创建了一个对象（也叫做创建了该对象的一个实例），可以访问该对象的所有属性和方法。

注意：我们已经遇到过另一个对象，它叫做window对象，表示浏览器窗口本身。它基本上是Web页面以及页面上的所有其他内容的容器对象。例如，alert（）和prompt（）都是窗口对象的方法，并且可以写成这样：window.alert（）和window.prompt（）。然而，既然窗口对象总是表示一个Web页面，那么可以省略其名称，因此，alert（'hello'）和window.alert（'hello'）是一样的。

无论何时，当你创建一个新的变量并向其中存储一个值的时候，实际上都创建了特定类型的对象的一个实例。因此，如下的JavaScript代码，每一行都创建了不同类型的JavaScript对象：

var first_name='Bob'；//a string object

var age=32；//a number object

var valid=false；//a Boolean object

实际上，当你修改变量中存储的信息的类型的时候，也修改了其对象的类型。例如，如果你创建了一个名为data的变量，用来存储一个数组，然后，将一个数值存入该变量，你已经将变量的类型从一个数组修改为一个数值对象：

var data=false；//a Boolean object

data=32；//changes to number object

对象、属性、方法和点语法的概念，初看上去有点奇怪。然而，由于它们是关系到JavaScript如何工作的基础概念，并且也是使用jQuery所必需的，因此，你将会很快熟悉它们。

提示：JavaScript包含了一个特殊的关键字，用来判断对象的类型（字符串、数值、Boolean等）。它叫做typeof操作符，并且放在要确定其中的对象的类型的那个变量的前面。例如：

var data=32；

alert（typeof data）；//"number"appears in alert window

随着你继续阅读本书，请记住下面的几点：

·JavaScript的世界充满了很多不同类型的对象。

·每个对象有自己的属性和方法。

·使用点语法来访问对象的属性或调用对象的方法，例如，document.write（）。

2.11　注释

有的时候，当你思考编程的时候，会觉得自己理解程序中所进行的所有事情。代码的每一行都有意义，并且更好的是，它们还都能工作。但是，一个月或两个月以后，当你的老板或客户请你对自己所编写的漂亮脚本做一个更改或添加一项新功能的时候，你可能会看着似曾相识的JavaScript代码抓耳挠腮：这个变量的作用是什么？为什么我要这样写程序呢？程序的这一段做什么事情呢？

我们很容易忘记一个程序是如何工作的以及为什么按照这样的方式来编写代码。

好在大多数编程语言为程序员提供了一种方法，使得他们可以给自己或其他阅读代码的人留下提示。JavaScript允许在代码中添加注释。如果你使用过HTML或CSS注释，那么也会熟悉JavaScript的注释。注释是一行或多行有用的提示JavaScript解释器忽略它们，但是它们对于程序如何工作可以提供有价值的信息。

JavaScript注释的语法和CSS相同。要创建单行注释，以双斜杠开始：

//this is a comment

我们也可以在一条JavaScript语句的后面添加注释：

var price=10；//set the initial cost of the widget

JavaScript解释器执行一行中的每一项内容，直到它遇到//，然后它跳转到下一行的开始。

我们也可以使用/*开始数行有用的注释，并且使用*/结束它（这和CSS所使用的注释类型是一样的）。JavaScript解释器忽略两组符号之间的所有文本。例如，假设要在代码开始处描述一个程序如何工作，可以像下面这样做：

/*

JavaScript Slideshow：

This program automates the display of

images in a pop-up window.

*/

不需要让/*和*/都单独占一行。实际上，可以使用它们创建单独的一行JavaScript注释：

/*this is a single line comment*/

通常，如果我们只是想要编写一个简短的、单行的注释，可以使用//。对于数行注释，使用/*和*/组合。

2.11.1　何时使用注释

对于相当长或复杂并且将来也会使用（并且可能修改）的程序来说，注释是很有价值的工具。尽管目前所学习的简单的脚本都只是一行或两行代码，但我们最终要创建较长或更复杂的程序。要确保可以快速搞清楚脚本在做什么，一种好办法是，添加注释以帮助理解程序的整体逻辑并说明任何特别容易混淆的地方或复杂的地方。

提示：在脚本中添加太多的注释会使得脚本更大（并且下载起来更慢）。然而，我们将在14.7节了解到，有一些方法使得JavaScript文件更小且更快。

很多程序员在一个外部JavaScript文件的开始处添加一段注释。这些注释可以说明这段脚本是用来做什么的，表明脚本的创建日期（包括用于经常更新的脚本的一个版本号），并且提供版权信息。

例如，在jQuery库的JavaScript文件的开始处，可以看到这样一段注释：

/*！

*jQuery JavaScript Library v1.6.3

*http：//jquery.com/

*

*Copyright 2011，John Resig

*Dual licensed under the MIT or GPL Version 2 licenses.

*http：//jquery.org/license

*

*Includes Sizzle.js

*http：//sizzlejs.com/

*Copyright 2011，The Dojo Foundation

*Released under the MIT, BSD, and GPL Licenses.

*

*Date：Wed Aug 31 10：35：15 2011-0400*/

在脚本的开始，可能还会包含如何使用脚本的说明：为了让脚本工作，可能需要设置的变量、可能需要对HTML所做的任何特定操作等。

也可以在一系列复杂的编程步骤之前添加一段注释。例如，假设编写一个脚本来实现访问者浏览器窗口中的图像动画。这段脚本的一部分是确定图像在浏览器窗口中的当前位置。这可能会占用数行复杂的程序，在这段程序前面添加注释是一个好办法，这样一来，当随后查看脚本的时候，就知道程序的这一部分到底干什么：

//determine x and y positions of image in window

在任何地方添加注释的首要基本原则是，你发现这些注释随后有用。如果一行代码十分显而易见，可能不需要注释。例如，为alert（'hello'）这样的简单代码添加注释，显然是没有什么理由的，因为它要做什么显而易见（打开一个警告框，其中显示单词“hello”）。

2.11.2　本书中的注释

在说明JavaScript的时候，注释也非常有用。在本书中，注释常常用来说明一行程序做什么，或者指明一条特定语句的结果。例如，我们可能看到如下注释，它展示了一条alert语句的结果：

var a='Bob'；

var b='Smith'；

alert（a+’’+b）；//'Bob Smith'；

第三行以一个注释结束，该注释表明了当在Web浏览器中预览这段代码的时候会看到什么内容。在阅读本书的时候，如果想要测试代码，可以将它们添加到一个Web页面并且在Web浏览器中查看，在把代码输入Web页面的时候，可以省略注释。这些类型的注释只是在你阅读本书的时候用来帮助你理解代码中发生了什么。

随着开始学习JavaScript中更为复杂的命令，将会开始操作变量中的数据。我们将会常常看到本书中的代码注释用来指出在命令运行后变量中应该存储的是什么。例如，charAt（）命令允许选择字符串中特定位置的一个字符。当在本书中读到如何使用这些命令的时候，可能会看到如下所示的代码：

var x="Now is the time for all good programmers."；

alert（x.charAt（2））；//'w'

出现在第二行末尾的注释//'w'表明：如果代码实际在一个Web浏览器中运行的话，我们会在警告对话框中看到什么（没错，'w'是对的，字符串像数组一样，因此，一个字符串的第一个字母的索引位置为0，因此，charAt（2）从字符串中获取第三个字符。有时候，编程只是伤脑筋）。

第3章　为程序添加逻辑和控制

到目前为止，我们已经学习了一些基本的JavaScript构建模块。但是，只是创建一个变量并在其中存储字符串或数值还远远不够。并且，用一个长长的列表项构建一个数组也并不是很有用，除非有简单的方法可以遍历数组中的项目。在本章中，我们将学习如何使用条件语句、循环和函数使得程序智能地响应并更高效地工作。

3.1　使程序智能地响应

我们的生活充满了选择：“我今天穿什么”、“午餐吃什么”、“星期五晚上该干什么”等。我们所做的很多选择取决于某个条件。例如，假设确定想要在星期五晚上看电影，我们可能会问自己一堆问题，例如，“有什么好的电影上映吗”、“电影能够准时开演吗”、“我带了足够的钱去电影院吗”（买一袋爆米花要17块钱）。

假设有一场电影刚好在我们去的时候上映。我们就会问自己一个简单的问题“我身上的钱够吗”。如果回答是够，我们就向电影院出发。如果回答是不够，我们就不去。但是在另一个星期五，我们可能有足够的钱，那就去电影院。对于条件是如何影响我们做出决定，电影院只是一个简单的例子。

JavaScript具有同样的判断功能，叫做条件语句。基本上，条件语句就是一个简单的是或非的问题。如果回答是，程序就会做一件事情；如果回答否，它就做其他的事情。条件语句是最重要的编程概念之一：它允许程序对不同的情况做出反应并智能地操作。我们将会在编程中无数次地用到它们，而这里只是给出如何使用它们的几个例子，以使你对其用法有一个清晰了解。

·表单验证。当想要确定某个人填写了一个表单中所有必需的字段（“Name，”、“Address，”、“E-mail，”等）的时候，我们将使用条件语句。例如，如果Name字段为空，就不提交该表单。

·拖拽。如果添加了在Web页面中拖动元素的功能，可能要检查访问者把元素放在页面的什么位置。例如，如果拖动一幅图片到一个垃圾箱图像上，我们就要让照片在页面上消失。

·评估输入。如果弹出一个窗口来询问访问者类似这样的问题：“您愿意回答几个针对这个Web站点的评价的问题吗？”我们可能希望脚本能够根据访问者如何回答这个问题而做出不同的反应。

图3-1给出了使用条件语句的应用程序的一个例子。

图　3-1　享受乐趣也要做很多工作。像Solitaire（http：//worldofsolitaire.com）这样的一个基于JavaScript的游戏展示了一个程序如何根据程序的条件做出不同的响应。例如，当一个玩家拖拽纸牌时，程序必须确定玩家是否把纸牌拖拽到了有效的位置，然后在每种情况下执行不同的操作

3.1.1　条件语句基础

条件语句也叫做“if/then”语句，因为只有在问题的答案为真的时候，它们才执行一项任务：“如果我有足够的钱，就去看电影。”条件语句的基本结构如下所示：

if（condition）{

//some action happens here

}

这条语句有三个部分：if表示接下来的程序是一个条件语句；圆括号包括了答案为是或否的问题，叫做条件（稍后会更详细地介绍它）；花括号（{}）表示如果条件为真所应该执行的JavaScript代码的开始和结束。

注意：在上面列出的代码中，“//some action happens here”是一条JavaScript注释。它不是实际运行的代码，只是在程序中保留的一个提示；并且，在这个例子中它指出，你作为代码的阅读者应该期待代码的这部分是什么内容。参见2.11节对于注释的更多介绍。

在很多情况下，条件是两个值之间的比较。例如，假设创建了一个游戏，当分数超过100的时候，玩家胜出。在这个程序中，我们需要通过一个变量来记录玩家的分数，并且，在某个时刻，我们需要检查这个分数是否超过100分。JavaScript检查玩家是否胜出的代码如下所示：

if（score＞100）{

alert（'You won！'）；

}

重要的部分是score＞100。这条语句是一个条件，并且，它直接测试存储在score变量中的值是否大于100。如果是，那么就会显示一个“You won！”的对话框；如果玩家的分数小于或等于100，那么JavaScript解释器就跳过警告框并直接到程序的下一部分。图3-2给出了这一过程的一个图示。

图　3-2　使用一条基本的条件语句，如果条件为真，则只运行花括号中的代码。如果条件为假，则略过这段代码并且程序继续运行

除了＞（大于），还有其他几种操作符用来比较数值（参见表3-1）。

提示：在包含在一对花括号中的JavaScript代码的每一行前面输入两个空格。空格（或制表符）缩进了这些行，使得人们更容易看到开始和结束的花括号，并且很容易看清楚哪些代码属于条件语句。常用两个空格，但是，如果4个空格使得你的代码更容易阅读，那就用4个空格。本书中的例子总是缩进花括号中的代码。

更为常见的是，我们将测试两个值是否相等。例如，假设我们创建了一个基于JavaScript的测验，并且其中的一个问题是“土星有多少颗卫星？”人们的答案将存储在一个名为answer的变量中。可以编写如下所示的一条条件语句：

if（answer==31）{

alert（'Correct.Saturn has 31 moons.'）；

}

这里的一对等号（==）并不是输入错误，它们让JavaScript解释器比较两个值并且判断二者是否相等。别忘了，在JavaScript中，单个等号是赋值操作符，通过它可以把一个值存储到变量中：

var score=0；//stores 0 into the variable score

由于JavaScript解释器已经给单个等号赋予了特殊的含义，当要比较两个值来判断它们是否相等的时候，我们需要使用两个等号符号。

我们也可以使用==（叫做相等性操作符）来查看两个字符串是否相同。例如，假设我们想让用户在表单中输入一个表示颜色的单词，并且如果他们输入“red”，就把页面的背景颜色改为红色。为此我们使用条件操作符：

if（enteredColor=='red'）{

document.body.style.backgroundColor='red'；

}

注意：在上面的代码中，现在先不要关心如何改变页面颜色。我们将在本书4.8.2节学习如何使用JavaScript来动态控制CSS属性。

也可以使用不相等操作符来测试两个值是否不同：

if（answer！=31）{

alert（"Wrong！That's not how many moons Saturn has."）；

}

惊叹号翻译为“不”，因此，！=意味着“不相等”。在这个例子中，如果存储在answer中的值不是31，那么可怜的测验参与者将看到一条错误警告消息。

条件为真才运行的代码，并不像前面的例子那样仅限于单行代码。只要愿意，可以在开始花括号和结束花括号之间使用多行JavaScript代码。例如，作为JavaScript测验示例的一部分，我们可以保持一个记分牌，记录测验参与者答对了多少道题。因此，当土星问题的回答正确，我们就给测验参与者的总分加1。可以通过如下的条件测试做到这点：

if（answer==31）{

alert（'Correct.Saturn has 31 moons.'）；

numCorrect+=1；

}

注意：正如本书2.5.5节所介绍的，上面的代码行（numCorrect+=1）直接给变量numCorrect中当前的值加上1。

并且也可以在花括号之间添加额外的JavaScript代码行，可以添加条件为真时运行的任何代码。

高级用户提示

Boolean返回值

在本书2.3.3节，我们认识了布尔值：true和false。布尔乍一看好像不是非常有用，但是，当我们开始使用条件语句的时候，会发现它们是必需的。实际上，既然条件确实是一个非真即假的问题，那么这个问题的答案就是一个布尔值。例如，查看如下代码：

var x=4；

if（x==4）{

//do something

}

代码的第一行把数值4存储到变量x中。下一行中的条件是一个简单的问题：存储在x中的值是4吗？在这个例子中，它是，因此，花括号之间的JavaScript代码运行。但是，圆括号之间真正发生的事情是：JavaScript解释器把条件转换为一个布尔值，用编程的术语来说，解释器计算了条件。如果条件计算为真（意味着问题的答案是yes），那么，花括号之间的代码运行。然而，如果条件计算为false，那么，花括号之间的代码将会略过。

布尔的一种常见用法是创建一个所谓的标志，这是表示某件事是否为真的一个变量。例如，当验证填满了访问者提交的信息的一个页面的时候，我们可能首先创建一个带有布尔值为true的valid变量，这意味着，首先，假设访问者正确地填写了表单。然后，依次查看每个表单字段，如果任何字段遗漏了信息或者信息的类型不对，可以把valid的值改为false。在检查了所有的表单字段字后，测试valid中存储的内容，如果仍然是true，就提交表单。如果不是true（以为这一个或多个表单字段为空），则显示某些错误消息并防止表单提交：

var valid=true；

//lot of other programming gunk happens in

here

//if a field has a problem then you set

valid to false

if（valid）{

//submit form

}else{

//print lots of error messages

}

3.1.2　添加备用计划

但是，如果条件为false呢？3.1.1节中的基本条件语句并没有准备一个备用计划，以便在条件为false的时候运行。在现实情况中，当我们要确定星期五晚上干什么并且没有足够的钱看电影的时候，我们还想要干些其他的事情。if语句有它自己的备用计划，叫做else子句。例如，以JavaScript测验脚本为例，我们想要通知测验参加者的回答是正确的或者是错误的。在这里，可以这么做：

if（answer==31）{

alert（'Correct.Saturn has 31 moons.'）；

numCorrect=numCorrect+1；

}else{

alert（"Wrong！That's not how many moons Saturn has."）；

}

这段代码创建了一种要么/要么的情景，两条消息中只有一条会显示（参见图3-3）。如果数值31存储在变量answer中，那么，“correct”警告窗口显示；否则，“wrong”警告窗口显示。

要创建一条else子句，只要在条件语句的结束花括号之后添加一个“else”，再跟上另外一对花括号即可。在花括号之间，添加如果条件结果为false应该执行的代码。同样，只要你愿意，可以有多行代码作为else子句的部分。

图　3-3　当使用一个ifelse条件的时候，包含了两组代码，但是，只有一组代码会运行。如果条件为ture，那么运行紧跟在条件之后的花括号中的代码（如图左边所示）；然而，如果条件为false，那么运行“else”后面的花括号中的代码（如图右边所示）

3.1.3　测试多个条件

有时候，我们想要测试多个条件并且拥有几种可能的输出。考虑这样一个游戏，主持人对你说：“你想要1号门、2号门还是3号门背后的奖金？”你只能选择其中之一。在日常活动中，我们也经常面对像这样的多个条件选择。

例如，回到“我星期五晚上干什么”的问题。你可能根据自己有多少钱以及愿意花多少钱来增加娱乐项目。例如，你可以这样开始，“如果我有超过50块钱，我就到外面好好吃一顿并看场电影（并且买点爆米花）”。如果没有50块钱，你可能会尝试另一个测试“如果我有35块钱或更多，我就好好地吃一顿”。如果没有35块钱，你可能会说：“如果我有12块钱或者更多，我就去看场电影。”最后，如果没有12块钱，你可能会说：“那我就待在家里看电视。”多么美妙的一个星期五晚上啊。

JavaScript允许我们使用else if语句执行同样的层叠逻辑。它像这样工作：从一条if语句开始，其选项为1号；然后，添加1个或多个else if语句，来提供可以触发其他选项的其他问题；最后，使用else子句作为退路。下面是其在JavaScript中的基本结构：

if（condition）{

//door#1

}else if（condition2）{

//door#2

}else{

//door#3

}

这个结构是我们创建一个JavaScript“星期五之夜计划”程序所需要的所有内容。它询问访问者有多少钱，然后确定他们应该在星期五做什么（听起来有些熟悉吧）。我们可以使用在本书2.7节的教程中所学习的prompt（）命令来收集访问者的反馈，并且使用一系列的if/else if语句来确定他应该做什么：

var fridayCash=prompt（'How much money can you spend?’，’'）；

if（fridayCash＞=50）{

alert（'You should go out to a dinner and a movie.'）；

}else if（fridayCash＞=35）{

alert（'You should go out to a fine meal.'）；

}else if（fridayCash＞=12）{

alert（'You should go see a movie.'）；

}else{

alert（'Looks like you will be watching TV.'）；

}

下面是对这个程序分步骤的分析：第一行代码打开一个提示对话框，询问访问者可以花费多少钱。访问者输入的内容将存储在名为fridayCash的变量中。下一行代码是一个测试：访问者输入的值是否大于或等于50？如果答案是肯定的，那么出现一个警告框，告诉他去吃顿饭并看场电影。此时，整个条件语句就完成了。JavaScript解释器略过下一条else if语句，接下来的else if语句，以及最后的else子句。使用条件语句，只会发生一种结果，因此，一旦JavaScript解释器遇到计算为true的一个条件，那么它为该条件运行花括号之间的JavaScript代码，并且略过条件语句中的其他所有内容（参见图3-4）。

假设访问者输入的是25。在这个例子中，第一个条件将不为true，因为25是一个小于50的数值。因此，JavaScript解释器跳过了针对第一个条件的花括号中的代码，并且继续“25是否大于等于35”的else if语句。既然答案为false，它跳过和这个条件相关的代码，并且遇到下一个else if语句。此时，条件询问“25是否大于等于12”，答案是true，因此，出现一个带有“You should go see a movie”消息的警告框并且程序结束，略过最终的else子句。

提示：还有另一种方式可以创建都测试同一个变量的一系列的条件语句，就像是在fridayCash示例中那样。switch语句可以做同样的事情，将在14.6.3节学习它。

图　3-4　使用一条基本的条件语句，如果条件为true，则只运行花括号之间的代码；如果条件为false，则略过该段代码，并且程序继续

3.1.4　更复杂的条件

当处理多个不同的变量时，经常需要更加复杂的条件语句。例如，当验证表单中一个必需的E-mail地址字段的时候，我们想要确保字段不为空并且字段包含一个E-mail地址（而不只是随机输入的字母）。幸运的是，JavaScript允许做这类检查。

多个条件为真时

我们通常需要根据多个因素的组合来做出决定。例如，如果有足够的钱，可能想去看电影，并且也有想看的电影正在放映。在这种情况下，只有两个条件都为true时，才会去看电影。在JavaScript中，可以使用逻辑AND操作符来组合条件，可以用两个＆符号（＆＆）表示。通过AND操作符可以把两个条件组合为一个条件语句。例如，如果想要查看一个数值是否在1～10之间，可以这么做：

if（a＜10＆＆a＞1）{

//the value in a is between 1 and 10

alert（"The value"+a+"is between 1 and 10"）；

}

这个例子中有两个条件：第一个条件a＜10询问存储在变量a中的值是否小于10；第二个条件a＞1等同于“这个值是否大于1”。只有在两个条件都为true的时候，包含在花括号之间的JavaScript代码才会运行。因此，如果变量a中存储了数值0，第一个条件（a＜10）为true（0小于10），但是，第二个条件为false（0不大于1）。

不仅限于使用两个条件。只要需要，可以使用＆＆操作符连接尽可能多的条件：

if（b＞0＆＆a＞0＆＆c＞0）{

//all three variables are greater than 0

}

这段代码检查3个变量以确保这3个变量的值都大于0。如果有一个变量拥有值0或者更小的值，那么将不会执行花括号中的代码。

至少一个条件为true时

还有些时候，我们也要检查一系列的条件，但是只需要一个为true。例如，假设添加了一个键盘控制，以便访问者可以在一个照片库中从一幅图片跳转到另一幅图片。当访问者按下N键的时候，显示下一张照片。在这种情况下，当他按下n（小写）的时候，或者已经按下Caps Lock键的时候，即N（大写），都希望跳转到下一幅照片。我们需要一种或者/或者的逻辑：或者这个键按下，或者那个键按下。逻辑OR操作符有了用武之地，它用两个管道符号（||）表示：

if（key=='n'||key=='N'）{

//move to the next photo

}

提示：按下Shift-\可以输入一个管道符号。斜杠和管道符号共用的输入键通常在回车键的上方。

使用OR操作符，只要一个条件为真，就会运行花括号之间的JavaScript代码。

和AND操作符一样，我们可以比较两个以上的条件。例如，假设我们要创建一个JavaScript赛车游戏。玩家有时间的限定、有限的汽油量和有限的车辆数目（每次他撞毁或失去一辆车）。为了让游戏更有挑战性，当这三项都用完的时候，我们让游戏结束：

if（gas＜=0||time＜=0||cars＜=0）{

//game is over

}

在测试多个条件的时候，有时候很难搞清楚条件语句的逻辑。一些程序员把每个条件组合到一对圆括号中，以使得逻辑更容易理解：

if（（key=='n'）||（key=='N'））{

//move to the next photo

}

要阅读这段代码，只需要把每个组当成一个单独的测试。圆括号之间的操作的结果要么总是true，要么总是false。

对条件取反

如果你是超人迷，可能会知道Bizarro，这是超人的对头，居住在一个名叫Htrae（Earth的逆向拼写）的立方体卫星，其制服有一个反写的S，并且其通常的行为方式都是和超人相反的。当Bizarro说“是”的时候，他实际的意思是“不是”；而当他说“不是”的时候，他实际的意思是“是”。

JavaScript编程有一个类似的字符，叫做NOT操作符，它由一个惊叹号（！）表示。我们已经看到了NOT操作符和等号符号一起使用，表示“不等于”（！=）。但是，NOT操作符可以单独使用来表示把一个条件语句的结果取反；换句话说，它可以使得false意味着true，而使得true成为false。

当我们想要根据一个否定条件来运行某些代码的时候，就使用NOT操作符。例如，假设我们已经创建了一个名为valid的变量，其中包含了一个Boolean值，它要么为true要么为false（参见3.1节中的“高级用户提示：Boolean返回值”部分）。我们使用这个变量来记录访问者是否正确地填写了表单。当访问者试图提交表单的时候，JavaScript检查每个表单字段，确保它们满足了设置的要求（例如，字段不为空，并且其中必须有一个E-mail地址）。如果有问题，例如字段为空，可以把valid设置为false（valid=false）。

现在，如果想要做些事情，例如显示一个错误以阻止表单提交，可以编写如下所示的一个条件语句：

if（！valid）{

//print errors and don't submit form

}

条件！valid可以翻译为“如果不是valid”，这意味着，如果valid为false，那么这个条件为true。若要搞清楚使用NOT操作符的一个条件的结果，只要计算没有NOT操作符的条件，然后对其取反即可。换句话说，如果条件结果为true，！操作符将其改为false，因此，条件语句不会运行。

可以看到，NOT操作符很容易理解（用Bizarro的话来说：它很容易令人混淆，但是，如果你使用它的时间足够长，就会习惯）。

3.1.5　嵌套条件语句

大多数时候，计算机程序必须根据访问者提供的信息或者根据程序中的当前条件来做出判断。程序所做的判断越多，可能的结果也就越多，程序似乎也更“智能”。实际上，我们可能会发现在执行了一个条件语句之后，需要进一步做判断。

假设，在“星期五晚上干什么”的示例中，我们想要把程序扩展到包含一周的每个晚上。在这种情况下，我们需要首先确定是星期几，然后确定在这一天干什么。因此，可能有一个条件语句询问今天是否是星期五，如果是，有另外一系列的条件语句来确定这一天做什么：

if（dayOfWeek=='Friday'）{

var fridayCash=prompt（'How much money can you spend?’，’'）；

if（fridayCash＞=50）{

alert（'You should go out to a dinner and a movie.'）；

}else if（fridayCash＞=35）{

alert（'You should go out to a fine meal.'）；

}else if（fridayCash＞=12）{

alert（'You should go see a movie.'）；

}else{

alert（'Looks like you will be watching TV.'）；

}

}

在这个例子中，第一个条件询问存储在变量dayO fWeek中的值是否是字符串“Friday”。如果回答是yes，那么，显示提示对话框，从访问者那里获取某些信息，并且另一个条件语句开始运行。换句话说，第一个条件（dayOfWeek=='Friday'）是通向一组条件语句的条件。然而，如果dayOfWeek不是“Friday”，那么，条件为false并且嵌套的条件语句也会略过。

3.1.6　编写条件语句的技巧

3.1.5　节中的嵌套条件语句的例子看上去可能有些令人惊讶。有这么多的（）、{}、else和if。并且，如果你碰巧错误地输入了条件语句的一个关键部分，脚本就不会工作。在输入JavaScript的时候，还有几种方法便于更容易地使用条件语句。

·提前输入两个花括号，然后再在其中输入代码。程序员常犯的一个错误就是忘了给条件语句添加一个表示结束的花括号。为了避免这个错误，先输入条件和花括号，然后再输入条件为true时执行的JavaScript代码。例如，像下面这样开始一个条件：

if（dayOfWeek=='Friday'）{

}

换句话说，输入if子句和第一个花括号，按下回车键两次，然后输入最后的结束花括号。现在，基本的语法是正确的，可以单击两个花括号之间的空白行并添加JavaScript代码。

·在花括号中缩进代码。如果缩进了一对花括号之间的所有JavaScript代码，那会使得条件语句的结构更好看：

if（a＜10＆＆a＞1）{

alert（"The value"+a+"is between 1 and 10"）；

}

通过使用几个空格（或者按下Tab键），在花括号内缩进代码，这可以很容易地表示出哪些代码作为条件语句的一部分运行。如果有嵌套的条件语句，要在每个嵌套语句中缩进：

if（a＜10＆＆a＞1）{

//first level indenting for first conditional

alert（"The value"+a+"is between 1 and 10"）；

if（a==5）{

//second level indenting for 2nd conditional

alert（a+"is half of ten."）；

}}

·使用==比较相等性。当检查两个值是否相等的时候，不要忘了使用相等操作符，如下所示：

if（name=='Bob'）{

常见的错误是使用一个等号，如下所示：

if（name='Bob'）{

单个等号会把值存储到变量中，因此，在这个例子中，字符串“Bob”将会存储到name变量中。JavaScript解释器会把这个步骤当做true，因此，条件后面的代码将总是会执行。

3.2　教程：使用条件语句

条件语句将会成为我们日常的JavaScript工具箱的一部分。在这个教程里，我们将尝试使用条件语句来控制脚本如何运行。

注意：请参考1.3节中的“注意”部分了解如何下载教程文件。

1.在文本编辑器中打开chapter03目录下的文件conditional.html。

首先直接提示用户输入一个数值。这个文件已经在头部和正文区域都包含了＜script＞标签。

2.在第一对＜script＞标签之间，即页面的＜head＞部分中，输入粗体所示的代码：

＜script＞

var luckyNumber=prompt（'What is your lucky number?’，’'）；

＜/script＞

这行代码打开了一个JavaScript提示框，询问一个问题，并且把访问者输入的内容存储到luckyNumber变量中。接下来将添加一个条件语句来查看访问者在提示对话框中输入了什么。

3.在页面正文的下方找到第二对＜script＞标签，并且添加粗体所示的代码：

＜script＞

if（luckyNumber==7）{

＜/script＞

这里是条件语句的开始，它直接检查访问者是否输入了7。4.按下回车键两次，并结束花括号，代码如下所示：

＜script＞

if（luckyNumber==7）{

}

＜/script＞

结束的花括号完成了条件语句。在两个花括号之间添加的任何JavaScript代码都只能够在条件为true的时候运行。

提示：正如3.2节所介绍的，在编写作为条件语句的一部分运行的代码之前先添加结束花括号，这是个好办法。

5.单击上面的结束花括号之上的空行。按下空格键两次，并输入：

document.write（"＜p＞Hey，7 is my lucky number too！＜/p＞"）；

代码前的两个空格缩进了这行，以方便看到这行代码是条件语句的一部分。这里实际的JavaScript代码，我们现在应该很熟悉了，它只是向页面显示一条消息。

6.保存文件并在Web浏览器中预览。当提示对话框出现的时候，输入7。

当页面载入的时候，应该会在标题栏下看到“Hey, that's my lucky number too！”这条消息。如果没有看到，检查一下代码确认自己输入正确（参见1.6关于处理无效脚本的技巧）。重新载入页面，但是这次输入不同的数值。这次可以看到标题栏下面什么也没有。我们将添加一个else子句来显示其他信息。

注意：为什么需要两组script标签呢？当使用document.write（）方法把内容添加到页面上的时候，我们必须把document.write（）代码放在页面上想要让消息出现的具体位置，在这个例子中，就是＜h1＞标签之下的正文中。第一组script标签出现在头部，因为我们想要提示窗口早点出现。如果将prompt（）向下移动到正文中（继续并尝试一下），你将会看到，当页面载入，提示窗口出现的时候，页面只有一部分会显示。因为在页面的任何其他部分显示之前，该处的JavaScript立刻执行，Web浏览器必须等到访问者在提示窗口中输入数值，然后才能够显示页面的其他部分。换句话说，页面看上去很奇怪。然而，通过将提示窗口放在＜head＞部分，页面一开始是空白的，然后提示窗口出现，这看上去要好一些。在第4章中，我们将学习如何不使用document.write（）方法而把内容添加到页面的任何位置。一旦了解了这一项技术，可以将自己的所有JavaScript代码放在页面的同一个位置。

7.返回文本编辑器，并且向页面添加如下粗体所示的内容：

＜script＞

if（luckyNumber==7）{

document.write（"＜p＞Hey，7 is my lucky number too！＜/p＞"）；

}else{

document.write（"＜p＞The number"+luckyNumber+"is lucky for you！＜/p＞"）；

}

＜/script＞

这个else子句提供了一条备用消息，这样，如果访问者没有输入7，他会看到一条包含自己的幸运数字的不同消息。为了让这个练习更完美，可以添加另一个else if语句来测试更多的值并提供其他的消息。

8.在脚本中添加如下两行粗体显示的代码：

＜script＞

if（luckyNumber==7）{

document.write（"＜p＞Hey，7 is my lucky number too！＜/p＞"）；

}else if（luckyNumber==13||luckyNumber==24）{

document.write（"＜p＞Wooh."+luckyNumber+"?That's an unlucky number！＜/p＞"）；

}else{

document.write（"＜p＞The number"+luckyNumber+"is lucky for you！＜/p＞"）；

}

＜/script＞

此时，脚本首先检查变量luckyNumber中存储的值是否是7。如果luckyNumber存储的值不是7，那么，else if开始执行。这个条件语句由两个条件组成，luckyNumber==13和lucky Number==24。||叫做逻辑OR操作符，如果条件中的任何一个为true，这个条件语句结果就是true。因此，如果访问者输入13或24，页面上将会显示一条“That's an unlucky number”消息。

提示：可以通过两次按下Shift-\得到||，从而添加逻辑OR操作符。

在Web浏览器中预览页面，并且当提示对话框出现的时候输入13。按下浏览器的刷新按钮，并且再次尝试不同的数值，包括字母和其他字符。你将会注意到，如果输入一个单词或者其他的非数值字符，最终的else子句将执行，显示一条这样的消息：“The number asdfg is lucky for you！”。既然这没有太多的意义，当访问者第一次输入一个非数值时，应该弹出另一个提示对话框。

9.返回文本编辑器，并且找到页面的＜head＞部分的第一个组＜script＞标签。添加如下粗体所示的代码：

＜script＞

var luckyNumber=prompt（'What is your lucky number?’，’'）；

luckyNumber=parseInt（luckyNumber，10）；

＜/script＞

这行代码把luckyNumber的值放入一个名为parseInt（）的函数。这条JavaScript命令接受一个值并且尝试把它转换为一个整数，即像1、5或100这样的完整的数值。我们将在14.3.1节中学习这条命令，但是现在，只需要知道如果访问者输入“ha ha，”这样的文本，parseInt（）命令不能将其转换为数值；相反，该命令会提供一个特殊的JavaScript值，即NaN，它表示“不是一个数字”。如果访问者第一次输入的不是一个数值，我们可以使用这一信息弹出另外的对话框。

10.把如下粗体所示的代码添加到脚本中：

＜script＞

var luckyNumber=prompt（'What is your lucky number?’，’'）；

luckyNumber=parseInt（luckyNumber）；

if（isNaN（luckyNumber））{

luckyNumber=prompt（'Please, tell me your lucky number.’，’'）；

}

＜/script＞

这次，条件语句再次发挥作用。条件isNaN（luckyNumber）使用另一条JavaScript语句来检查内容是否是数值。它特别检查luckyNumber中的值是否是一个数值。如果这个值不是一个数值（例如，访问者输入asklsdkl），又弹出一个提示框并再次询问一个问题。如果访问者确实输入了一个数值，第二个提示框略过。

保存页面并在Web浏览器中预览。这次，在提示框出现的时候，输入一个单词并单击OK按钮。然后应该看到另一个提示框。然后输入一个数值。当然，这段脚本假设访问者第一次输入一个单词时候不小心犯了一个错误，但是，不会第二次犯同样的错误。遗憾的是，如果访问者在第二个提示框中输入一个单词，最终会遇到同样的问题，我们将在3.3节学习如何修正这个问题。

提示：在chapter03目录中可以找到这个教程的完整版本：complete_conditional.html。

3.3　使用循环处理重复性任务

有时候，脚本需要一次又一次地重复同样的一系列步骤。例如，假设有一个Web表单，它带有30个文本字段。当用户提交该表单的时候，我们想要确保没有一个字段为空。换句话说，你需要执行同样的一组操作30次，即检查一个表单字段是否为空。既然计算机善于执行重复性的任务，JavaScript包含快速地重复做同样事情的工具就有意义了。

用编程术语来说，重复执行同样的任务叫做循环，由于循环在编程中如此常见，因此JavaScript提供了几种类型的循环。它们都做同样的事情，只不过方式略有不同。

3.3.1　while循环

用while循环重复一段代码，只要一个特定的条件为true；换句话说，只要条件为true。while循环的基本结构如下：

while（condition）{

//javascript to repeat

}

第一行代码引入了while循环。和条件语句一样，我们把条件放置在紧跟while之后的一对圆括号中。条件是可以在条件语句中使用的任何测试，例如x＞10或answer=='yes'。就像条件语句一样，如果条件为true, JavaScript解释器运行开始花括号和结束花括号之间的所有代码。

然而，和条件语句不同，当JavaScript解释器遇到了while语句的结束花括号，它会跳回到while语句的顶部并再次测试条件，而不是继续程序的下一行。如果条件再次为true，解释器再次运行花括号之间的JavaScript代码。这个过程持续到条件不再为true时为止，然后，程序继续循环之后的下一条语句（参见图3-5）。

假设想要在页面上显示数值1～5。做到这一点的一种可能的方式是：

图　3-5　只要测试条件（在这个例子中是x＜10）为真，while循环就运行花括号之间的JavaScript代码

document.write（'Number 1＜br＞'）；

document.write（'Number 2＜br＞'）；

document.write（'Number 3＜br＞'）；

document.write（'Number 4＜br＞'）；

document.write（'Number 5＜br＞'）；

注意，每行代码都几乎相同：每行之间只有数值不同。在这种情况下，循环提供了一种更高效的方式来实现同样的目标：

var num=1；

while（num＜=5）{

document.write（'Number'+num+’＜br＞'）；

num+=1；

}

代码的第一行var num=1不是while循环的一部分：它设置了一个变量来存储要在页面上显示的数值。第二行代码是循环的开始。它设置了测试条件。只要存储在变量num中的数值小于或等于5，花括号之间的代码就会运行。当第一次遇到测试条件，num的值是1，因此测试为true（1小于5），并且document.write（）命令执行，'Number 1＜br＞’显示到页面（＜br＞只是一个HTML分行，确保每一行在Web页面上显示为单独一行）。

提示：编写num=num+1的一种更加紧凑的方式（这只是给存储在num变量中的当前值增加1）如下所示：

num++

这种简写的方法也为变量num加1（参见表2-3了解详细信息）。

循环的最后一行num=num+1非常重要。它不仅把num的值增加到1，以便能够显示下一个数值（例如，2），而且它使得测试条件最终可能变为false（如果觉得+=这样的东西看上去很奇怪，那么，可以回到本书2.5.5节，了解一下它是如何工作的）。只要条件是true, while循环中的JavaScript代码就会重复，因此，我们必须改变条件的一个要素以便条件最终变为false，从而停止循环并转向脚本的下一部分。如果测试条件不会变为false，最终将得到一个所谓的无限循环，即不会终止的程序。注意，如果在循环中漏掉了这一行，将会发生什么：

var num=1；

while（num＜=5）{//this is an endless loop

document.write（'Number'+num+’＜br＞'）；

}

第一次迭代这个循环，测试会询问：1小于等于5吗？答案是肯定的，因此document.write（）运行。在循环的末尾（最后一个花括号），JavaScript解释器回到了循环的开始处并再次测试条件。此时，num仍然是1，因此，条件再次为真，并且再次执行document.write（）。又一次，JavaScript解释器回到了循环的开始处并第三次测试条件。我们可以看看会发生什么情况：无数多行都显示“Number 1”。这个简单的例子还展示了循环所提供的一些灵活性。例如，假设想要显示的数值是1～100，而不是1～5。只需要更改测试条件，而不是加入更多的document.write（）命令的代码行，如下所示：

var num=1；

while（num＜=100）{

document.write（'Number'+num+’＜br＞'）；

num=num+1；

}

现在，循环将会执行100次，向页面显示100行。

3.3.2　循环和数组

当处理一种常见的JavaScript元素（数组）的时候，你会发现循环有了用武之地。还记得2.8节介绍过，数组是数据的一个集合。我们可以把数组看做一种购物清单。当我们要去购物的时候，实际上是执行一种循环：在商店寻找清单上的一件商品，找到之后，将它放入购物车；然后继续寻找清单上的下一件商品，再放入购物车，以此类推；直到已经遍历完整个清单。然后就结束了（这就像是退出循环），并且去收银台结账（换句话说，继续程序的下一个步骤）。

在JavaScript中，可以使用循环来遍历数组中的项目并且在每个项目上执行一项任务。例如，假设我们要构建一个程序来生成日历。这个日历完全是使用JavaScript生成的，并且我们想要在日历上显示每一天是星期几。我们可能首先把星期几的名字存储到一个数组中，如下所示：

var days=['Monday'，'Tuesday'，'Wednesday'，'Thursday'，?

'Friday'，'Saturday'，'Sunday']；

注意：上面代码中的8表示JavaScript代码的这些行属于单独的一行。由于本书页面的宽度有限，有时候一行代码无法在单独一行文字中显示，因此本书使用8符号来表示应该出现在单独一行中的代码。如果你打算把这些代码输入到文本编辑器中，就要把它们作为长长的一行输入（并且省略掉8）。

现在遍历数组中的每个项目并在页面中显示。别忘了使用项目的索引值来访问数组中的一项。例如，上面的days数组的第一项（Monday），通过days[0]来访问。第二项是days[1]，以此类推。

下面展示如何使用一个while循环来显示这个数组中的每一项：

var counter=0；

while（counter＜days.length）{

document.write（days[counter]+’，'）；

counter++；

}

第一行var counter=0设置（或者用程序员的话来说，叫做初始化）一个counter变量，它用作测试条件的一部分，也用作访问数组项目的索引。条件counter＜days.length只是询问counter中存储的当前值是否小于数组的项数（别忘了，在2.8.2节介绍过，数组中项目的总数存储在数组的length属性中）。在这个例子中，该条件检查counter是否小于7（一个星期中的天数）。如果counter小于7，那么循环开始，星期几就会显示到页面上（后面跟着一个逗号和一个点号），并且，counter增加1（counter++和counter=counter+1是相同的，参见3.3.1节的“提示”部分）。在循环运行之后，再次尝试测试；循环继续运行，直到测试结果为false。这个过程如图3-6表示。

图　3-6　通过这个循环，条件测试了8次。最后一次测试询问7是否小于7。答案为false，因此，while语句完成，并且JavaScript解释器略过循环并继续脚本的下一部分。这个脚本的最终结果将是“Monday, Tuesday, Wednesday, Thursday, Friday, Saturday, Sunday”

3.3.3　for循环

JavaScript还提供了另一种类型的循环，叫做for循环，这是一种更加紧凑的形式（而且更容易令人混淆）。for循环通常用来重复一系列的步骤一定的次数，因此，它们通常包含某种类型的计数器变量、一个条件测试以及改变计数器变量的一种方法。在很多情况下，for循环可以实现和while循环相同的事情，而只需要寥寥数行代码。例如，下面是3.3.1节中的while循环：

var num=1；

while（num＜=100）{

document.write（'Number'+num+’＜br＞'）；

num+=1；

}

可以使用一个for循环实现同样的效果，而只需要3行代码：

for（var num=1；num＜=100；num++）{

document.write（'Number'+num+’＜br＞'）；

}

刚一开始，for循环可能看上去有点容易令人混淆，但是，一旦搞清楚了for语句的不同部分，它们就没那么难了。每个for循环以关键字for开始，后面跟着一对圆括号和一对花括号，圆括号中包含了3个部分。和while循环一样，花括号中的内容（在这个例子中是document.write（'Number'+num+’＜br＞'）；）就是作为循环的一部分而执行的JavaScript代码。

表3-2说明了圆括号中的3个部分，但是，简单地说，第一部分（var num=1；）初始化了一个计数器变量。这个步骤只是在语句刚开始运行的时候发生一次。第二部分是条件，测试该条件看看循环是否运行；第三部分是在每次循环的末尾发生的一个操作，它通常改变计数器的值，因此，测试条件最终为false并且循环结束。

由于for循环提供了一种容易的方法来重复一系列步骤一定次数，它们对于遍历数组的元素效果很理想。图3-6中的while循环把数组中的每个项都显示到页面，可以使用一个for循环重新编写它，代码如下所示：

var days=['Monday'，'Tuesday'，'Wednesday'，'Thursday'，?

'Friday'，'Saturday'，'Sunday']；

for（var i=0；i＜days.length；i++）{

document.write（days[i]+’，'）；

}

提示：优秀的程序员经常为for循环中的计数器变量使用一个非常简短的名字。在上面的代码中，字母i充当计数器的名字。一个字母的名字（i、j和z都很常用）很快就可以录入，并且由于这个变量除了运行循环以外不作他用，因此没必要提供一个像counter这样描述性强的名字。

到目前为止的例子都是计数到一定的次数然后停止循环，但是也可以反向计数。例如，假设我们想要按照相反的顺序来显示数组中的项目（换句话说，数组中的最后一项先显示），可以这么做：

var example=['first'，'second'，'third'，'last']；

for（var j=example.length；j＞0；j--）{

document.write（example[j-1]+’＜br＞'）；

}

在这个例子中，计数器变量j从数组中的总数（4）开始。每循环一次，测试j中的值是否大于0；如果是，运行花括号之间的代码。然后，从j中减去1（j——），然后再次运行测试。唯一的具有技巧的部分就是程序访问数组项目的方式（example[j-1]）。既然数组索引从0开始，那么数组的最后一项的索引就是数组中项目总数减去1（参见2.8.2节的介绍）。这里，j从数组中的项目的总数开始，因此，为了访问最后一项，必须从j减去1以获取正确的项目。

3.3.4　do/while循环

还有另一种不那么常见的循环类型，叫做do/while循环。这种类型的循环基本上和while循环相同，它的基本结构如下所示：

do{

//javascript to repeat

}while（condition）；

在这种循环中，条件测试发生在末尾，即在循环运行之后。因此，花括号中的JavaScript代码总是至少运行一次。即便条件不为true，也只有在代码运行一次之后才会进行测试。

可以使用这种循环的情况并不太多，但是，当想要提示用户输入的时候，它非常有用。我们在本章前面（3.2节中的教程）见到的教程是一个很好的例子。那段脚本要求访问者输入一个数值。它包含了一个失效预防系统，因此，如果访问者没有输入一个数值，脚本要求他们再次输入一个数值。遗憾的是，如果某人真的很顽固并且再次输入数值以外的内容，就会在页面上显示一条无意义的消息。

然而，有了do/while循环，就可以继续提示访问者输入一个数值，直到他确实输入了一个数值。为了看看如何做到这一点，我们将编辑在3.2节的教程中完成的页面：

1.在文本编辑器中打开在3.2节的教程中完成的页面conditional.html。

（如果你没有完成该教程，可以直接打开文件complete_conditional.html）。我们将使用一个do/while循环来替代靠近页面顶部的代码。

2.找到页面的＜head＞部分的＜script＞标签之间的代码，并且删除如下粗体所示的代码：

var luckyNumber=prompt（'What is your lucky number?’，’'）；

luckyNumber=parseInt（luckyNumber，10）；

if（isNaN（luckyNumber））{

luckyNumber=prompt（'Please, tell me your lucky number.’，’'）；

}

删除的代码提供了第二个提示对话框。我们不再需要这些代码。相反，把剩下的代码包含到一个do/while循环中。

3.把光标放到代码的第一行（以var luckyNumber开始的一行）并输入：

do{

这行代码创建了循环的开头。接下来将完成循环并添加测试条件。

4.单击这段JavaScript代码的最后一行，并且输入：}while（isNaN（luckyNumber））；。完整的代码段如下所示：

do{

var luckyNumber=prompt（'What is your lucky number?’，’'）；

luckyNumber=parseInt（luckyNumber，10）；

}while（isNaN（luckyNumber））；

保存文件并在Web浏览器中预览。尝试在提示框中输入文本和其他的非数字符号。这个恼人的对话框持续出现，直到你真正地输入了一个数字。

它是这么工作的：do关键字告诉JavaScript解释器，让它准备进入一个do/while循环。下面的两行代码随后运行，因此，提示框出现并且将访问者的回答转换成一个整数。只有此时才进行条件测试。这和3.2节教程的脚本中的条件相同：它只是检查从访问者那里获取的输入是否“非数值”。如果输入不是一个数值，循环重复。换句话说，只要输入的是非数值，提示继续出现。这种方法的好处是，它保证了提示框至少出现一次，因此，如果访问者确实输入了一个数值作为问题的回答，就没有循环。在Chapter03文件夹的complete_do-while.html中可以看到一个完整有效的教程。

3.4　函数：把有用的代码转换为可复用的命令

假设在工作中你刚刚得到一位新的助理，来帮助你完成每项任务（这时候应该把本书归类为“玄幻小说”了）。假设你饿了想吃披萨饼，但助理是新来到这座大楼和这个区域的，你必须给他清楚的指示：“出门右转，乘电梯到1层，走出大楼……”等。助手按照你的指示并且买来了一块披萨饼。几个小时之后，你又饿了，还想吃更多的披萨饼。现在，你不需要再次复述整个指示（“出门右转，乘电梯到1层，走出大楼……”）。这次，助手知道到哪里去购买披萨饼，所以，你说“买块披萨饼来”，他就会到有披萨饼的地方买来一块。

换句话说，我们只需要提供一次详细指示就够了，助手记住这些步骤，并且只要简单地说“买块披萨饼来”，他立即离开并且稍后带着披萨饼回来。JavaScript也有与此等效的机制，叫做函数。函数是在脚本的开始处设置的一系列的编程步骤，等效于为助手提供的详细指示。当创建函数的时候，这些步骤并没有实际运行；相反，它们存储在Web浏览器的内存中，而在我们需要执行那些步骤的时候可以调用它们。

函数对于高效重复执行多个编程步骤很有价值：例如，假设要创建一个照片集Web页面，其中填满了50幅小的缩略图图像。当某人单击其中一幅小图像时，我们可能希望页面淡出，出现一个标题，并且该图像的一个较大版本填充整个屏幕（将在7.4节学习如何做到这些）。每次某人单击一幅图像，就重复这个过程，因此，在具有50幅较小的照片的Web页面上，脚本可能必须做同样的一系列步骤50次。幸运的是，我们不一定必须编写同样的代码50次才能让照片集工作。相反，可以编写一个具有所有必需步骤的函数，随后，对于缩略图的每次单击，运行这个函数。编写一次代码，但是可以运行它任意多次。

函数的基本结构如下所示：

function functionName（）{

//the JavaScript you want to run

}

关键字function让JavaScript解释器知道我们要创建一个函数，这和使用if开始一条if/else语句或使用var来创建一个变量类似。接下来，我们提供了一个函数名，和变量一样，我们也可以选择自己的函数名。遵守2.4.1节列出的命令变量的规则。此外，在函数名中包含一个动词是很常见的，例如calculateTax、getScreenHeight、updatePage或fadeImage。带有动词的函数名使得人们很清楚这个函数做什么，并且很容易区分函数名和变量名。

紧接着名字，我们添加了一对圆括号，这是函数的另一个特征。在圆括号的后面是一个空格，后面跟着一个花括号、一行或多行JavaScript代码，最后是结束花括号。和if语句一样，花括号表示组成函数的JavaScript代码的开始和结束。

提示：和if/else语句一样，如果缩进了花括号之间的JavaScript代码，函数会更容易阅读。每行开始使用两个空格（或一个制表符）是很常见的。

下面是一个简单的函数，它以"Sun May 12 2008"的格式显示当前日期：

function printToday（）{

var today=new Date（）；

document.write（today.toDateString（））；

}

这个函数名为printToday。它只有两行JavaScript代码，接受当前日期，把日期转换为我们可以理解的一种格式（即toDateString（）部分），然后使用document.write（）命令把结果显示到页面上。现在不必关心所有这些日期内容是如何工作的，我们将在14.4节中找到答案。

程序员通常把他们的函数放在脚本的开始处，在那里设置了各种函数，以供脚本剩下的部分随后使用。别忘了，当初次创建函数的时候，它不会运行，这就好像告诉助手如何到达披萨店，而不是真的让他去那里。JavaScript代码只是存储在浏览器的内存里，等到随后需要的时候才运行。

但是，我们如何运行一个函数呢？用编程的术语来说，在想要函数执行其任务的任何时候调用函数。调用函数只是写下函数名后面跟着一对圆括号就可以了。例如，要让printToday函数运行，直接输入：

printToday（）；

正如你所看到的，让一个函数运行并不需要太多的录入，这就是函数美丽的地方。一旦创建了函数，不需要添加太多的代码就可以得到结果。

注意：当调用一个函数的时候，不要忘了跟在函数后面的圆括号。这是让函数运行起来的部分。例如，printToday不做任何事情，但printToday（）执行该函数。

3.4.1　小教程

因为函数是如此重要的一个概念，所以这里给出一系列的步骤供我们练习在真正的Web页面上创建和使用函数：

1.在文本编辑器中打开print_date.html。

首先在文档的开始处添加一个函数。

2.找到页面＜head＞中的＜script＞标记之间的代码，并且录入如下代码：

function printToday（）{

var today=new Date（）；

document.write（today.toDateString（））；

}

基本的函数准备好了，但它现在还不能做任何事情。

3.保存文件并在Web浏览器中预览。

什么也没发生。好了，实际上有些事情确实发生了，只是我们没有看到。Web浏览器把函数语句读入内存，并且等待真正地调用函数，我们将在下面这么做。

4.返回文本编辑器和print_date.html文件。找到以“Today is”开始的＜p＞标签，并在两个＜strong＞标签之间添加如下粗体所示的代码：

＜p＞Today is＜strong＞

＜script＞printToday（）；＜/script＞

＜/strong＞＜/p＞

保存页面并在Web浏览器中预览。当前的日期显示在页面上。如果想要在Web页面的底部也显示日期，所需要做的只是再次调用函数。

3.4.2　给函数提供信息

如果函数能接收到信息就更有用了。回想一下你的助手，那个为你买披萨饼的人。3.4节所描述的最初的“函数”只是说明了如何找到披萨店，指示要购买一块披萨饼然后返回办公室。当你想要某一块披萨饼，就通过告诉助手“买一块披萨饼”来“调用”这个函数。当然，根据你的口味，你可能想要意大利香肠披萨饼、奶酪披萨饼或者橄榄披萨饼。要让你的指令更为灵活，可以告诉助手你喜欢什么类型的披萨。每次你需要一块披萨的时候，可以指定不同的类型。

JavaScript函数也可以接收信息，这种信息叫做参数，函数使用它们来执行操作。例如，如果想要创建一个函数来计算某人购物车的总金额，那么，这个函数需要知道每件商品的价格，以及每件商品订购的数量。

当创建这个函数的时候，在圆括号内放置一个新的变量的名字，这就是参数。基本结构如下所示：

function functionName（parameter）{

//the JavaScript you want to run

}

这个参数只是一个变量，因此，我们提供任何有效的变量名（参见2.4.1节关于命名变量的提示）。例如，假设每次要在Web页面显示内容的时候，都想要减少键盘录入。我们创建一个简单的函数，使用一个更短的名字来替换Web浏览器的document.write（）函数：

function print（message）{

document.write（message）；

}

这个函数的名字是print，并且它还拥有一个名为message的参数。当调用这个函数的时候，它接收一些信息（要显示的消息），然后它使用document.write（）函数把消息显示到页面。当然，函数在调用之前不会做任何事情，因此，在Web页面的某个地方，我们可以像下面这样调用函数：

print（'Hello world.'）；

这段代码运行的时候，调用print函数并且把一些文本（字符串“Hello world.”）发送到函数，然后在页面上显示出“Hello world.”。从技术上讲，向函数发送消息的这个过程叫做“传递一个参数”。在这个例子中，文本“Hello world.”就是参数。

即便是像这样一个真正简单的函数，如果你是编程新手，事情何时发生以及如何发生的逻辑也有点儿容易令人混淆。下面是分解的每个步骤，如图3-7所示：

1.JavaScript解释器读入函数并存储到内存中。这个步骤只是准备Web浏览器稍后运行的函数。

2.调用该函数并且信息“Hello world.”传递给了函数。

3.传递给函数的信息存储在一个新的名为message的变量中。这个步骤等效于var message='Hello World.’。

4.最后，函数运行，在Web页面上显示变量message中存储的值。

图　3-7　在使用函数的时候，通常在使用函数之前创建它。这里的print（）函数在代码的前三行创建，但是，函数中的代码实际上到了最后一行才运行

函数并不仅限于单个参数。我们可以给一个函数传递任意多个参数。只需要在函数中指定每个参数，例如：

function functionName（parameter1，parameter2，parameter3）{

//the JavaScript you want to run

}

然后，使用同样数目的参数，以同样的顺序来调用函数：

functionName（argument1，argument2，argument3）；

在这个例子中，调用functionName的时候，参数argument1存储在parameter1中，argument2存储在parameter2中，以此类推。扩展上面的print函数，假设除了在Web页面上显示一条消息外，我们还想要指定一个HTML标签来包围消息。通过这种方式，可以把消息显示为一个标题或者一个段落。新的函数如下所示：

function print（message, tag）{

document.write（'＜’+tag+’＞’+message+’＜/’+tag+’＞'）；

}

函数调用如下所示：

print（'Hello world.’，'p'）；

在这个例子中，我们给函数传递了两个参数“Hello world.”和“p”。这些值存储在函数的两个变量中，即message和tag中。结果是一个新的段落，＜p＞Hello world.＜/p＞显示在页面上。

不仅限于把字符串传递给函数，可以把任何类型的JavaScript变量或值传递给函数。例如，可以把数组、变量、值或一个Boolean值作为一个参数传递。

3.4.3　从函数获取信息

有些时候，函数只是做一些诸如此类的事情：如在页面上写消息，在屏幕上移动一个对象，或者验证页面上的表单字段。其他时候，我们还希望从函数取回一些信息，毕竟，如果最终没拿到某种可口的披萨饼，那就是“Get me a slice of pizza”函数的效果并不是太好。同样，计算购物车的商品总金额的函数，如果不能使我们知道最终的总金额，说明也不是很有用。

在一些内置的JavaScript函数中，我们已经看到了返回值。例如，prompt（）命令（参见2.7节）弹出了带有一个文本字段的对话框，用户在框中输入的内容将被返回。正如我们已经看到的，可以把返回值存储到一个变量中并且用它做些事情：

var answer=prompt（'What month were you born?’，’'）；

访问者对于提示对话框的响应存储在变量answer中；然后，可以使用条件语句来测试变量中的值，或者对它做其他任何JavaScript允许对变量做的事情。

要从自己的函数返回一个值，可以使用return关键字，其后跟着想要返回的值：

function functionName（parameter1，parameter2）{

//the JavaScript you want to run

return value；

}

例如，假设想要计算包含销售税的销售总金额，可以创建如下所示的一段脚本：

var TAX=.08；//8%sales tax

function calculateTotal（quantity, price）{

var total=quantity*price*（1+TAX）；

var formattedTotal=total.toFixed（2）；

return formattedTotal；

}

第一行代码把税率存储到一个名为TAX的变量中（这使得我们只需要更改这行代码就很容易修改税率）。接下来的3行代码定义了函数。对于函数中发生的事情，现在不需要关心太多，我们将在14.3节学习如何操作数值。重要的部分是函数的第四行代码——return语句。它返回存储在变量formattedTotal中的值。

为了利用返回值，通常将其存储在一个变量中，因此，在这个例子中，可以像下面这样调用该函数：

var saleTotal=calculateTotal（2，16.95）；

document.write（'Total cost is：$’+saleTotal）；

在这个例子中，值2和16.95传递给函数。第一个数值表示购买的商品的数目，第二个数值表示其单价。结果从函数返回，并且存储在一个新变量saleTotal中，该变量用作document.write（）命令的一部分，用于显示销售的总金额（包含税）。

注意：return关键字不应该是一个函数中的最后一句，因为只要浏览器的JavaScript解释器遇到return语句，它会退出该函数。在函数中，跟在return语句之后的任何代码行将不会执行。

然而，不一定必须把返回值存储在一个变量中。可以在另一条语句中直接使用返回值，例如：

document.write（'Total：$’+calculateTotal（2，16.95））；

在这个例子中，调用函数并且将其返回值添加到字符串“Total：$”后面，然后显示到文档。首先，这种使用函数的方法很难阅读，因此，我们可能希望采用一个额外的步骤只是把函数的结果存储到一个变量中，然后在脚本中使用该变量。

注意：一个函数只能返回一个值。如果想要返回多个项目，可以把值存储在一个数组中并返回数组。

3.4.4　防止变量冲突

函数的一个很好的优点是，它们可以减少必须编写的程序的数量。我们可能会发现自己在不同的项目上一次又一次地使用一个真正有用的函数。例如，辅助计算运输税和销售税的函数可能对于我们所创建的每个订单都有用，因此，可能会把函数复制到站点上或其他项目中的其他脚本中。

当只是把函数放入一个已经创建的脚本中时，会产生一个潜在的问题。如果脚本使用和函数同样的变量名，会发生什么情况呢？函数会覆盖脚本中的变量吗？或者情况刚好相反？例如：

var message='Outside the function'；

function warning（message）{

alert（message）；

}

warning（'Inside the function'）；//'Inside the function'

alert（message）；//'Outside the function'

注意，变量message既出现在函数之外（脚本的第一行代码），也作为函数中的一个参数。参数实际上是在函数调用的时候才会使用数据填充的一个变量。在这个例子中，函数调用warning（'Inside the function'）给函数传递了一个字符串参数，并且函数把这个字符串存储到message变量中。这看上去似乎message变量有两个版本。那么，在脚本的第一行创建的最初的message变量中的值发生了什么？

你可能会认为存储在message中的最初的值被一个新值覆盖了，这个新值就是字符串“Outside the function”，但实际上不是这样。当运行这段脚本的时候，会看到两个对话框：第一个显示“Inside the function”，而第二个显示“Outside the function”。实际上有两个名为message的变量，但是，它们存在于不同的位置（如图3-8所示）。

图　3-8　函数参数只在函数的内部可见，因此，这个函数的第一行function warning（message）会创建一个名为message的新变量，这个变量只能在函数内部访问。一旦函数结束，这个变量也消失了

JavaScript变量对于在函数内部声明的变量和在函数外部创建的变量区别对待。用编程的术语来说，每个变量都有自己的作用域。函数的作用域就像是包围起函数的一堵墙，墙内部的变量不会被墙外部的其他脚本所看到。当你第一次学习作用域的时候，它确实是一个容易混淆的概念，但是它非常有用。因为函数有自己的作用域，所以不必担心在函数中为参数使用的名字会覆盖在脚本其他部分所使用的变量或与之发生冲突。

到目前为止，我们只是讨论了变量作为参数使用的情况。但是，对于在函数内部创建的但不是用作参数的变量，会怎么样呢？例如：

var message='Outside the function'；

function warning（）{

var message='Inside the function'；

alert（message）；

}

warning（）；//'Inside the function'

alert（message）；//'Outside the function'

这段代码创建了message变量两次，在脚本的第一行创建一次，在函数内的第一行再创建一次。这种情况和使用它作为参数的情况相同，只不过在函数中输入了var message，我们在函数的作用域内创建了一个新的变量。这种类型的变量叫做局部变量（local variable），既然它只在函数的围墙之内可见，主脚本和其他函数是不能看到或访问这个变量的。

然而，在脚本的主要部分（函数之外）创建的变量则存在于全局作用域内。脚本中所有的函数都可以访问在其主体内创建的变量。例如，在下面的代码中，变量message创建于脚本的第一行，它是一个全局变量，并且它可以被函数访问。

var message='Global variable'；

function warning（）{

alert（message）；

}

warning（）；//'Global variable'

这个函数没有任何参数，并且没有定义一个message变量，因此，当alert（message）部分运行的时候，函数查找一个名为message的全局变量。在这个例子中，该变量存在，因此显示一个带有文本“Global variable”的警告框。

关于局部变量和全局变量，有一个潜在的陷阱，如果一个变量是参数，或者如果一个变量通过var关键字创建于函数的内部，它只存在于函数的作用域中。图3-9说明了这种情况。代码的上半部分展示了一个名为message的全局变量和一个名为message的函数局部变量，二者可以并列存在。关键在于，函数中的第一行代码var message='Inside the function'；。通过使用var，我们创建了一个局部变量。

把这个和图3-9下半部分的代码相比较。在下半部分代码中，函数没有使用var关键字。相反，代码行message='Inside the function'；没有创建一个新的局部变量；它直接把一个新值存储到全局变量message中。结果呢？这个函数干扰了全局变量，替换掉了其初始值。

图　3-9　在函数中给变量赋值的时候，有一个微妙而至关重要的区别。如果想要变量只能在函数内访问，确保在函数中使用var关键字来创建变量（如图上半部分所示）。如果没有使用var，只是把一个新值存储到全局变量中（如图下半部分所示）

变量作用域的概念确实很容易令人混淆，因此，现在提前讨论这个问题对我们没有太多意义。但是，请记住，如果在脚本中创建的变量似乎没有存储我们期望的值，可能是遇到了作用域问题。如果遇到这种情况，可以返回来阅读这一节。

3.5　教程：一个简单测验

是时候把本章学习的内容组合到一起来创建一个完整的程序了。在这个教程中，我们将创建一个简单测验系统，它提出问题并评估参加测验者的表现。首先，本节将看看可能解决这个问题的一些方式，并且讨论编程的高效技巧。

第一步总是搞清楚程序要做什么。下面是我们希望程序完成的一些事情：

·询问问题。如果我们想要测验一个人，需要一种方式向他提出问题。此时，我们知道从Web页面获取反馈的一种简单方法：prompt（）命令。此外，我们需要一个问题列表，既然数组可以很好地用来存储信息列表，那么我们将使用数组来存储测验问题。

·让测验参加者知道自己的答案是否正确。首先，需要确定测验参加者是否给出了正确答案：一个条件语句可以负责这项工作。然后，为了让测验参加者知道他的回答是正确的或错误的，可以使用alert（）命令。

·显示测验结果。我们需要一种方式来记录测验参加者做得有多好，只需要一个变量来记录正确回答的数目就够了。然后，为了宣布测验的最终结果，可以使用alert（）命令或document.write（）方法。

有很多种方法可以解决这个问题。一些程序员新手可能会采用生硬的方法，并且复制同样的代码来询问每个问题。例如，询问测验中的头两个问题的JavaScript可能如下所示：

var answer1=prompt（'How many moons does Earth have?’，’'）；

if（answer1==1）{

alert（'Correct！'）；

}else{

alert（'Sorry.The correct answer is 1'）；

}

var answer2=prompt（'How many moons does Saturn have?’，’'）；

if（answer2==31）{

alert（'Correct！'）；

}else{

alert（'Sorry.The correct answer is 31'）；

}

这种方法似乎合乎逻辑，因为程序的目的是一个接着一个地询问问题。但是，这并不是高效的编程方式。无论何时，当你看到程序中有相同的步骤重复多次时，应该考虑使用循环或函数来替代。我们将创建一个程序把这二者都用到：使用一个循环来遍历测验中的每个问题，并且使用一个函数来执行询问问题的任务：

1.在文本编辑器中打开文件quiz.html。

首先，可以设置几个变量来记录正确回答的问题的数目以及测验中问题的数目。

2.找到页面＜head＞中的＜script＞标签之间的代码，并且输入如下代码：

var score=0；

这个变量存储了测验参加者回答正确的问题的数目。在测验刚开始，回答任何问题之前，我们把这个变量设置为0。接下来，可以创建问题及其答案的一个列表。

3.按下回车键添加新的一行，并输入var questions=[。

我们将把所有的问题存储到一个数组中，数组只不过是可以存储多个项目的一个变量。我们刚刚输入的代码是数组语句的第一部分。我们将分多行输入数组，如2.8.1节所介绍的那样。

4.按下回车键两次，增加两个新行，并输入]；。代码如下所示：

var score=0；

var questions=[

]；

既然测验是由一系列的问题组成，那么，把每个问题存储为数组的一个项目就有意义了。然后，当你需要询问测验问题的时候，直接遍历列表中的每个项目来提问。然而，每个问题还有一个答案，因此，我们需要一种方法来记录答案。

一个解决方案是创建另一个数组，例如answers[]，它保存了所有的答案。要询问第一个问题，查找questions数组中的第一项，并且看看答案是否正确。然而，这有一个潜在的缺陷，就是两个列表可能会不同步，例如，我们在questions数组的中间位置添加了一个问题，但是却把答案放在了answers数组的开头。这时候，questions数组的第一项不再和answers数组的第一项匹配。一种更好的替代方案是使用一个嵌套的数组或者（如果你真的想要听起来更加引人注意和令人激动的话）使用一个多维数组。这只是意味着，我们要创建一个包含问题和答案的数组，并且把这些数组作为questions数组的一个项目来存储。换句话说，我们创建了这样的列表，而其中的每个项目都是另外的一个列表。

5.单击[和]之间的空行，并且添加如下粗体所示的代码：

var questions=[

['How many moons does Earth have?’，1]，

]；

代码['How many moons does Earth have?’，1]是拥有两项的一个数组。第一项是一个问题，第二项是答案。这个数组是数组questions中的第一项。我们不会给这个数组一个名字，因为它嵌套在另一个数组中。这一行末尾的逗号表示questions数组的第一项结束，而后面跟着的是另一个数组项目。

6.按下回车创建一个新的空白行，并把如下两行粗体代码添加到脚本中：

var questions=[

['How many moons does Earth have?’，1]，

['How many moons does Saturn have?’，31]，

['How many moons does Venus have?’，0]

]；

这一测验还有另外两个问题。注意，在数组的最后一项之后，不要输入一个逗号。在一个单个的数组中设置好所有的问题，这具有更大的灵活性。如果想要向列表添加一个问题，只要添加包含新的问题和答案的另一个嵌套的数组就可以了。

既然设置好了测验用到的基本变量，是时候搞清楚如何询问每个问题了。问题存储在一个数组中，并且我们希望询问列表中的每个问题。还记得在本书3.3.2节介绍过，循环是遍历数组中的每一项的一种理想方法。

7.在]；（answers数组的末尾）的后面单击，按下回车键创建一个新的空行，并添加如下代码：

for（var i=0；i＜questions.length；i++）{

这一行是for循环的开始（参见3.3.3节）。它做三件事情：第一，创建一个新的名为i的变量并在其中存储数值0。这个变量是一个计数器，记录了循环迭代的次数。第二，i＜questions.length是一个条件，就像if/else语句中的条件一样。它测试i中的值是否小于questions数组中的项目的数目，如果是，循环再次运行。一旦i等于或大于数组中的项目数，循环结束。第三，每次通过循环的时候，i++都改变i的值，它给i值加1。

现在是时候进入循环的核心部分了，即每次迭代循环实际执行的JavaScript代码。

8.按下回车键创建一个新的空行，并且添加如下代码行：

askQuestion（questions[i]）；

我们只是运行一个函数来提问题，而不是把用来提问的所有程序代码都放入

循环中。这个函数（随后创建）叫做askQuestion（）。每次迭代循环的时候，我们都把questions数组的一项发送到该函数，即questions[i]部分。别忘了，我们使用一个索引值来访问数组中的一项，因此，questions[0]是数组中的第一项，questions[1]是第二项，以此类推。

通过创建一个函数来提问，我们可以编写更加灵活的程序。如果需要的话，我们可以把函数移动到其他的程序中来复用。最后，我们将完成循环代码。

9.按下回车键创建一个新的空行，并且输入}表示循环的结束。完成后的循环代码如下所示：

for（var i=0；i＜questions.length；i++）{

askQuestion（questions[i]）；

}

是的，这就是所有的代码，只是一个简单的循环，它使用测试中每个问题来调用函数。现在，我们将创建测验的核心部分，即askQuestion（）函数。

10.在刚才添加的for循环的前面创建一个空行。

换句话说，在脚本开始定义基本变量的两条语句和刚刚添加的循环之间，添加这个函数。在脚本中任何地方定义函数都没有问题，但是，大多数程序员把函数定义放在靠近程序的开始处。在很多脚本中，像这个脚本中的score和questions那样的全局变量都先定义了，以便我们很容易看到或改变这些变量；接下来是函数，因为它们通常是构成大多数脚本的核心内容；最后是像循环这样的一步一步的操作。

11.添加如下的代码：

function askQuestion（question）{

}

这段代码表示函数的主体；先录入一个函数的开始花括号和结束花括号，然后再在其中添加脚本，这是一个不错的办法。通过这种方式，我们不会偶然忘记添加结束花括号。

这个函数接受一个单个的参数并且将其存储在一个名为question的变量中。注意，这和我们在步骤6中所创建的questions[]数组不同。在这个例子中，question变量实际上是使用questions[]数组中的一项来填充的。正如我们在步骤8中所见到的，数组中的一项实际上是包含两项的另一个数组，这两项是question和answer。

12.添加如下粗体所示的代码：

function askQuestion（question）{

var answer=prompt（question[0]，’'）；

}

这看上去有些似曾相识，这是prompt（）命令。唯一可能觉得比较新的部分就是question[0]。这是访问question数组的第一项。在这个例子中，这个函数接受一个数组，它包含一个问题和一个答案：例如，第一个数组是['How many moons does Earth have?’，1]。因此，question[0]访问了第一项，即'How many moons does Earth have'，它传递给prompt（）命令，作为将要出现在提示对话框中的问题。

我们的程序把测验参加者在提示框中输入的内容添加到变量answer中。接下来，我们将把测验参加者的回答和问题的实际答案进行比较。

13.通过添加如下所示的粗体代码来完成函数：

function askQuestion（question）{

var answer=prompt（question[0]，’'）；

if（answer==question[1]）{

alert（'Correct！'）；

score++；

}else{

alert（'Sorry.The correct answer is'+question[1]）；

}

}

这段代码只是一条基本的if/else语句。条件answer==question[1]检查用户输入（回答）的是否和答案相同，答案则存储在数组的第二项（question[1]）中。如果匹配，那么测验参加者答对了：显示一条警告信息让参加者知道答对了，并且他的分数增加1（score++）。当然，如果他没有答对，一条消息会给出正确的答案。

此时，测验功能完整了。如果保存文件并在Web浏览器中载入它，我们将能够进行测验。然而，我们还没有为测验参加者提供结果，以便他知道答对了多少问题。我们将在Web页面的＜body＞中添加一段脚本，以显示结果。

14.找到靠近Web页面底部的第二对＜script＞标记，在其中输入如下代码：

var message='You got'+score；

这里，我们创建了一个新的变量并把字符串“You got”以及测验参加者的分数存储到其中。因此，如果他答对了3个问题，变量message将会是“You got 3”。为了让脚本更容易阅读，我们将用数行代码来构建一条更长的消息。

15.按下回车键并输入如下代码：

message+='out of'+questions.length；

这会向消息字符串中添加“out of”以及问题的总数，因此，此时，消息会像“You got 3 out of 3”这样。现在，完成消息并将其显示到屏幕。

16.把代码中粗体显示的行添加到脚本中：

var message='You got'+score；

message+='out of'+questions.length；

message+='questions correct.’；

document.write（'＜p＞’+message+’＜/p＞'）；

保存页面并在Web浏览器中打开它。进行测验，并看看能够做到多好（如图3-10所示）。如果这个脚本不工作，请参考1.6节提到的一些排错技术。我们也可以把这个脚本和complete_quiz.html文件中完整的、功能完备的版本做比较。

尝试在脚本的开始处向questions[]数组中添加几个问题，以便测验能够更长。

图　3-10　简单的测验程序的结果。在本书4.7节，我们将学习如何操作一个W e b页面；在本书第5章，我们将学习如何对事件做出响应；在本书第9章，我们将学习如何操作页面。学完了这些知识之后，我们可以尝试重新编写这个测验程序，以便让问题直接出现在W e b页面中，并且在每次回答后动态地更新分数。换句话说，我们很快将学习如何避免使用枯燥的prompt（）命令

既然我们已经学习了JavaScript中并不令人兴奋且颇费脑筋的细节，是时候把注意力转向真正的乐趣了。在第二部分中，我们将学习jQuery，了解jQuery是什么，如何使用它，还有最重要的，如何通过JavaScript编程享受乐趣和完成实际任务。

第二部分：jQuery入门

第4章　jQuery简介

第1章介绍了JavaScript编程语言的很多基础知识，包括JavaScript的关键字、概念和语法等。很多这样的概念都相当简单（例如，变量就像是在其中存储值的一个盒子），但是，有些主题可能让你抓耳挠腮或者头疼不已（例如，3.3.3节介绍的for循环）。实际上，对于大多数人来说，JavaScript编程是很难的。关于JavaScript的一切以及它是如何在如此众多的不同Web浏览器中工作的，即便是一本1000页的关于JavaScript编程图书也不能介绍完。

编程是很难的：这就是为什么本书既介绍JavaScript又介绍jQuery。正如4.1节所介绍的，jQuery是一个JavaScript库，它通过处理JavaScript编程的诸多细节，让你快速开始编程。jQuery的信条是“编写更少代码，完成更多事情”，这让编程充满乐趣、更加快速，并且有所回报。使用jQuery，你可以只用一行代码来实现以前需要编写100行JavaScript代码才能做到的事情。当你学完本章和下一章之后，和只是学习完1 000页的JavaScript图书相比，你通过Web页面所能做到的事情要更多。

4.1　JavaScript库简介

很多JavaScript程序必须一次又一次地处理同一组Web页面任务：选择元素、添加新的内容、隐藏和显示内容、修改标签的属性、确定表单字段的值，以及让程序对不同的用户交互做出响应。这些基本操作实际上都是相当复杂的，尤其是，如果你希望程序在所有主流浏览器中工作更是如此。幸运的是，JavaScript库提供了可以省略以前那些耗费时间的编程细节的一种方式。

JavaScript库只不过是JavaScript代码的一个集合，这些代码为很多常见的、日常的JavaScript细节提供了简单的解决方案。我们可以把库看做预先编写的JavaScript函数的一个集合，可以把这些函数添加到自己的Web页面中。这些函数使得完成常见任务变得很容易。在很多情况下，可以用一个JavaScript库中的单个函数来替换自己的JavaScript程序中的多行代码（这还节省了用来测试这些代码所需要的时间）。冗长的程序代码已经过时了。有很多JavaScript库，并且其中的很多都用于像Yahoo、Amazon、CNN、Apple、Twitter以及更多其他的主流Web站点上。

本书使用流行的jQuery库（www.jquery.com）。也有其他的JavaScript库（参见后面的“快速熟悉：其他库”部分的介绍），但是jQuery有如下的很多优点：

·相对较小的文件大小。最小版本的库大约只有90KB，如果你的Web服务器使用“gzip”压缩，可以使文件大小只有30 KB。

·对Web设计师友好。jQuery并不假设你是一位计算机科学家。它利用了大多数Web设计师已有的CSS方面的知识。

·它经过尝试并且证明可用。jQuery用在了数以百万计的站点上，包括很多流行的、高流量的Web站点，例如Digg、Dell、the Onion和NBC，甚至Google也在某些地方使用它，并且它已经内置到了头号博客软件WordPress中。jQuery如此流行的事实，足以说明它有多么好。

·它是免费的。这真是不可抗拒。

·拥有很大的开发者社区。正如你已经看到的，大量的人员从事jQuery项目，如编写代码、修改bug、增加新的功能并且用文档和教程更新其Web站点。对于那些由单个程序员创建的库（或者由单个作者支持的库）来说，如果这个程序员（或作者）逐渐厌倦了这个项目，那么该项目很容易就消失了。另外，jQuery在全世界程序员的努力下，应该可以流行很长的时间。这就好像有大堆的JavaScript程序员在免费为你工作。

·插件、插件、插件。jQuery允许其他程序员创建插件JavaScript程序，这些插件和jQuery一起使用，使得某些任务、效果或功能可以非常容易地添加到Web页面中。在本书中，我们将学习那些可以验证表单、添加下拉导航菜单以及构建交互式幻灯片放映的插件，这些工作可以在半个小时内实现，而不是要花两周时间的项目。实际上，有数以百计的其他插件可供jQuery使用。

我们已经在本书中真正用到了jQuery。在1.5节的教程中，只是添加了数行JavaScript代码就给页面添加了淡入效果。

快速熟悉其他库

jQuery不是唯一的JavaScript库。还有很多其他的库。其中的一些设计用来执行特定的任务，另一些则是以解决存在的每个JavaScript任务为目标的通用库。下面是一些最为流行的库：

·Yahoo User Interface Library（http：//developer.yahoo.com/yui/）是Yahoo的一个项目，并且实际上该公司在自己的站点上全面使用它。Yahoo程序员不断地增加和改进这个库，并且他们在YUI站点上提供了丰富的文档。

·Dojo Toolkit（http：//dojotoolkit.org/）是另一个发布了很长时间的库。它功能很强大，是处理几乎所有JavaScript任务的JavaScript文件的一个大集合。

·Mootools（http：//mootools.net/）是另一个流行的库，具有很好的文档和一个漂亮的Web站点。

·Prototype（http：//www.prototypejs.org/）是最早可用的JavaScript库之一。它通常和一个名为scriptaculous（http：//script.aculo.us/）的视觉效果库组合使用，后者添加了动画和其他的用户界面组件。

4.2　获取jQuery

jQuery只是一个外部JavaScript文件中的一组JavaScript程序。和任何外部JavaScript文件一样（参见本书1.2节），需要将其链接到自己的Web页面。然而，由于jQuery如此流行，当将其添加到一个Web页面的时候，可以有几种选择：要么使用Google、Microsoft或jQuery.com寄存的一个版本，要么可以将jQuery文件下载到自己的计算机上并且将其添加到Web站点中。

第一种方法使用一个CDN（Content Distribution Network，内容分发网络），也就是说，另一个Web站点寄存了jQuery文件，并且将其发送给请求它的任何人。这种方法还有几个优点：首先，你可以通过允许Google、Microsoft或jQuery将文件分发给站点访问者，而为自己的Web服务器节省若干毫秒的时间。其次，CDN还有额外的优点，就是拥有位于全球的服务器。例如，如果某人从新加坡访问你的站点，他将会从比到你的Web服务器要近得多的一台服务器接受jQuery文件，这意味着，他将更快地获取文件，并且你的站点将貌似运行得更快。最后，也是最重要的一点，由于其他的设计者也使用这些CDN，很可能访问你的站点的某人已经在自己的浏览器缓存中拥有了jQuery文件。由于他在访问另一个站点的时候已经从Google下载了jQuery文件，当访问你的站点的时候，他不需要再次下载，这会带来显著的速度提升。

使用CDN也有几个缺点：首先，访问者需要连接到互联网，这种方法才会有效。如果需要保证你的站点离线也能工作（例如，在博物馆的展示亭或者在教室中编程演示的过程中），这就是个问题。在这种情况下，需要从jQuery.com下载jQuery文件（下面将学习如何做到这点）并且将其添加到Web站点中。添加自己的jQuery文件，将能够保证如果CDN服务无效时你的站点也能继续工作（当然，如果Google的服务器失效，那么可能真的有大麻烦，你的Web站点可能也无法工作）。

连接到CDN服务器上的jQuery文件Microsoft、jQuery和Google都允许使用它们的服务器，将jQuery包含到Web页面中。例如，要使用Microsoft的CDN连接到jQuery1.6.3，可以在Web页面的＜head＞中添加如下这行代码：

＜script src="http：//ajax.aspnetcdn.com/ajax/jQuery/jquery-1.6.3.min.js"＞

＜/script＞

要使用jQuery CDN，将用到如下的代码：

＜script src="http：//code.jquery.com/jquery-1.6.3.min.js"＞＜/script＞

要使用Google的CDN，代码如下所示：

＜script src="http：//ajax.googleapis.com/ajax/libs/jquery/1.6.3/jquery.min.

js"＞＜/script＞

根据你想要使用的CDN，只需要在自己的页面上使用这些代码行之一即可。Google CDN似乎是最流行的，因此，如果你不确定要使用哪一个，就使用Google服务器。

下载自己的jQuery文件

可以很容易地下载jQuer y文件并将其添加到站点以及所有其他的Web页面和文件中。从www.sawmac.com/js2e/下载的本书教程文件包含jQuery库文件，但由于jQuery团队定期更新该库，因此可以在http：//docs.jquery.com/Downloading_jQuery找到最新版本，它在Current Release标题下列出。

下载最新版jQuery的方法：

1.访问http：//docs.jquery.com/Downloading_jQuery。

这个页面有关于代码的信息、上面所提及的CDN列表，以及jQuery之前的版本。你可以找到“Current Release”。

2.单击页面顶部的“CurrentRelease”链接，或者向下滚动直到看到“Current Release”标题。

在该下载站点上，有两个版本的jQuery文件——最小化版和未压缩版。未压缩版文件很大（超过200KB），并且该版本供你通过查看代码以对jQuery了解更多。代码包含很多注释（参见2.11节），它们有助于了解文件的不同部分的作用（但是，要理解这些注释，你需要较多的JavaScript知识）。

你应该在自己的站点中使用最小化版本。最小化的文件比常规的JavaScript文件还要小很多：所有的JavaScript注释和不必要的空白（制表符、换行符等）都删除了，使得文件难以阅读，但是下载速度很快。

注意：通常可以通过文件名中的.min来识别一个最小化的JavaScript文件，例如，jquery-1.6.3.min.js表示该文件包含jQuery1.6.3版的最小化版本。

3.鼠标右键（在Mac上是Control-单击）单击Minified链接，从弹出菜单中选择Save Link As。

如果只是单击该链接，不会下载这个文件：相反，Web浏览器会在一个浏览器窗口中显示所有的代码，因此，需要使用“Save as”这种方法。

4.在计算机上找到保存Web站点的文件夹，并保存该文件。

可以把jQuery文件放在站点上的任何位置，但是，很多Web设计师将他们的外部JavaScript文件放在一个专用的文件夹中。通常，该文件夹的名称类似于scripts、libs、js或_js。

4.3　把jQuery添加到页面

如果你使用jQuery的CDN版本之一，可以使用4.2节列出的代码段之一来指向它。例如，要使用jQuery的Google CDN版，可以在页面的头部添加＜script＞标签，如下所示：

＜script src="http：//ajax.googleapis.com/ajax/libs/jquery/1.6.3/jquery.min.

js"＞＜/script＞

提示：当使用Google CDN的时候，可以省略版本号码部分。如果在链接中使用1.6而不是1.6.1（＜script src="http：//ajax.googleapis.com/ajax/libs/jquery/1.6/jquery.min.js"＞＜/script＞），那么Google会加载1.6系列中的最新版，例如，1.6.3。如果jQuery更新到1.6.9，那么Google会加载这个版本。这一技术很智能化，因为（正如本节所介绍的），从1.6.1到1.6.9的小版本变化，通常只是进行一些bug修复以提升站点的性能。

常见问题jQuery版本

我确保本书使用jQuery1.6.3，但是，jQuery站点上的当前版本是1.X。这是个问题吗？

jQuery不断更新。常常会发现新的bug，并且jQuery团队很勤奋地工作以修改这些bug。此外，随着带有新功能的新Web浏览器出现以及为了更好地支持当前的标准，jQuery团队更新jQuery以便能够在这些Web浏览器中更有效地工作。最终，有时候会向jQuery中添加新的功能，以使它对Web程序员更有用。基于这些原因，你很可能会找到比本书所用的版本更新的jQuery。如果有新的版本，那么尽量使用新版本。

jQuery随着时间不断成熟，并且其核心功能变化很小。尽管jQuery程序员常常深入其中一探究竟，以使得jQuery更快、更好地跨浏览器工作并且修正bug，但是，使用jQuery的方式通常没有什么大的变化。换句话说，尽管程序员可能会修改一个jQuery函数以便更好地工作，但是，使用函数的方式（函数名称、给函数的参数以及它返回的值）很少改变。这意味着，你从本书中学到的知识很可能也适用于jQuery的新版本，只不过新版更快且更好。

通过版本号，你很容易分辨两个jQuery版本之间有多大区别。第一个数字表示非常重要的新

版本。目前的jQuery的主版本号仍然是1，因此，我们看到过版本1、1.2、1.3和1.4等。版本2（可能还需要相当长的时间才会出现）肯定会提供一些重要的新功能。然后，还有很多带有小版本号的版本，例如jQuery 1.6。这些版本中的每一个，通常都提供一些新的函数，重新编写一些旧的函数以使其更好地工作等。最后的版本号，例如jQuery 1.6.3中的3，通常指的是对jQuery1.6分支的某些bug修复。因此，如果你正在使用jQuery1.6.3，而jQuery1.6.8版发布了，那么，升级到新版本通常是个好主意，因为新版通常会修正1.6.3版中的一些问题。

要搞清楚新版中有哪些变化，只要浏览http：//docs.jquery.com/Downloading_jQuery#Current_Release的下载页面中的“Current release”部分。然后，你会找到“Release notes”的一个链接。单击该链接，所访问的页面列出了该版本中做出的修改。在阅读了该列表后，你可以自行决定是否值得升级（例如，如果修改的相关功能你在自己的站点上也不会用到，你可以跳过这次升级；然而，如果这些修改是你确实用到的功能的bug修复，那么最好升级。如果你在自己的站点上使用jQuery插件，对于升级到jQuery的最新版本要多加小心，除非你确定该插件能够用于jQuery的新版本）。

一旦把jQuer y下载到计算机中，就必须将其附加到想要使用它的Web页面上。jQuery文件只是一个外部.js文件，因此，可以像任何外部JavaScript文件一样附加它，具体步骤参见1.2节的介绍。例如，假设你在自己的站点的根目录中一个名为js的文件夹中存储了jquery.js文件。要将该文件附加到你的主页中，可以在该页面的头部添加如下script标签：

＜script src="js/jquery-1.6.3.min.js"＞＜/script＞

一旦已经附加了jQuery文件，就准备好了添加自己的脚本，从而利用jQuery的高级功能。下一步是添加第二组＜script＞标签，在其中带有一些jQuery程序：

＜script src="js/jquery-1.6.3.min.js"＞＜/script＞＜script＞

$（document）.ready（function（）{

//your programming goes here

}）；

＜/script＞

第二组＜script＞标签包含你想要添加到特定Web页面中的任何程序，然而，你可能会有疑惑，那个$（document）.ready（）有什么作用。$（document）.ready（）函数是一个内置jQuery函数，它会等待页面的HTML加载之后再运行脚本。

为什么你想要这么做呢？因为很多JavaScript编程都要操作Web页面的内容：例如，对一个div设计动画，将图像淡入视野，当访问者把鼠标指针悬停到一个链接上的时候产生下拉菜单等。要做一些有趣的事情并且和一个页面元素交互，JavaScript需要选中该元素。然而，在Web浏览器下载HTML标签之前，JavaScript不能选中它。由于Web浏览器立即运行它所遇到的任何JavaScript，因此页面剩下的内容还没有立刻下载（在第3章的测验教程中，你可以看到这种效果）。当载入测验的时候，页面是空白的。只有在完成了测验之后，内容才会出现，这是因为进行测验的JavaScript先运行，而这发生在Web浏览器显示HTML标签之前。

换句话说，为了让页面上的HTML完美，需要等待页面加载。这就是$（document）.ready（）函数所做的工作：它直接等待，直到HTML完成加载，然后，它运行JavaScript代码。如果这有些令人混淆，只要记住，你总是需要包含一个.ready（）函数，并且，需要把代码放入其中，放在$（document）.ready（function（）{和最后的}）；之间。

此外，还要记住如下几点：

·到jQuery的链接必须放在任何依赖于jQuery的程序之前。换句话说，在加载jQuery的＜script＞标签之前，不要放置任何其他的＜script＞标签。

·把JavaScript程序放置在任何CSS样式表之后（包括链接的样式表、外部样式表和内部样式表）。由于jQuery程序经常引用样式表中的样式，应该在Web浏览器已经加载了任何样式之后，再放置JavaScript程序。经验法则是，将JavaScript程序（所有的＜script＞标签）放在＜head＞标签中的任何其他内容之后，但是，在结束的＜/head＞标签之前。

·在}）；之后添加一条JavaScript注释，例如，//end ready，表明ready（）函数结束。例如：

$（document）.ready（function（）{

//your programming goes here

}）；//end ready

在函数的末尾放置一条注释，这使得很容易识别程序的结束。随后你将看到，jQuery很多时候要用到花括号、圆括号和分号的组合。通过在其后添加注释，很容易识别这一组标点符号属于程序的哪一部分。

提示：jQuery提供了一种快捷方式，来编写$（document）.ready（function（）{}：

$（function（）{

//your programming goes here

}）；//end ready

4.4　修改Web页面：概览

JavaScript赋予了我们动态修改Web页面的能力。通过使用JavaScript，可以添加图片和文本、删除内容或者立即改变页面上元素的外观。实际上，动态改变Web页面的内容是支持JavaScript的最新的Web站点的一个特点。例如，Google Maps（http：//maps.google.com/）提供了对世界地图的访问；当放大地图或者滚动地图，页面会更新而不需要载入一个新的Web页面。类似地，当把鼠标指针停留在Netflix（www.netflix.com）上的一部电影名上的时候，在页面顶端会出现一个信息框，提供有关电影的更详细的信息（如图4-1所示）。在这些例子中，JavaScript都改变了Web浏览器最初下载的HTML。

在本章中，我们将学习如何使用JavaScript修改Web页面。我们将添加新的内容、HTML标签和HTML属性，并且还将修改页面上已有的内容和标签。换句话说，将使用JavaScript来产生新的HTML并改变页面上已有的HTML。

图　4-1　JavaScript可以使得Web页面更容易扫视或阅读，只在需要那些内容的时候才显示它们。在Netflix.com上，电影的描述信息隐藏了，但是，当鼠标指针移动到电影名或缩略图上的时候会显示出来

这似乎令人难以置信，但是，如果你知道如何使用HTML和CSS创建Web页面，你就已经知道了有效地使用JavaScript来创建交互性Web站点所需的很多知识。例如，jQuery UI项目的流行插件Datepicker，使得访问者能够很容易地从一个表单上选择一个日期（例如，作为航班预订或活动计划的一部分）。当访问者单击一个特别标记的文本字段的时候，会弹出一个日历（如图4-2所示）。尽管效果真的很酷，并且日历使得访问者很容易选择日期，但JavaScript只是提供了交互性，实际的日历创建同样使用了你所熟悉的旧的HTML和CSS知识。

图　4-2　jQueryUI（http：//jqueryui.com）为Web应用程序提供了有用的用户界面挂件。例如，Datepicker提供了一种易用的、用户友好的方法来选定一个日期

如果深入到这个日历内部，你会看到诸如div、table和＜td＞标签等一系列HTML标签，它们带有特定的class和ID（ui-datepicker-month、ui-datepicker-div等）。通过带有class和ID styles的一个样式表来添加颜色、字体和格式。总而言之，可以使用HTML和CSS来自行创建同一个日历。JavaScript只是生成显示性的交互：当访问者在一个表单字段上单击的时候，让日历显示出来；并且当访问者选中一个日期的时候，让日历消失。

因此，有一种对现代JavaScript编程的认识（特别是当它应用于用户界面设计的时候），认为其是一种将HTML的创建和CSS的应用自动化的方式。在图4-1中的Netflix例子中，当访问者把鼠标指针悬停在一部电影名上的时候，JavaScript让弹出的信息提示显示，但是，真正有趣的部分（信息提示的设计）善用了HTML和CSS，这些事情你已经知道如何做了！

因此，使用JavaScript的目的主要是操作一个Web页面，通过添加新的内容、更改页面的HTML或者对一个元素应用CSS。不论何时改变一个页面的内容、HTML或CSS，不管是添加用弹出菜单实现的导航栏、创建一个JavaScript驱动的幻灯片，或者是直接让页面淡入视线（就像在第1章教程中所做的那样），我们都要执行两个主要步骤。

1.标识页面上的一个元素。

元素是一个已有的标签，并且需要首先在JavaScript中标识它（将在本章学习如何做到这点），然后才能够对元素做某些事情。例如，要让页面淡入到视野中，首先必须选择页面的内容（＜body＞标签）；要让鼠标指针移动到一个按钮上的时候出现弹出菜单，需要选择该按钮。即便只是想要使用JavaScript在Web页面的底部添加JavaScript文本，也需要标识一个标签，以便在标签之中、之前或之后插入文本。

2.使用元素做一些事情。

没错，“做一些事情”并不是非常明确的指令。这是因为对于一个元素可以做几乎无穷多的事情，从而改变Web页面的外观和行为。实际上，本书的大部分篇幅都用于教你对页面元素做不同的事情。下面是一些例子：

·改变元素的属性。例如，当在页面中实现一个＜div＞的动画的时候，改变该元素在页面上的位置。

·添加新的内容。如果在填充Web表单的时候，访问者错误地填写了一个字段，通常会产生一条错误消息“Please supply an email address”。在这种情况下，就在某处添加了和该表单字段相关的内容。

·删除元素。在图4-1中的Netflix示例中，当鼠标光标从电影名上移开的时候，弹出信息框就消失了。在这个例子中，JavaScript从页面删除了弹出的信息框。

·从元素中提取信息。其他时候，我们想要知道已经标识的标签的某些内容，例如，要验证一个文本字段，需要标识该文本字段，然后发现在该字段输入了什么文本，换句话说，需要获取该字段的值。

·添加/删除一个类属性。有的时候，我们想要使页面上的元素改变外观：段落中的文本变为蓝色，或者文本字段的背景颜色变为红色，以表示一个错误。尽管JavaScript可以进行这些视觉变化，最容易的方式往往是直接应用一个类，并且让Web浏览器根据样式表中的一个CSS样式来做出这些视觉改变。例如，要将一个段落的文本改为蓝色，可以直接创建带有蓝色文本颜色的一个类样式，并且，使用JavaScript将这个类动态地应用于该段落。

很多时候，要同时做上面列出的几件事情。例如，假设想要确保访问者不会忘记在一个表单字段中输入其E-mail地址。如果他尝试提交表单而没有输入E-mail地址，你可以提醒他。这个任务可能包括首先搞清楚他是否在该文本字段中输入了什么内容（从该元素提取信息）；如果他没有输入，输出一条错误消息（“adding new content”），并且突出显示该表单字段（通过给该文本字段添加一个类实现）。

选择一个页面元素是第一步。要理解如何使用JavaScript来标识和修改页面的一部分，首先需要知道文档对象模型。

4.5　理解文档对象模型

当Web浏览器载入一个HTML文件时，它会在屏幕上显示文件的内容（当然，使用CSS适当样式化）。但是，这不是Web浏览器对标签、属性以及文件内容所做的所有事情，它还创建并记忆了该页面的HTML的一个“模型”。换句话说，Web浏览器记住了HTML标签、它们的属性以及它们在文件中出现的顺序，页面的这些表示叫做文档对象模型（Document Object Model, DOM）。

DOM提供了JavaScript和Web页面上的元素通信所需的信息。DOM还提供了导航页面、改变页面以及在页面上添加HTML所必需的工具。DOM本身不是真正的JavaScript，它是万维网联盟（World Wide Web Consortium, W3C）的一个标准，大多数的浏览器厂商采用这个标准并将其添加到其浏览器中。DOM使得JavaScript能够和页面的HTML通信并修改页面的HTML。

为了理解DOM是如何工作的，首先查看下面这个非常简单的Web页面：

＜！DOCTYPE HTML＞

＜html＞

＜head＞

＜meta charset="UTF-8"＞

＜title＞A web page＜/title＞

＜/head＞

＜body class="home"＞

＜h1 id="header"＞A headline＜/h1＞

＜p＞Some＜strong＞important＜/strong＞text＜/p＞

＜/body＞

＜/html＞

在这里以及所有其他的Web站点上，一些标签还包含其他的标签，例如，＜html＞标签就包含所有其他的标签；还有＜body＞标签也包含其他标签以及出现在浏览器窗口中的内容。可以用一种家族树（如图4-3所示）来表示标签之间的关系。＜html＞标签是树的“根”，就像是页面上所有其他标签的最老的“祖母”，而其他的标签表示家族树的不同“分支”，例如，＜head＞标签和＜body＞标签，每一个都包含各自的一组标签。

图　4-3　HTML页面的基本嵌套结构，其中标签包含其他标签，常常表现为家族树的形式，而包含其他标签的标签叫做祖先，位于其他标签中的标签叫做子孙

除了HTML标签外，Web浏览器还记录了出现在标签中的文本（例如，图4-3中的＜h1＞标签中的“A headline”），以及分配给每个标签的属性（图4-3中应用于＜body＞和＜h1＞标签的class属性）。实际上，DOM把每一个这样的标签（也叫做元素）、属性和文本都当做独立的单元，叫做节点。

JavaScript提供了几种方法来从一个页面中选择元素，以便可以对这些元素做些事情，例如，让它们淡出视线或者在页面中动画地穿过。document.getElementById（）方法允许使用应用于元素的HTML的一个特定ID来选择该元素。因此，如果你有一个＜div＞标签，并且对它应用了ID banner，即＜div id=“banner”＞，那么可以像下面这样选择它：

document.getElementById（'banner'）；

同样，document.getElementsByTagName（）方法选择一个特定标签的每个实例，例如，document.getElementsByTagName（'a'）选择了页面上的所有锚标签（链接）。并且，一些浏览器支持使用特定类选择所有元素的方法，或者支持使用CSS选择器来选择页面元素。

4.6　再谈选择页面元素

前面介绍过了，Web浏览器提供了两种主要的方法来选择Web页面上的元素，即document.getElementById（）和document.getElementsByTagName（）。遗憾的是，这两个方法都没有提供做出更细致的选择所需的控制。例如，如果要选择一个navButton类的所有＜a＞标签，首先需要选择每个标签，然后遍历每个标签并找到有相应的类名的那些标签。

workaround workshop解决方案研究DOM的问题

遗憾的是，主流浏览器还是按照传统用不同的方式来解释DOM。例如，Internet Explorer9之前的版本处理事件的方式和其他浏览器不同；同样的HTML在Firefox和Safari中产生的文本节点可能比Internet Explorer中产生的更多；并且，IE不会总是按照与Firefox、Safari或Opera相同的方式来获取HTML标签属性。此外，不同的浏览器处理HTML中的空白（例如，制表符和空格）的方式不同，在某些情况下，把空白当做额外文本节点一样对待（Firefox和Safari），而在另一些情况下则忽略空白（IE）。而这些只是最常见的Web浏览器处理DOM的一些区别。克服跨浏览器的JavaScript问题，对于JavaScript程序员来说是如此巨大的一项任务，以至于应

该用一整本（很枯燥的）书来介绍这个主题。实际上，很多JavaScript图书花了很多时间给出了让各种浏览器表现自己所需的代码。但是，生命如此短暂，你何不为自己的Web站点构建交互式的用户界面并添加漂亮的效果，而是要操心如何让脚本在Internet Explorer、Firefox、Safari和Opera中同样地工作呢。

此外，对于Web设计师来说，选择页面元素的传统的DOM方法并不是很直接，特别是已经有了一种优秀的方法（即CSS选择器）来选择页面元素的时候。好在jQuery解决了这些问题，并且提供了易于使用的、跨浏览器兼容的方法来选择和操作页面元素。

幸运的是，jQuery提供了一种非常强大的技术用来选择和操作元素的集合，这就是CSS选择器。没错，如果你习惯使用层叠样式表来进行Web页面样式化，应该已经使用jQuery了。CSS选择器只是一条指令，告诉Web浏览器将样式应用于哪个标签。例如，h1是一个基本的元素选择器，它向每个＜h1＞标签应用一个样式，而.copyright是一个类选择器，它只对那些class属性为copyright的标签进行样式化：

＜p class="copyright"＞Copyright，2011＜/p＞

通过jQuery，可以使用叫做jQuery对象的一条特殊命令来选择一个或多个元素。基本的语法如下所示：

$（'selector'）

在创建jQuery对象的时候，可以使用几乎所有的CSS 2.1和很多CSS 3选择器（即便浏览器本身并不理解特定的选择器——例如，IE无法识别某些CSS 3选择器）。例如，在jQuery中，如果想要选择带有一个特定ID banner的标签，可以这样编写：

$（'#banner'）

#banner就是用来样式化ID名为banner的标签的CSS选择器，#部分表示要标识一个ID。当然，一旦选择一个或多个元素，我们希望对它们做些事情，jQuery提供了很多工具来操作元素。例如，假设想要改变一个元素中的HTML，可以这样编写：

$（'#banner'）.html（'＜h1＞JavaScript was here＜/h1＞'）；

从4.7节开始，在本书剩余的篇幅中，我们还将学习使用jQuery操作页面元素的更多内容。但是，首先需要学习更多关于如何使用jQuery选择页面元素的内容。

注意：大多数浏览器的最新版本都包含了通过类名和通过CSS选择器来选择元素的功能。然而，由于世界上使用的各种浏览器之间对这些功能的支持是不同的，jQuery这样解决这一困难可以确保你的选择即便在旧的浏览器版本IE 6中也能工作。

4.6.1　基本选择器

像ID、class和element这样的基本CSS选择器，构成了CSS的核心。它们是使用jQurey来选择范围广泛的元素的好方法。

阅读有关选择器的内容并非理解它们的最佳方法，本书包含一个交互的Web页面，以便可以测试选择器。在本书教程文件的testbed文件夹下，可以找到一个名为selectors.html的文件。在Web浏览器中打开这个文件。可以通过在选择器文本框中输入各种jQuery选择器并单击Apply按钮来测试它们（如图4-4所示）。

注意：参见本书1.3节中的“注意”部分了解如何找到本书的教程文件。

ID选择器

可以使用jQuery和一个CSS ID选择器来选择应用了一个ID的任何页面元素。例如，假设在一个Web页面中有如下的HTML：

＜p id="message"＞Special message＜/p＞

要使用老的DOM方式来选择元素，会这样编写：

var messagePara=document.getElementById（'message'）；

jQuery方法如下所示：

var messagePara=$（'#message'）；

和DOM方法不同，这里没有使用ID名（'message'），必须使用CSS语法来定义ID选择器（'#message'）。换句话说，在ID名的前面包含了井号，就好像为该ID创建了一种CSS样式。

图　4-4　本书的教程文件中提供的selectors.html允许尝试jQuery选择器。只要在Selector表单字段（圈出的）输入一个选择器，然后单击Apply按钮就可以了。这个页面把选择器转换为一个jQuery对象，并且匹配输入的选择器的任何元素都会变为红色。字段的下面是用来选择该项的jQuery代码，以及选择的元素的总数。在这个例子中，p是选择器，并且页面上的所有＜p＞标签（该页面上一共有12个）都突出显示为红色

元素选择器

对于getElementsByTagName（）方法，jQuery也拥有自己的替代。只要把标签的名字传递给jQuery即可。例如，使用旧的DOM方法来选择页面上的每个＜a＞标签，可以这么写：

var linksList=document.getElementsByTagName（'a'）；

使用jQuery，应该这么写：

var linksList=$（'a'）；

提示：jQuery所支持的选择器比这里列出的范围更广。尽管本书列出了很多有用的选择器，但可以在http：//docs.jquery.com/Selectors找到jQuery的选择器的一个完整列表。

类选择器

选择元素的另一种有用方式是通过类名。

例如，假设想要创建一个包含下拉菜单的导航栏，当访问者的鼠标光标移动到一个主导航按钮上时，希望下拉菜单出现。为此，需要使用JavaScript控制这些菜单，并且需要一种方法来编程每个主导航按钮，以便某人把鼠标光标移动到按钮上的时候打开一个下拉菜单。

注意：由于找到具有特定类名的所有元素是如此常见的一项任务，因此一些浏览器（例如，Firefox和Safari的最新版）已经添加了这样的功能。既然并不是所有的浏览器都有一种内置方式来找到一个特定类的元素，像jQuery这样考虑了不同浏览器的一个库就显得很有价值了。

有这样一种技术，把一个类（例如，navButton）添加到每个主导航栏链接中，然后使用JavaScript来查找带有该类名的链接，并且把所有神奇的菜单打开功能应用到这些链接（将在8.5节学习如何做到这一点）。这个方案现在听起来有点容易混淆，但是，现在来说重要的在于：要让这个导航栏工作，需要一种方式来仅仅选择具有一个特定类名的那些链接。

幸运的是，jQuery提供了一种简单的方法来选择具有相同类名的所有元素。只要像下面这样使用一个CSS类选择器即可：

$（'.submenu'）

再次，注意我们只是像这样编写CSS类选择器，在这个类选择器中，类名的前面有一个点号。一旦选择那些标签，就可以使用jQuery操作它们。例如，要隐藏类名为.submen的所有标签，可以这样编写：

$（'.submenu'）.hide（）；

我们将在6.1.1节学习有关jQuery hide（）函数的更多内容，但是现在，这个例子使我们对于jQuery如何工作有了一些概念。

4.6.2　高级选择器

jQuery还允许使用更为复杂的CSS选择器来精确地找到想要选择的标签。现在先不要太急于立即掌握这些，一旦再阅读一些章节并且对jQuery如何工作以及如何使用它操作Web页面有了更好的理解，你还可以回顾本节的内容。

·子孙选择器提供了一种方式来找到一个标签中的另一个标签。例如，假设已经创建了一个无序的链接列表，并且给这个列表的＜ul＞标签添加navBar的一个ID名：＜ul id="navBar"＞。jQuery表达式$（'a'）选择了该页面上的所有＜a＞标签。然而，如果想要只选择无序列表内的链接，可以使用一个子孙选择器，如下所示：

$（'#navBar a'）

快速熟悉理解CSS

层叠样式表在任何对于JavaScript的介绍中都是一个重要话题。要很好地学习本书，我们至少需要有一些Web设计背景知识，并且知道一些CSS知识以及如何使用它。CSS是Web设计师创建美丽的Web站点所需要的最重要的工具，因此，如果你还不太了解它，现在就该学习它。CSS不仅帮助你使用jQuery，而且你会发现可以把JavaScript和CSS结合起来使用，从而很容易地给Web页面添加交互视觉效果。

如果需要一些帮助来快速掌握CSS，有充足的资料供我们学习。

要概览C S S的基础知识，请尝试位于w w w.htmldog.com/guides/的HTML Dog CSS教程。你将会在该站点找到基础的、中级的和高级的教程。

也可以阅读《CSS：The Missing Manual》一书，该书全面地介绍了CSS（包括很多和本书类似的动手实验教程）。

特别是，在使用jQuery的时候，理解CSS选择器很重要，这个教程告诉Web浏览器CSS规则适用于哪个标签。要操作一个选择器，这里介绍的资源是很好的。如果只是想要回顾可用的不同选择器，还有一些地方可以访问：

·http：//css.maxdesign.com.au/selectutorial/

·http：//www.456 bereastreet.com/archive/200601/css_3_selectors_explained/

另外，这个语法只是基本的CSS：一个选择器，后面依次是一个空格和另一个选择器。最后列出的选择器是目标（在这个例子中是a），而其左边的每个选择器表示包含该目标的一个标签。

·孩子选择器找到是另一个标签的孩子的一个标签。孩子标签是另一个标签的直接子孙。例如，在图4-3所示的HTML中，＜h1＞和＜p＞标签是＜body＞标签的孩子，而＜strong＞标签则不是（因为它由＜p＞标签包围着）。这样创建孩子元素：首先列出父亲元素，然后是一个＞，然后是孩子元素。例如，要选择＜body＞标签的孩子＜p＞标签，这样编写：

$（'body＞p'）

·相邻的兄弟选择器使得我们能够选择直接出现在另一个标签之后的标签。例如，假设有一个不可见的面板，当单击一个标签页的时候，面板会显示出来。在HTML中，标签页可能用一个标题标签（例如＜h2＞）表示，而隐藏的面板则是跟在标题后面的一个＜div＞标签。要使得＜div＞标签（面板）可见，需要一种方式来选择它。使用jQuery和一个相邻兄弟选择器可以很容易地做到这一点：

$（'h2+div'）

要创建一个相邻兄弟选择器，只要在两个选择器之间添加一个加号（这两个选择器可以是任何类型，包括ID、类或元素）。右边的选择器是要选择的一个，而只有它直接出现在左边的选择器的后面的时候才是这样。

·属性选择器允许根据一个元素是否拥有一个特定属性来选择它，甚至能够检查以确定该属性是否匹配一个特定值。通过属性选择器，可以找到设置了alt属性的＜img＞标签，甚至可以匹配一个拥有特定的alt文本值的＜img＞标签。或者，可以找到指向站点之外的每个链接标签，并且仅为那些链接添加代码，以使它们都在一个新的窗口中打开。

在要检查属性的元素之后添加属性选择器。例如，要找到设置了alt属性的＜img＞标签，可以这样编写：

$（'img[alt]'）

还有一些其他的不同属性选择器：

·[attribute]选择HTML中那些已经指定了特定属性的元素。例如，$（a[href]）找到设置了一个href属性的所有＜a＞元素。通过属性来选择，这允许排除带有名字的锚点，例如＜a name="placeOnPage"＞＜/a＞，这经常用作页面内的链接。

·[attribute=value]选择特定属性拥有一个具体值的一个元素。例如，找出表单中的所有文本框，可以使用：

$（'input[type="text"]'）

既然大多数表单元素都共享同样的标签＜input＞，因此分辨表单元素的类型的唯一方式就是查看其type属性（选择表单元素是如此常见，以至于jQuery为此包含了专门的选择器，参见9.1.1节）。

·[attribute^=value]匹配一个属性以一个特定值开始的元素。例如，如果要找到指向站点之外的所有链接，可以使用下面的代码：

$（'a[href^="http：//"]'）

注意，不一定要整个属性值匹配，只要开始部分匹配就行了。因此，href^=http：//匹配那些指向http：//www.yahoo.com或http：//www.google.com等的链接。或者，可以使用这个选择器来识别一个mailto：链接，如下所示：

$（'a[href^="mailto："]'）

·[attribute$=value]匹配属性以一个特定值结束的元素，这对于匹配文件扩展名来说很好用。例如，使用这个选择器，可以找到指向PDF文件的链接（可能使用JavaScript来添加一个专门的PDF图标；或者动态地生成到Adobe.com的一个链接，以便访问者可以下载Acrobat Reader程序）。选择指向PDF文件的链接的代码如下所示：

$（'a[href$=".pdf"]'）

·[attribute*=value]匹配属性中的任意位置包含了一个特定值的元素。例如，可以找到指向一个特定域名的任何类型的链接。例如，下面代码展示了如何找到指向missingmanuals.com的链接：

$（'a[href*="missingmanuals.com"]'）

这个选择器提供了很好的灵活性，不仅可以找到指向http：//www.missingmanuals.com的链接，而且也能找到指向http：//missingmanuals.com和http：//www.missingmanuals.com/library.html的链接。

注意：在操作表单的时候，jQuery有一组有用的选择器。它们允许选择文本字段、密码字段和单选按钮这样的元素。我们将在9.1.1节学习它们。

4.6.3　jQuery过滤器

jQuery还提供了一种方法，可以根据特定的特征过滤选择。例如，：even过滤器允许选择一个元素集合中的所有even元素。此外，还可以找到包含特定标签、特定文本的元素，找到隐藏不可见的元素甚至是不匹配某个特定选择器的元素。要使用过滤器，在主选择器后面添加一个冒号，然后是过滤器的名字。例如，要找到表中的每一个偶数行，可以像下面这样编写jQuery选择器：

$（'tr：even'）

这行代码选取了每个偶数＜tr＞标签。要缩小选择范围，我们可能想要找出表中拥有类名striped的每个偶数行。可以像下面这样做：

$（'.striped tr：even'）

：even还可以和其他的过滤器一起使用：

·：even和：odd在一组中分别选择偶数索引和奇数索引对应的元素。这些过滤器的作用有些不符合常规，但要记住一个jQuery选择是匹配特定选择器的所有元素的一个列表。在某种意义上，它们就像是数组一样的类型（参见本书2.8节）。一个jQuery选项中的每个元素都有一个索引编号，记住数组的索引值总是从0开始的（参见2.8.2节）。因此，既然：even作用于每个索引值上（例如，0、2和4），这个过滤器实际返回的是选择中的第1项、第3项和第5项（以此类推），换句话说，它真正选择的是每个奇数元素。：odd的工作方式相同，只不过它所选择的都是奇数索引编号（1、3、5等）。

·：first和：last选择一组中的第一个元素和最后一个元素。例如，如果想要选择一个页面的第一段，可以输入如下代码：

$（'p：first'）；

要选择页面的最后一段，可以输入如下代码：

$（'p：last'）；

·可以使用：not（）找出不匹配一个特定选择器类型的元素。例如，假设要选择每个＜a＞标签，但除了那些拥有一个navButton类的标签之外。可以像下面这样做：

$（'a：not（.navButton）'）；

把要忽略的选择器的名字传递给：not（）函数。在这个例子中，.navButton是一个类选择器，因此，这段代码翻译为“不带有.navButton类”。可以把任何的jQuery过滤器以及大多数的jQuery选择器和：not（）一起使用，例如，要找到不是以http：//开头的每个链接，可以这样编写：

$（'a：not（[href^="http：//"]）'）

·：has（）找到包含了其他选择器的元素。例如，假设想要找到所有的＜li＞标签，但是只有其中拥有一个＜a＞的时候才选择它们。可以这么做：

$（'li：has（a）'）

这一设置和一个子孙选择器不同，因为它不是选择＜a＞；它在选择＜li＞标签，但是只有＜li＞标签中有一个链接的时候才选择。

·：contains（）找出包含特定文本的元素。例如，要找到显示“Click Me！”的每个链接，可以创建如下所示的一个jQuery对象：

$（'a：contains（Click Me！）'）

·：hidden找到隐藏的元素，包括CSS的display属性设置为none（意味着不会在页面上看到它）的元素，使用jQuery的hide（）函数隐藏的元素（参见6.1.1节），或者隐藏的表单字段（这个选择器不适用于那些CSS visibility属性设置为invisible的元素）。例如，假设已经隐藏了几个＜div＞标签，可以找到它们，然后使用jQuery使其可见，如下所示：

$（'div：hidden'）.show（）；

这行代码对于页面上已经可见的＜div＞标签没有什么影响（我们将在6.1.1节学习jQuery的show（）函数）。

·：visible是和：hidden相对应的属性。它找到页面上的可见元素。

4.6.4　理解jQuery选择

当使用jQuery对象选择一个或多个元素的时候，例如，$（'#navBar a'），最终不会像使用getElementById（）或getElementsByTagName（）方法那样，得到一个传统的DOM节点列表。相反，得到了一个特殊的jQuery，只是元素的集合。这些元素不会理解传统的DOM方法，因此，如果你在其他图书中学习过DOM，会发现DOM不能和jQuery对象一起工作。这似乎是一个重要的缺陷，但是，几乎一个一般DOM节点的所有属性和方法都有jQuery的对应物，因此，我们可以做到传统DOM所能做的所有事情，而且通常会更快并且使用更少的代码。

然而，DOM和jQuery选择的工作方式在概念上有两个重要的不同。jQuery的目标在于使得编写JavaScript程序更容易和更快。这个库的目标之一是，使得我们用尽可能少的数行代码做很多事情。为了做到这一点，jQuery使用两个与众不同的原则。

自动循环

通常，当使用DOM并且选择一堆节点的时候，需要创建一个循环（见3.3节）来遍历选择的每个节点，并且对该节点做些事情。例如，想要选择页面中的所有图像然后隐藏它们，如果想要创建一个JavaScript驱动的幻灯片放映效果，可能要这么做：首先必须选择这些图像然后创建一个循环来遍历图像的列表。

由于遍历元素的一个集合如此常用，以至于jQuery函数中直接构建了这一功能。换句话说，当对元素的选择集合应用一个jQurey函数的时候，不需要自己创建循环，因为函数会自动进行循环。

例如，要选择一个＜div＞标签中拥有一个slideshow ID的所有图像，然后将这些图像隐藏，可以用jQuery这么编写：

$（'#slideshow img'）.hide（）；

$（#slideshow img'）所创建的元素的列表可能包含50幅图像。hide（）函数自动遍历列表，隐藏每个单幅图像。这一设置如此方便（设想一下你不必再自己编写循环的数目），这么好的功能却不是DOM的一部分，真是令人惊讶。

串联函数

有时候，我们希望对一个元素选择集合执行几种操作。例如，假设想要使用JavaScript来设置一个＜div＞标签（拥有一个ID为popUp）的宽度和高度。通常，必须至少编写两行代码。但是jQuery使得我们可以用一行代码做到这点：

$（'#popUp'）.width（300）.height（300）；

jQuery使用一个独特的原则叫做串联，它允许一个接一个地添加函数。每个函数用一个点号和下一个函数连接起来，并且和前一个函数一样在同一个jQuery元素结合执行。因此，上面这段代码修改了ID为popUp的元素的宽度，然后修改了该元素的高度。串联jQuery函数使得我们能够简练地执行多个操作。例如，假设不仅要设置＜div＞标签的宽度和高度，而且要向＜div＞中添加文本并使其淡出视线（假设当前在页面上是可见的）。可以像下面这样简洁地编写：

$（'#popUp'）.width（300）.height（300）.text（'Hi！'）.fadeIn（1000）；

这段代码对于ID名为popUp的标签使用了4个jQuery函数（width（）、height（）、text（）和fadeIn（））。

提示：一行长长的串联jQuery函数很难阅读，因此，一些程序员将其分为数行，如下所示：

$（'#popUp'）.width（300）

.height（300）

.text（'Message'）

.fadeIn（1000）；

只要在串联的最后一行之后添加一个分号，JavaScript解释器就把这一行当做单独的一句对待。

串联函数的功能确实很少见，并且这是jQuery特有的功能。换句话说，不能在串联中添加非jQuery函数（不管是自己创建的函数还是内置的JavaScript函数）。

4.7　向页面添加内容

jQuery提供了很多函数来操作页面的元素和内容：从简单地替换HTML到精确地找到和选择的元素相关的新的HTML，再到完全从页面删除标签和内容。

注意：位于testbed教程目录下的示例文件content_functions.html允许测验每个jQuery函数。只要在Web浏览器中打开该文件，在文本框中输入一些文本，然后单击任何一个“Run It”，就可以看到每个函数是如何工作的。

为了研究这些函数后面的例子，假设有一个页面，具有如下的HTML：

＜div id="container"＞

＜div id="errors"＞

＜h2＞Errors：＜/h2＞

＜/div＞

＜/div＞

·. html（）和DOM的innerHTML属性作用相同。它读取一个元素中的当前HTML，并且使用其他的一些HTML来替换当前内容。把html（）函数和一个jQurey选择一起使用。

要获取选择中的当前HTML，只要在jQurey选择的后面添加.html（）。例如，可以使用本节开始的HTML代码段来运行如下命令：

alert（$（'#errors'）.html（））；

这行代码创建了一个包含文本“＜h2＞Errors：＜/h2＞”的警告框。当以这种方式使用html（）的时候，可以复制一个特定元素中的HTML并将其粘贴到页面的另一个元素中。

如果为.html（）提供一个字符串作为参数，就替换了选择中的当前内容：

$（'#errors'）.html（'＜p＞There are four errors in this form＜/p＞'）；

这行代码替换了拥有名为errors的ID的一个元素中的所有HTML。它将会把示例HTML代码段替换为：

＜div id="container"＞

＜div id="errors"＞

＜p＞There are four errors in this form＜/p＞

＜/div＞

＜/div＞

注意，它替换了已有的＜h2＞标签。可以使用下面列出的其他函数来避免替换HTML。

·. text（）的功能和.html（）一样，但是它不接收HTML标签。当你想要用一个标签替换文本的时候，它很有用。例如，在本节开始的代码中，你将会看到其中带有文本“Errors：”的一个＜h2＞标签。假设在运行了一个程序来查看表单中是否有任何错误之后，你想要使用“No errors found”替换文本，你可以使用如下代码：

$（'#errors h2'）. text（'No errors found'）；

＜h2＞标签仍然在那里，只是其中的文本改变了。对于传递给text（）函数的任何HTML标签，jQuery对其编码，因此，＜p＞转换为＆lt；p＆gt；。如果你想要在页面上真正显示方括号和标签名称，这很方便。例如，可以使用它来显示示例的HTML代码以供其他人查看。

·append（）把HTML作为选择元素的最后一个孩子元素添加。例如，假设选择一个＜div＞标签，只想在结束＜/div＞标签之前添加一些HTML，而不是要替换＜div＞的内容。.append（）函数是一种很好的方法，可以在符号表项（＜ul＞）或编号列表（＜ol＞）的末尾添加一项。例如，假设使用本节开始的HTML代码，在一个页面上运行如下代码：

$（'#errors'）.append（'＜p＞There are four errors in this form＜/p＞'）；

这个函数运行之后，得到的HTML如下所示：

＜div id="container"＞

＜div id="errors"＞

＜h2＞Errors：＜/h2＞

＜p＞There are four errors in this form＜/p＞

＜/div＞

＜/div＞

注意，＜div＞中最初的HTML保持不变，新的HTML只是添加在其后。

·prepend（）和append（）类似，但是，它在选择的开始标签之后直接添加HTML。

例如，假设在和前面列出的同样的HTML上运行如下代码：

$（'#errors'）.prepend（'＜p＞There are four errors in this form＜/p＞'）；

在这个prepend（）函数之后，得到了如下所示的HTML：

＜div id="container"＞

＜div id="errors"＞

＜p＞There are four errors in this form＜/p＞

＜h2＞Errors：＜/h2＞

＜/div＞

＜/div＞

现在，新添加的内容直接出现在＜div＞的开始标签之后。

·如果想要在选择之外添加HTML，要么在选择的元素的开始标签之前，或者紧接在元素的结束标签之后，可以使用.before（）或.after（）函数。例如，当访问者提交表单的时候，检查表单中的一个文本字段以确保该字段不为空，这是常见的任务。假设在表单提交之前，字段的HTML如下所示：

＜input type="text"name="userName"id="userName"＞

现在假设当访问者提交表单的时候该字段为空。可以编写程序来检查该字段，然后在该字段后添加一条错误消息。要在这个字段之后添加消息（现在不要担心如何实际地检查表单字段的内容是否正确，我们将在9.4节学习这点），可以使用.after（）函数，如下所示：

$（'#userName'）.after（'＜span class="error"＞User name required＜/span＞'）；

这行代码使得Web页面显示错误消息，并且HTML部分将如下所示：

＜input type="text"name="userName"id="userName"＞

＜span class="error"＞User name required＜/span＞

·. before（）函数直接把新的内容放在选择的元素之前。

注意：本节所介绍的函数html（）、text（）等，都是在页面上添加或替换内容最常用的方式，但是，它们并非唯一的方法。可以在http：//api.jquery.com/category/manipulation/找到更多的函数。

替换和删除选择

有时候，我们可能想要完全替换或删除选择的元素。例如，假设使用JavaScript创建了一个弹出对话框（不是老式的alert（）方法，而是看上去更为专业的一个对话框，而实际上只是页面顶端浮动的一个绝对定位的＜div＞）。当访问者单击对话框上的“Close”按钮，当然要从页面中删除对话框。要做到这一点，可以使用jQuery remove（）函数。假设弹出对话框的ID为popup，可以使用如下代码来删除它：

$（'#popup'）.remove（）；

.remove（）函数不仅限于对单个元素使用。假设想要删除所有使用一个error类的＜span＞标签，可以这么做：

$（'span.error'）.remove（）；

也可以使用新内容完全替换一个选择。例如，假设有一个页面，其中具有公司销售的产品的照片。当访问者单击一个产品的图片，就将其添加到购物车中。当图片被单击的时候，我们可能想要用一些文本替换＜img＞标签（例如，“Added to cart”）。我们将在第5章学习如何让特定元素对事件（例如，图像被单击）做出回应，但是现在，只要假设想要替换ID为product101的＜img＞标记的文本。下面使用jQuery做到这一点：

$（'#product101'）.replaceWith（＜p＞Added to cart＜/p＞'）；

这段代码从页面删除了＜img＞标签并且用一个＜p＞标签替代它。

注意：jQuery还包含了一个名为clone（）的函数，它允许我们复制一个选择的元素。我们将在4.11节中看到这个函数的用法。

4.8　设置和读取标签属性

添加、删除和改变元素并不是jQuery唯一擅长的事情，并且，这也不是我们希望对元素的选择所做的唯一事情。我们常常想要改变一个元素的属性值，例如，给标签添加一个类，或者改变一个元素的CSS属性。也可以获取属性的值，例如，一个特定链接到底指向哪个URL。

4.8.1　类

层叠样式表是非常强大的技术，使得我们能够把各种高级的可视化格式添加到HTML中。一条CSS规则可以为页面添加带颜色的背景，而另一条规则可以完全把元素隐藏起来。可以直接使用JavaScript来删除、添加或者改变用于元素的类，从而创建一些真正高级的视觉效果。由于Web浏览器非常快速而高效地处理和实现CSS指令，因此只要直接为标签添加一个类，就可完全改变标签的外观，甚至让它从页面上消失。

有用工具提示View Source概览

通过添加、修改、删除和重新组合HTML代码来使用JavaScript操作DOM的一个问题是，当JavaScript完成的时候，很难识别页面的HTML是什么样子。例如，每个浏览器中的View Source命令只有在Web页面文件从Web服务器下载之后才显示。换句话说，我们看到HTML是在JavaScript修改它之前，使得很难搞清楚编写的JavaScript是否真的产生了想要的HTML。例如，如果在JavaScript向表单页面添加10条错误消息之后，或者在JavaScript程序创建了一个带有文本或表单字段的、精致的弹出式对话框之后，我们能够看到页面的HTML是什么样子，那么，要判断最终是否得到想要的HTML就容易很多了。幸运的是，大多数浏览器提供了一组开发者工具，允许你查看HTML的渲染，也就是在JavaScript完成其“魔术”之后浏览器所显示的HTML。

通常，这些工具在浏览器窗口的底部作为面板出现，位于Web页面之下。不同的标签允许你访问JavaScript代码、HTML、CSS和其他有用的资源。工具面板上所打开的标签和方法的实际名称因浏览器的不同而有所不同。

·在Fi re fox中可以安装Fi re fo x插件（参见1 5.2节）。打开想要查看的一个带有JavaScript代码的页面，并且打开Firebug（Tools→Firebug→Open Firebug）。在Firebug面板中单击HTML标签，将会看到完整的DOM（包括JavaScript所生成的任何HTML）。或者可以在Firefox中使用Web Developer工具栏（https：//addons.mozilla.org/en-US/firefox/addon/web-developer/）来查看常规的HTML源代码和生成的HTML。

·在IE 9中，按下F12键可以打开DeveloperTools面板，然后，单击HTML标签来查看页面的HTML。在IE 9中，HTML标签首先展示下载的HTML（这和View Source命令相同）。但是，如果单击刷新图标（或者按下F5键），HTML标签将显示任何JavaScript产生的改变完成之后所渲染的HTML。

·在Chrome中，选择View→Developer→Dev-eloper Tools并单击浏览器窗口的底部的面板中的Elements标签。

·在Safari中，确保Developer菜单是打开的（选择Safari→Preferences，单击Advanced按钮，并且确保选中“Show Develop menu in menu bar”）。然后，打开你感兴趣查看的页面，并且选择Develop→Show Web Inspector。在位于浏览器窗口底部的面板中单击Elements标签。

·在Opera，选择Tools→Advanced→OperaDragonfly（Dragonfly是Opera内置的开发工具集的名称）。在位于浏览器窗口底部的面板中单击Documents标签。

jQuery提供了几个函数来操作一个标签的类属性：

·addClass（）为元素添加一个特定的类。在jQuery选择的后面添加addClass（），并且把一个字符串传递给函数，字符串表示要添加的类的名字。例如，要给指向站点之外的所有链接添加externalLink类，可以使用如下代码：

$（'a[href^="http：//"]'）.addClass（'externalLink'）；

这段代码将会处理如下所示的HTML：

＜a href="http：//www.oreilly.com/"＞

并且将其改变为如下所示：

＜a href="http：//www.oreilly.com/"class="externalLink"＞

为了让这个函数有用，需要提前创建CSS类，并且将其添加到页面的样式表中。然后，当JavaScript添加类名的时候，Web浏览器可以根据之前定义的CSS规则来应用这个样式属性。

注意：当使用addClass（）和removeClass（）函数的时候，只需要提供类名，而省略在创建类选择器时通常会使用的点号。例如，addClass（'externalLink'）是正确的，而addClass（.'externalLink'）是错误的。

当已经有类应用于标签的时候，jQuery函数还考虑到了可能引发的问题，addClass（）函数不会删除已经应用于标签的旧的类，这个函数也只是添加新的类。

注意：为单个标记添加多个类名确实是有效的，并且往往很有用。浏览http：//webdesign.about.com/od/css/qt/tipcssmulticlas.htm可以了解关于这一技术的更多信息。

·removeClass（）是addClass（）相对应的函数。它从选择的元素上删除特定的类。例如，如果想要从ID为alertBox的＜div＞中删除名为highlight的类，这么做：

$（'#alertBox'）.removeClass（'highlight'）；

·最后，可能需要切换一个特定的类，即如果它还不存在就添加它，而如果它已经存在就删除它。表示元素处于打开或关闭状态的时候，切换是一种常用的方式。例如，当单击一个单选按钮，它就选中（打开）；再次单击它，选中标记就消失了（关闭）。

假设在Web页面上有一个按钮，当按下的时候会改变＜body＞标签的类。通过这么做，可以使用子孙选择器来构造另一组样式，从而为页面添加一次彻底的样式变化。当再次按下按钮，我们希望从＜body＞标签中删除该类，页面返回到其最初的外观。对于这个例子，假设访问者单击来改变页面样式的按钮有一个ID为changeStyle，并且我们想要在每次单击按钮的时候切换类名altStyle。下面是实现代码：

$（'#changeStyle'）.click（function（）{

$（'body'）.toggleClass（'altStyle'）；

}）；

此时，不要关心上面代码的第一行和第三行，这些和事件有关，事件使得脚本对页面上发生的操作做出响应（例如，单击按钮）。粗体代码展示了toggleClass（）函数，它在每次单击按钮的时候添加或删除类altStyle。

4.8.2　读取和改变CSS属性

jQuery的css（）函数也允许直接修改元素的CSS属性，而不是直接把类样式应用于元素，可以直接添加border或background-color，或者设置positioning属性。可以以三种方式使用css（）函数：找到元素的CSS属性的当前值、在元素上设置CSS属性或者一次设置多个CSS属性。

要确定一个CSS属性的当前值，可以把属性的名字传递给css（）函数。例如，假设想要找到ID为main的＜div＞标签的背景颜色：

var bgColor=$（'#main'）. css（'background-color'）；

这段代码运行之后，变量bgColor将包含一个字符串，其中带有该元素的背景颜色值。

注意：jQuery不能总是按照期望的方式返回CSS值。在颜色方面（例如，CSS背景颜色或颜色属性），jQuery总是返回类似rgb（255，0，10）的RGB值；又或者如果颜色中有任何透明度，那么会返回诸如rgba（255，10，10，.5）的RGBA颜色值。不管样式表中的颜色是否是使用十六进制表示法（#F4477A）定义的，jQuery总是返回RGB值，RGB使用百分比（rgb（100%，10%，0%）或者HSL（hsl（72，100%，50%））形式。此外，jQuery不理解像font、margin、padding或border这样缩写的CSS属性，相反，必须使用特定的CSS属性，例如font-face、margin-top、padding-bottom或border-bottom-width，来访问那些可以和CSS缩写结合的样式。此外，jQuery把所有的单位值都转换为像素，因此，即便使用CSS把＜body＞标签的font-size设置为150%，在查看font-size属性的时候，jQuery还是返回一个像素值。

css（）函数也允许为元素设置CSS属性。要按照这种方式使用该函数，可以为函数提供两个参数：CSS属性名和一个值。例如，要把＜body＞标签的字体扩大两倍，可以这么做：

$（'body'）.css（'font-size'，’200%'）；

提供的第二个参数可能是一个字符串值，例如“200%”，或者是一个数值，jQuery会将其转换为像素。例如，要把带有类.pullquote的所有标签中的内边距修改为100像素，可以编写如下代码：

$（'.pullquote'）.css（'padding'，100）；

在这个例子中，jQuery把padding属性设置为100像素。

注意：当使用jQuery的.css（）函数来设置CSS属性的时候，可以使用CSS缩写方法。例如，下面展示了如何围绕所有带有highligh类的段落，添加一个像素宽度的黑色边框：

$（'p.highlight'）.css（'border'，’1px solid black'）；

根据一个CSS属性的当前值改变其属性，常常是很有用的。例如，假设想要在Web页面上添加一个“Make text bigger”按钮，以便当访问者单击该按钮的时候，页面的font-size增大一倍。要做到这点，读取该值，然后设置一个新值。在这个例子中，首先确定当前的font-size，然后用该值的两倍来设置font-size。这比想象的需要一些技巧。下面是代码，随后是完整的说明：

var baseFont=$（'body'）.css（'font-size'）；

baseFont=parseInt（baseFont，10）；

$（'body'）.css（'font-size'，baseFont*2）；

第一行代码获取＜body＞标签的font-size值，返回值是以像素为单位，并且是一个类似“16px”的字符串。因为想要把这个值增加一倍，所以给它乘以2，必须通过删除字符串的“px”部分将其转换为数字。第二行代码使用14.3.1节介绍的JavaScript parseInt（）方法来实现这一点。该函数只是删除跟在数字后面的任何部分，因此，在第二行之后，baseFont包含一个数值，例如16。最后，第三行代码通过把baseFont的值乘以2重新设置font-size属性。

注意：只有在页面上的其他标签（段落、标题等）使用像em或百分比这样的相对值来设置它们的font-size的时候，这段代码才会影响到页面的字体大小。如果其他的标签使用像素这样的绝对值，改变＜body＞标签的字体大小不会影响到它们。

4.8.3　一次改变多个CSS属性

如果我们想要在一个元素上设置多个CSS属性，不需要多次使用.css（）函数。例如，如果想要动态地突出显示＜div＞标签（可能是作为对访问者行为的一种响应），可以改变＜div＞标签的背景颜色并添加一个围绕它的边框，如下所示：

$（'#highlightedDiv'）.css（'background-color'，’#FF0000'）；

$（'#highlightedDiv'）.css（'border'，’2px solid#FE0037'）；

另一种方式是把所谓的一个对象直接量传递给.css（）函数。把对象直接量看做属性名/值对的一个列表。在每个属性名的后面插入一个冒号（：），后面跟着一个值。每个名/值对都用一个逗号隔开，并且整个列表用花括号{}括住。这样，上面的两个CSS属性值的一个对象直接量如下所示：

{'background-color'：’#FF0000’，'border'：’2px solid#FE0037’}

由于挤压到单独一行的对象直接量很难阅读，因此，很多程序员将其分散到多行。如下代码和前面一行代码的作用相同：

{

'background-color'：’#FF0000’，

'border'：’2px solid#FE0037’

}

对象直接量的基本结构如图4-5所示。

图　4-5　JavaScript对象直接量提供了创建属性和值的一个列表的一种方法。JavaScript把对象直接量当做一块单独的信息对待，就像数组一样也是值的一个列表。在为jQuery插件设置选项的时候，将经常使用这样的对象直接量

警告：在创建对象直接量的时候，确保通过在值的后面添加一个逗号来分隔开每个名/值对（例如，在这个例子中，逗号跟在值’#FF0000’的后面）。然而，最后一个属性/值对之后不应该再有逗号了，因为其后面不会再有属性/值对。如果确实在最后一个值的后面添加了一个逗号，一些Web浏览器（包括Internet Explorer）会产生一个错误。

要把对象直接量和css（）函数一起使用，只要像下面这样把对象传递给函数：

$（'#highlightedDiv'）.css（{

'background-color'：’#FF0000’，

'border'：’2px solid#FE0037’

}）；

仔细地研究这个例子，因为它看上去和我们目前为止已经见到的例子有所不同，并且我们将会在后面的各章中遇到很多这样的代码。首先要注意的是，这段代码只是一条JavaScript语句（实际上只是一行代码），我们可以分辨这一点，因为结束语句的分号直到最后一行才出现。这条语句划分为4行是为了使代码便于阅读。接下来，注意对象直接量是传递给css（）函数的一个参数（就像一段数据）。因此，在代码ss（{中，左圆括号是函数的一部分，而左{表示对象的开始。最后一行的3个字符可以这样分解：}是对象的结束，也是传递给函数的参数的结束；}表示函数的结束，是css（）中最后的圆括号；而；表示这条JavaScript语句的结束。

如果所有这些对象直接量的内容令你不知所措，可以把CSS属性改为每次一行，如下所示：

$（'#highlightedDiv'）.css（'background-color'，’#FF0000'）；

$（'#highlightedDiv'）.css（'border'，’2px solid#FE0037'）；

更好的方法是使用jQuery的内置串联功能（参见4.6.4节）。串联是通过把一个函数添加到另一个函数的末尾，从而把几个jQuery函数应用于元素的一个集合，如下所示：

$（'#highlightedDiv'）.css（'background-color'，’#FF0000'）

.css（'border'，’2px solid#FE0037'）；

这段代码可以解释为：找到ID为highlightedDiv的元素并修改其背景颜色，然后，修改其边框颜色。上面的代码进行了两次选择（$（'#highlightedDiv'）），而相比之下，串联的性能要更好一些，因为每次进行选择的时候，都要让Web浏览器运行所有的jQuery代码来选择该元素。因此，这段代码不是最优的。

$（'#highlightedDiv'）.css（'background-color'，’#FF0000'）；

$（'#highlightedDiv'）.css（'border'，’2px solid#FE0037'）；

这段代码迫使浏览器选择元素，更改其CSS，再次选择元素（浪费了处理器时间），并且再次应用CSS。而使用串联方法，浏览器只需要选择元素一次，然后运行CSS函数两次；选择该元素一次，这要更快并且更高效。

4.9　读取、设置和删除HTML属性

既然使用JavaScript改变类和CSS属性是如此常见的任务，jQuery已经为此内置了函数。但是addClass（）和css（）函数只是改变HTML类和样式属性的快捷方式。jQuery包含了用来操作HTML属性的通用函数，即attr（）和removeAttr（）函数。

attr（）函数允许读取一个标签的指定HTML属性。例如，要确定一个特定＜img＞指向的当前图形文件，把字符串'src'（表示＜img＞标签的src属性）传递给函数：

var imageFile=$（'#banner img'）.attr（'src'）；

attr（）函数返回在HTML中设置的属性值。这段代码返回另一个标签中ID为banner的第一个＜img＞标签的src属性，因此变量imageFile将包含在页面的HTML中设置的路径：例如，'images/banner.png'或'http：//www.thesite.com/images/banner.png'。

注意：当把一个属性名传递给.attr（）函数的时候，不需要关心属性名的大小写，href、HREF或者甚至HrEf都有效。

如果给attr（）函数传递第二个参数，可以设置标签的属性。例如，要交换为不同的图像，可以像下面这样改变一个＜img＞标签的src属性：

$（'#banner img'）.attr（'src'，'images/newImage.png'）；

如果想要从标签完全删除一个属性，可以使用removeAttr（）函数。例如，这段代码从＜body＞标签删除bgColor属性：

$（'body'）.removeAttr（'bgColor'）；

4.10　操作选择中的每个元素

正如本书4.6.4节所介绍的，jQuer y的独特功能之一是，它的大多数函数都自动遍历jQuery选择中的每一项。例如，要让页面上的每个＜img＞淡出，只需要一行JavaScript代码：

$（'img'）.fadeOut（）；

.fadeOut（）函数导致一个元素慢慢消失，并且当把它附加到包含多个元素的一个jQuery选择时，这个函数遍历选择并且淡出每个元素。我们经常想要遍历元素的选择并在每个元素上执行一系列操作。jQuery提供的.each（）函数正是用于此目的。例如，假设要在页面底部的一个参考资料框（这个框可能叫做“O ther Sites Mentioned in This Article”）中列出页面中的所有外部链接（没错，你可能曾经想过要这么做，但一直焦急地等着学习如何做）。不管怎样，可以通过以下步骤创建该框：

1.获取指向站点之外的所有链接。

2.获取每个链接（URL）的HREF属性。

3.把指向其他链接的URL添加到列表中，放入参考资料框。

jQuery没有内置的函数来执行这些额外的步骤，但是可以使用each（）函数来自行完成。它只是一个jQurey函数，因此，可以在jQurey元素选择的末尾添加它，如下所示：

$（'selector'）.each（）；

4.10.1　匿名函数

要使用each（）函数，可以把一种特殊类型的参数传递给它，即一个匿名函数。匿名函数是包含了想要在每个选择元素上执行的步骤的函数。它之所以叫做匿名，是因为和3.4节所介绍的函数不同，没有给它一个名字。下面是匿名函数的基本结构：

function（）{

//code goes here

}

由于没有名字，因此没有办法调用这个函数。例如，对于正常命名的函数，将其名称与一对圆括号一起使用，即calculateSalesTax（）；。相反，把匿名函数作为一个参数传递给其他函数（奇怪，有点令人混淆，但事实如此）。如下所示，把一个匿名函数作为each（）函数的一部分：

$（'selector'）.each（function（）{

//code goes in here

}）；

图4-6展示了这一结构的不同部分。最后一行代码特别容易令人混淆，因为它包括3个不同的符号来结束整个结构的3个部分。}表示函数的结束（这也是传递给each（）函数的参数的结束）；）是each（）函数的最后部分；而；表示一条JavaScript语句的结束。换句话说，JavaScript解释器把所有这些代码看做一条单独的指令。

图　4-6　jQuery的each（）函数允许遍历页面元素的一个选择并且在每个元素上执行一系列的任务。使用该函数的关键在于理解匿名函数

既然外围的结构设置好了，就该在其中放入匿名函数了，而这是我们希望对于选择中的每个元素发生的所有事情。each（）函数就像一个循环一样运行，这意味着匿名函数内部的指令对于获取的每个元素都将运行一次。例如，假设在页面上有50幅图像并且把如下JavaScript代码添加到页面的其中一个脚本中：

$（'img'）.each（function（）{

alert（'I found an image'）；

}）；

将会出现50个带有消息“I found an image”的对话框（这真是烦人，因此不要尝试这么做）。

注意：这看上去可能有些熟悉。正如你在4.3节所见到的，当把jQuery添加到页面中，应该使用document.ready（）函数来确保在浏览器执行任何JavaScript之前，页面的HTML已经载入了。这个函数还接收一个匿名函数作为参数：

$（document）.ready（function（）{

//programming goes inside this

//anonymous function

}）；

4.10.2　this和$（this）

在使用each（）函数的时候，你当然想要访问或设置每个元素的属性，例如，找到每个外部链接的URL。要在每次循环中访问当前元素，使用一个特殊的关键字this。this关键字引用正在调用匿名函数的元素。因此，首次迭代循环的时候，this引用jQurey选择中的第一个元素，而第二次迭代循环的时候，this引用第二个元素。

jQuery按照这种方式工作，this引用一个传统的DOM元素，因此我们可以访问传统的DOM属性。但是，正如你在本章所了解到的，特殊的jQurey选择允许你把所有精彩内容放入jQurey函数。因此，要将其转换为jQurey的对应物，需要按照$（this）编写。

此时，你可能会认为关于this的所有内容有点黑色幽默，故意使你头疼。这不是幽默，但确实有点令人混淆。为了帮助你搞清楚如何使用$（this），再看一眼本节开始所描述的任务：在页面底部的参考资料框中创建外部链接的一个列表。

假设页面的HTML已经准备好一个＜div＞标签用于外部链接，例如：

＜div id="bibliography"＞

＜h3＞web pages referenced in this article＜/h3＞

＜ul id="bibList"＞

＜/ul＞

＜/div＞

第一步是获取指向站点外部的所有链接的一个列表。可以使用一个属性选择器来做到这一点（参见4.6.2节）：

$（'a[href^="http：//"]'）

现在，遍历每个链接，添加each（）函数：

$（'a[href^="http：//"]'）.each（）

然后添加匿名函数：

$（'a[href^="http：//"]'）.each（function（）{

}）；

匿名函数的第一步是获取链接的URL。既然每个链接都有一个不同的URL，因此必须访问每次迭代时的当前元素。$（this）关键字让我们做到这一点：

$（'a[href^=http：//]'）.each（function（）{

var extLink=$（this）.attr（'href'）；

}）；

中间的粗体代码做几件事情：首先，它创建新的变量（extLink），然后将当前元素的href属性值存储到该变量中。每次迭代，$（this）引用页面上的一个不同链接，因此，每次迭代的时候，extLink变量都会变化。

在这之后，只需要把新的列表项添加到＜ul＞标签（参见4.10.2节开篇给出的HTML）中，如下所示：

$（'a[href^=http：//]'）.each（function（）{

var extLink=$（this）.attr（'href'）；

$（'#bibList'）.append（'＜li＞’+extLink+’＜/li＞'）；

}）；

几乎每次使用each（）函数的时候都要使用$（this）关键字，因此，$（this）迟早会变成你的第二个特性。为了帮助你熟悉这个概念，我们将尝试另一个教程。

注意：本节用到的示例脚本是说明$（this）关键字的用法的一种好方法，但是，它可能不是完成把外部链接的列表写入页面这一任务的最佳方法。首先，如果没有链接，＜div＞标签（它直接编码到页面的HTML中）仍然出现，但它是空的。此外，如果某人访问页面而没有支持JavaScript，将不会看到链接，而只会看到一个空框。更好的方式是也使用JavaScript来创建结束的＜div＞标签。我们将会在本章教程配套的bibliography.html文件中看到这样的一个例子。

4.11　自动提取引用

在本章的最后一个教程中，将创建一段脚本以方便向页面添加提取引用（如图4-7所示）。提取引用是包含了对页面的主文本的有趣引用的一个框。新闻、杂志和W e b站点都使用这些框来吸引读者的注意并且强调要点或有趣的内容。但是，手动添加提取引用需要从页面复制文本并将其放置在一个＜div＞标签、＜span＞标签或其他一些容器中。创建所有这些HTML很费时间，并且还会给最终页面增加额外的HTML和文本副本。好在，可以使用JavaScript快速地向页面添加任何数量的提取引用，而只需要增加少量的H T M L。

4.11.1　概览

要创建的脚本将做几件事情：

1.找到包含特殊的类名pq（pull quote，表示提取引用）的每个＜span＞标签。对于页面的HTML必须做的唯一工作，就是在想要放入提取引用中的任何文本周围添加＜span＞标签。例如，假设页面上有一段文本并且我们想要在提取引用框中强调来自该段落的几句话。只要把强调的文本包含到＜span＞标签中，代码如下所示：

＜span class="pq"＞……and that's how I discovered the Loch Ness monster.＜/span＞

图　4-7　手动把提取引用添加到一个页面的HTML中，有点麻烦。特别是当可以使用JavaScript来自动完成这一过程的时候

2.复制每个＜span＞标签。

每个提取引用框实际上只是其中具有不同文本的另一个＜span＞标签，因此，可以通过复制当前的＜span＞标签来使用JavaScript。

3.从复制的＜span＞标签删除pq类，并且添加新的pullquote。

格式化的魔术，包括组成每个提取引用框的框、较大文本、边框和背景颜色，这些都不是JavaScript来完成的。页面的样式表包含一个CSS类选择器：.pullquote，它完成所有这些内容。因此，直接使用JavaScript来改变复制标签的类名，就完全改变新的＜span＞标签的外观。

4.向页面添加复制的＜span＞标签。

最后可以向页面添加复制的＜span＞标签（步骤2只是在Web浏览器的内存中复制了标签，但还没有真正把标签添加到页面中。这给了我们一个机会，可以在把复制的标签显示给页面浏览者之前进一步操作它）。

4.11.2　编程

现在对于想要完成的脚本有了一个概念，是时候打开文本编辑器并完成编程了。

注意：参见1.3节中的“注意”部分了解如何下载教程文件的信息。

1.在文本编辑器中打开chapter04目录下的pull-quote.html文件。

首先，添加到jQuery文件的一个链接。

2.单击结束的＜/head＞标签之上的空行，并且输入：

＜script src="../_js/jquery-1.6.3.min.js"＞＜/script＞

这会从站点加载jQuery文件。注意，包含jQuery文件的文件夹的名称是_js（别忘了开始的下划线）。然后，可以为程序添加一对＜script＞标签。

3.按下回车键，在jQuery代码下面创建新行，并且添加如下粗体所示的代码：

1＜script src="../_js/jquery-1.6.3.min.js"＞＜/script＞

2＜script＞

3

4＜/script＞

注意：每行左边的行号只是为了方便引用。不要在Web页面上真的将它们作为脚本的一部分录入。

现在，加入document.ready（）函数。

4.单击＜script＞标签之间的空行，并且添加粗体所示的代码：

1＜script src="../_js/jquery-1.6.3.min.js"＞＜/script＞

2＜script＞

3$（document）.ready（function（）{

4

5}）；//end ready

6＜/script＞

当程序变得很长很复杂的时候，JavaScript注释//end ready特别有用。在一个较长的程序中，最终整个程序中有很多}）；散落各处，每一个}）；都标志着一个匿名函数和一个函数调用的结束。放置一个注释来表明该}）；是和哪一部分相匹配的，这使得你稍后回头来阅读自己的代码的时候更容易理解。

步骤1到步骤4涵盖了使用jQuery编写任何程序的基本过程，因此，确保要理解每一个步骤在做什么。接下来，通过选择包含应该出现在pullquote框中的文本的＜span＞标签，我们进入程序的核心部分。

5.添加第4行中粗体所示的代码：

1＜script src="../_js/jquery-1.6.3.min.js"＞＜/script＞

2＜script＞

3$（document）.ready（function（）{

4$（'span.pq'）

5}）；//end ready

6＜/script＞

$（'span. pq'）是一个jQuery选择器，它查找带有pq类的每个＜span＞标签。接下来添加遍历每个＜span＞标签并对它们进行一些操作所需的代码。

6.添加如下第4行和第6行所示的粗体代码：

1＜script src="../_js/jquery-1.6.3.min.js"＞＜/script＞

2＜script＞

3$（document）.ready（function（）{4

$（'span.pq'）.each（function（）{

5

6}）；//end each

7}）；//end ready

8＜/script＞

正如4.10节所介绍的，.each（）是一个jQuery函数，允许遍历一个选择的元素。这个函数接收一个参数，它本身也是一个匿名函数。

接下来，开始构建将要应用于页面上每一个匹配的＜span＞标签的函数：第一步是创建＜span＞的一个副本。

7.把下面第5行中粗体所示代码添加到脚本中：

1＜script src="../_js/jquery-1.6.2.min.js"＞＜/script＞

2＜script＞

3$（document）.ready（function（）{4

$（'span.pq'）.each（function（）{5 var quote=$（this）.clone（）；

6

}）；//end each

7}）；//end ready

8＜/script＞

这个函数首先创建一个名为quote的变量，它包含当前的＜span＞（如果忘记了$（this）的含义，请参见4.10.2节）的一个“副本”（只是一个副本）。jQuery的.clone（）函数复制了当前元素，包括元素中所有的HTML。在这个例子中，它复制了＜span＞标签，包括＜span＞标签中的将要出现在提取引用框中的文本。

复制一个元素会复制所有内容，包括应用于元素的所有属性。在这个例子中，最初的＜span＞有一个类名pq。我们将从副本中删除该类。

8.把如下第6行和第7行所示的粗体代码添加到脚本中：

1＜script src="../_js/jquery-1.6.3.min.js"＞＜/script＞

2＜script＞

3$（document）.ready（function（）{4

$（'span.pq'）.each（function（）{

5 var quote=$（this）.clone（）；

6 quote.removeClass（'pq'）；

7 quote.addClass（'pullquote'）；

9}）；//end ready

10＜/script＞

8}）；//end each

正如4.8.1节所述，removeClass（）函数从标签中删除一个类名，而.addClass（）函数为标签添加一个类名。在这个例子中，要替换副本中的类名，以便使用一个名为.pullquote的CSS类来把＜span＞格式化为提取引用框。

最后，将向页面添加＜span＞。

9.把如下粗体所示代码（第8行）添加到脚本中：

1＜script src="../_js/jquery-1.6.3.min.js"＞＜/script＞

2＜script＞

3$（document）.ready（function（）{

4$（'span.pq'）.each（function（）{

5 var quote=$（this）.clone（）；

6 quote.removeClass（'pq'）；

7 quote.addClass（'pullquote'）；

8$（this）.before（quote）；

9}）；//end each

10}）；//end ready

11＜/script＞

这行代码是函数的最后部分，在这行之前，我们一直在操作Web浏览器的内存中＜span＞的一个副本。浏览页面的人看不到它，直到这个副本真正地添加到DOM中。

在这个例子中，插入的是＜span＞标签的副本，就在HTML中的那个＜span＞标签之前。实际上，页面最终的HTML如下所示：

＜span class="pullquote"＞……and that's how I discovered the Loch Ness monster.

＜/span＞＜span class="pq"＞……and that's how I discovered the Loch Ness

monster.＜/span＞

尽管从这段内容看上去会紧挨着显示副本，CSS格式化使得提取引用框出现在页面的右边。

注意：要实现提取引用框的视觉效果，页面拥有一个CSS样式，它使用了CSS float属性。框移动到文本所出现的段落的右边，并且由段落中其他的文本包围着它。如果不熟悉这一技术，可以通过http：//css.maxdesign.com.au/floatutorial/来了解CSS float属性。如果想要查看.pullquote样式，只要看一下教程文件的头部就行了。该样式及其所有的属性在那里列出。

此时，所有的JavaScript都完成了。然而，我们没有看到任何提取引用框，直到在HTML中给出一些消息。

10.找到页面的H T M L的第一个＜p＞标签。找到一条句子，并且用＜s p a n class="pq"＞＜/span＞包围它。例如：

＜span class="pq"＞Nullam ut nibh sed orci tempor rutrum.＜/span＞

可以重复这一过程来给其他的段落添加提取引用。

11.保存文件并在Web浏览器中预览它。

最终的结果如图4-7所示。如果没有看到一个提取引用框，确保在步骤10中正确地添加了＜span＞标签。此外，检查1.6节关于修改有问题的程序的提示。可以在文件complete_pull-quote.html中看到本教程的一个完成版本。

第5章　动作/响应：让页面通过事件动起来

当你听到人们谈论JavaScript，通常会在对话中听到“交互性”这个词，例如，“JavaScript使我们能够实现Web页面的交互性”。实际上他们说的是，JavaScript允许Web页面对访问者所做的某件事情做出响应：例如，把鼠标指针移动到一个导航按钮上以产生链接的菜单；选择一个单选按钮来显示一组新的表单选项；单击一幅较小的照片使得页面变暗，同时照片的一个较大版本弹出到屏幕上。所有这些Web页面可以响应的不同的访问者的操作叫做事件（event）。JavaScript是一种事件驱动的语言：没有事件，Web页面就不能对访问者做出响应，或者不能做任何真正有趣的事情。这就像台式机，在早上打开它，它不会对你有任何帮助，直到你开始单击文件、做出菜单选择并且在屏幕上移动鼠标光标。

5.1　事件

Web浏览器通过编程来识别像页面载入、某人移动鼠标光标、按下键盘按键或调整浏览器窗口大小等基本动作。发生在Web页面中的每件事情都是一个事件。要让Web页面具有交互性，可以通过编写程序来响应事件。通过这种方式，可以做到：当访问者单击鼠标的时候，＜div＞标签显示或消失；当他把鼠标移动到链接上的时候出现新的图像；或者当他单击表单的提交按钮的时候检查文本字段的内容。

事件表示某事发生的精确时刻。例如，当单击鼠标再释放鼠标按钮的那一时刻，Web浏览器会表示一个单击事件刚刚发生。Web浏览器表示一个事件发生的时刻，也就是该事件触发的时刻，即程序员按下鼠标的时刻。

当单击鼠标按钮的时候，Web浏览器实际上触发了几个事件。首先，只要按下鼠标按钮，mousedown事件就会触发，而当释放按钮的时候，mouseup事件触发，最后，click事件触发（如图5-1所示）。

图　5-1　尽管我们可能没有意识到，当输入、移动鼠标光标或单击的时候，Web浏览器经常会触发事件。events.html文件（包含在本章的教程文件中）展示了这些不同事件的作用。例如，单击一个链接（图5-1中圆圈所示）会触发mousedown、mouseup和click事件

注意：理解这些事件何时以及如何触发需要一些技巧。为了让你测试不同的事件类型，本章在教程文件中包含了一个演示Web页面。在Web浏览器中打开events.html（在testbed文件夹下）。然后，移动鼠标、单击并输入，以查看经常在Web页面上发生的一些不同事件（如图5-1所示）。

5.1.1　鼠标事件

自从Steve Jobs在1984年引入Macintosh的时候，鼠标就开始成为所有个人计算机的一种关键设备。人们使用它打开应用程序，把文件拖拽到文件夹中，从菜单选择项目甚至用鼠标去绘图。自然，Web浏览器提供了很多方式来记录访问者如何使用鼠标和Web页面交互。

·click。当单击并释放鼠标按钮时，click事件就触发了。通常会为一个链接分配click事件。例如，单击缩略图上的一个链接时可以显示该图像的一个较大版本。然而，click事件并非仅限于链接。可以让页面上的任何标签对一个事件做出响应，甚至只是在页面上的任意地方单击。

注意：click事件也可以通过键盘在链接上触发。如果跳到一个链接，并按回车键，click事件就会触发。

·dblclick。当按下并释放鼠标按钮两次，就触发了一个双击事件（dblcli-ck）。这和用来在桌面上打开一个目录或文件的操作相同。双击Web页面不是一种常见的Web冲浪操作，因此，如果使用这个事件，应该让访问者知道他们可以在哪里双击并且在这么做了之后将会发生什么。还要注意，双击事件和两个click事件是一样的，因此，不要把click和dblclick事件分配给同一个标签。否则，click的函数将会在dblclick函数运行之前运行两次。

·mousedown。mousedown事件是click事件的前一半，即单击按钮并且在释放之前的时刻。这个事件对于在页面上拖拽元素很方便。可以让访问者在Web页面上拖拽项目，就像是在自己的桌面上拖拽鼠标一样：通过单击它们（不要释放按钮）并且移动它们，然后释放按钮以放置它们。

·mouseup。mouseup事件是一个click事件的第二部分，即释放按钮的时刻。当释放一个已经拖拽的项目的时候，这个事件很方便用来做出响应。

·mouseover。当把鼠标光标移动到页面上的一个元素的时候，就触发了一个mouseover事件。可以使用这一事件把一个事件处理程序指定给一个导航按钮，并且当访问者把鼠标光标移动到按钮上的时候显示一个弹出的子菜单。（如果习惯了CSS：hover伪类，那么，你就知道这个事件是如何工作的了。）

·mouseout。把鼠标光标从一个元素移走就会触发mouseout事件。当访问者把鼠标光标从页面移开的时候，可以使用这一事件来表示，或者当鼠标光标移动到菜单之外的时候，可以隐藏弹出菜单。

·mousemove。当鼠标光标移动的时候，mousemove事件触发，这在逻辑上意味着这个事件随时触发。可以使用这个事件来记录屏幕上光标的当前位置。此外，可以把这个事件分配给页面上的一个特定的标签，例如，一个＜div＞，并且只响应在该标签中的移动。

注意：一些Web浏览器，例如Internet Explorer，支持很多事件（参见http：//msdn2.microsoft.com/en-us/library/ms533051（VS.85）.aspx），但是，大多数浏览器只是共享其中的一些事件。

5.1.2　文档事件和窗口事件

浏览器窗口本身理解一些事件，包括从当载入页面的时候触发的事件，到当访问者离开页面的时候触发的事件：

·load。当Web浏览器完成下载Web页面文件的全部内容之后（HTML文件本身，加上任何链接图像、Flash电影和外部的CSS和JavaScript文件），load事件触发。Web设计师习惯上会使用这个事件来启动操作Web页面的任何程序。然而，如果有很多的图形或者其他较大的链接文件的话，载入一个Web页面及其所有的文件可能会花很长的时间。在某些情况下，这意味着在页面已经在浏览器中显示相当一段时间后，JavaScript程序都还没有运行。好在jQuery提供了load事件的一个响应性要好得多的替代，这将在本书5.4.1节介绍。

·resize。当我们通过单击最大化按钮或者拖动浏览器的调整大小指针来调整浏览器窗口的大小的时候，浏览器会触发resize事件。当访问者改变自己浏览器窗口的大小的时候，一些设计者使用这一事件来改变页面的布局。例如，在访问者重新调整浏览器窗口的大小之后，可以查看浏览器的宽度，如果确实变宽了，可以改变设计以添加更多的内容列来填充空间。

注意：当重新调整窗口的大小的时候，Internet Explorer、Opera和Safari都触发多个resize事件，而Firefox只是在已经释放了调整大小指针之后触发resize事件。

·scroll。当拖动滚动条的时候，或者使用键盘（up/down/home/end等键）或鼠标滚轮来滚动Web页面的时候，触发scroll事件。如果页面没有一个滚动条，就不会触发scroll事件。一些程序员使用这一事件来帮助搞清楚（在页面滚动后）元素出现在屏幕的什么位置。

·unload。当单击了连接到其他页面的链接、关闭一个浏览器标签页或者关闭浏览器窗口的时候，Web浏览器都会触发unload事件。它就像是JavaScript程序的最后一次喘息，并且给我们一个机会来完成浏览器离开页面之前的最后一次操作。恶意的程序员曾经使用这个事件来使得访问者难以离开页面。每次访问者试图关闭该页面，就会出现一个新的窗口并返回该页面。但是，也可以用这个事件来做些好事情。例如，程序可以警告访问者他已经开始填写一个表单但是还没有提交，或者程序可以把表单数据发送给Web服务器以便在访问者退出页面之前保存数据。

5.1.3　表单事件

在JavaScript出现以前，人们主要通过利用HTML创建的表单来和Web站点交互。在表单字段输入信息确实是访问者向Web站点提供输入的唯一一种方式。由于表单仍然是Web的一个重要部分，因此可以发现有很多表单事件可供使用。

·submit。当访问者提交一个表单的时候，submit事件触发。可以通过单击Submit按钮来提交表单，或者当光标在一个文本字段中的时候直接按回车键来提交。我们将会经常使用带有表单验证的submit事件，在数据发送给Web服务器之前，确保所需的字段都正确填写了。我们将在9.4节学习如何验证表单。

·reset。Reset按钮允许撤销已经对表单所做的任何改变（尽管这和它们通常的用法不一样）。它把页面载入时的表单状态返回给表单。当访问者试图通过reset事件来重置表单的时候，可以运行一段脚本。例如，如果访问者已经对表单做出一些修改，我们可能希望弹出一个对话框并询问“Are you sure you want to delete your changes?”。这个对话框给了访问者一个机会来单击“No”按钮并防止重置（擦除）表单的过程。

·change。很多表单字段在它们的状态发生变化的时候触发一个change事件，例如，当某人单击一个单选按钮，或者从一个下拉菜单做出选择的时候。可以使用这个事件来立即检查在菜单中做出的选择，或者哪个单选按钮选中。

·focus。当跳转到或单击一个文本字段，该字段就会得到焦点。换句话说，浏览器的注意力现在聚焦在该页面元素上。同样，选择一个单选按钮或者单击一个复选框，都会给这些元素提供焦点。可以使用JavaScript来响应focus事件。例如，可以在一个文本字段中添加一条有用的说明：“Type your full name.”。当访问者在字段中单击（给它焦点），可以擦除这些说明，因此会留下一个空白字段供填充。

·blur。blur事件和focus事件相反。当退出一个当前聚焦的字段时，要么跳转到字段之外，要么单击字段之外，就会触发该事件。blur事件的另一个用武之地就是表单验证。例如，当某人在一个文本字段输入其E-mail地址，然后跳转到下一个字段，可以立即检查输入的内容以确保这是一个有效的E-mail地址。

注意：focus和blur事件也适用于页面上的链接。当跳转到一个链接时会触发一个focus事件，当从链接跳走（或者单击离开）时会触发blur事件。

5.1.4　键盘事件

Web浏览器还记录了访问者何时使用键盘，以便可以给按键分配命令或者允许访问者通过按下各种按键来控制脚本。例如，按下空格键可以启动或停止一个JavaScript动画。

遗憾的是，不同的浏览器对于键盘事件的处理存在差异，甚至很难分辨按下了哪个字母（在5.4.5节，我们将学习识别输入了键盘上的哪个字母的一种技术）。

·keypress。当按下一个按键时会触发keypress事件，而不必释放按键。实际上，只要保持按下按键，keypress事件会一次又一次地连续触发。

·keydown。keydown事件和keypress事件相似，当按下一个按键的时候触发keydown事件。在Firefox和Opera中，keydown事件只触发一次。在Internet Explorer和Safari中，keydown事件的行为和keypress事件一样，只要按键按下，它会一次又一次地触发。

·keyup。最后，当释放一个按键时就触发了keyup事件。

5.2　以jQuery的方式使用事件

以传统方式，对事件编程需要些技巧。很长一段时间以来，Internet Explorer以和其他浏览器完全不同的方式来处理事件，因此，需要两套代码（分别针对IE和所有其他的浏览器）才能让代码工作。好在，IE 9现在使用和其他浏览器相同的方法来处理事件，因此，编程变得容易多了。然而，还是有很多人使用IE 8或更早的版本，因此，针对事件编程仍然需要一种容易的、跨浏览器兼容的解决方案。幸运的是我们有jQuery。

正如在第4章中所学到的，像jQuery这样的JavaScript库通过JavaScript编程解决了很多问题，包括令人烦恼的浏览器不兼容问题。此外，库通常简化和JavaScript相关的基本任务。jQuery使得指定事件和事件辅助函数（处理事件的函数）变得很简单。下面是一个基本过程：

正如4.4节所提到的，jQuery编程涉及：a）选择一个页面元素；b）对该元素做些事情。实际上，由于事件对于JavaScript编程来说是不可或缺的，因此最好把jQuery编程考虑成一个包含3个步骤的过程：

1.选择一个或多个元素。

第4章介绍了为什么jQuery允许使用CSS选择器来选择想要操作的页面部分。当分配事件的时候，我们希望选择访问者将要与其交互的元素。例如，一个访问者将会单击的是一个链接、一个表格或还是一幅图像？如果想要分配一个mouseover事件，访问者的鼠标光标停留在什么页面元素上会触发操作？

2.分配一个事件。

在jQuery中，大多数DOM事件有一个对等的jQurey函数。因此，要把事件分配给一个元素，只需要添加一个点号、事件的名称然后是一对圆括号。例如，如果想要给页面上的每个链接添加一个mouseover事件，可以这样做：

$（'a'）.mouseover（）；

要对ID为menu的一个元素添加一个click事件，可以这样编写：

$（'#menu'）.click（）；

我们可以使用5.1.1节到5.1.4节列出的任何事件名称（以及5.4.2节所讨论的只用于jQuery的事件）。

在添加了事件之后，还有一些工作可以做。为了让事件触发时有一些事情发生，必须为事件提供一个函数。

3.给事件传递一个函数。

最后，需要定义当事件触发的时候发生什么。为了做到这一点，可以把一个函数传递给事件。函数包含了在事件触发的时候将要运行的命令：例如，使得一个隐藏的＜div＞标签可见或者突出显示一个鼠标光标停留其上的元素。可以传递之前定义的函数的名字，如下所示：

$（'#start'）.click（startSlideShow）；

当给事件分配一个函数的时候，省略了调用函数的时候通常放在函数名后面的（）。换句话说，如下的方式是无效的：

$（'#start'）.click（startSlideShow（））

然而，给事件分配一个函数最常用的方法是给事件传递一个匿名函数。4.10.1节已经介绍了匿名函数，它们通常是没有名字的一个函数。匿名函数的基本结构如下所示：

function（）{

//your code here

}

通过一个事件使用匿名函数的基本结构如图5-2所示。

注意：要学习有关如何使用jQurey和事件的更多知识，请访问http：//api.jquery.com/category/Events。

下面是一个简单的例子。假设有一个Web页面，其中带有ID为菜单的一个链接。当访问者把鼠标光标移到这个链接上的时候，我们希望其他链接的一个隐藏的列表显示出来，假设这个链接列表的ID为submenu。因此，我们希望做的是为菜单添加一个mouseover事件，然后调用一个函数来显示子菜单。这个过程分解为4个步骤：

图　5-2　在jQuery中，事件像函数一样工作，因此，可以给事件传递一个参数。可以把匿名函数只看做一个参数，即传递给一个函数的一段数据。如果按照这种方式来思考，很容易理解所有这些标点符号是如何组合起来的。例如，在最后一行中，}表示函数的结束（也是传递给mouseover函数的参数的结束）；而）是mouseover（）函数的结束；而分号则是以选择器$（'a'）开始的整条语句的结束

1.选择菜单：

$（'#menu'）

2.附加事件：

$（'#menu'）.mouseover（）；

3.添加匿名函数：

$（'#menu'）.mouseover（function（）{

}）；//end mouseover

后面会经常遇到这样的组合：结束的花括号、结束的圆括号和分号（}）；），在一个函数调用中，这通常标志着一个匿名函数结束了。由于这种组合随处可见，为其添加一个JavaScript注释是一种好办法。在这个示例中，//end mouseover指明了这组符号的含义。

4.添加必要的操作（在这个例子中，就是显示submenu）：

$（'#menu'）.mouseover（function（）{

$（'#submenu'）.show（）；

}）；//end mouseover

很多人发现匿名函数所涉及的混乱的标点符号嵌套很容易令人混淆（最后的}）；总是很奇怪）。这确实容易令人混淆，但是，熟悉JavaScript的奇怪世界的最佳方法就是通过多多实践，因此，下面的动手教程能够帮助你巩固刚刚学习的知识。

注意：show（）在本书6.1.1节介绍。

5.3　教程：事件简介

本教程对于事件的使用给出了一个简单介绍。我们将会让页面对几种不同类型的事件做出反应，以便你了解jQuery事件是如何工作的，以及如何使用它们。

注意：参见1.3节中的“注意”部分以了解如何下载教程文件的信息。

1.在文本编辑器中，打开chapter05目录下的events_intro.html文件。

首先添加到jQuery文件的一个链接。

2.在结束的＜/head＞标签之上的空行上单击，并且输入：

＜script src="../_js/jquery-1.6.3.min.js"＞＜/script＞

这行代码从该站点加载jQuery文件。注意，包含jQuery文件的文件夹的名称是_js（别漏掉了开头的下划线字符）。接下来，要为程序添加一组＜script＞标签。

3.按下回车键，在jQuery代码下创建新的一行，并且添加如下粗体所示的代码：

＜script src="../_js/jquery-1.6.3.min.js"＞＜/script＞

＜script＞

＜/script＞

现在，添加document.ready（）函数。

4.单击＜script＞标签之间的空行，并且添加粗体所示代码：

＜script src="../_js/jquery-1.6.3.min.js"＞＜/script＞

＜script＞

$（document）.ready（function（）{

}）；//end ready

＜/script＞

不要漏掉}）；后面的JavaScript注释。尽管添加注释需要一些额外的录入，但它们能够帮助你识别程序的不同部分。现在，我们已经完成了任何时候在页面上使用jQuery时所要遵从的一些步骤。

接下来要添加事件了。第一个目标很简单：当访问者在页面上的任何地方双击时，出现一个警告框。首先，需要选择想要为其添加事件的元素（在这个例子中，就是该页面）。

5.单击.ready（）函数中的空行，并且添加如下的粗体代码：

＜script src="../_js/jquery-1.6.3.min.js"＞＜/script＞

＜script＞

$（document）.ready（function（）{

$（'html'）

}）；//end ready

＜/script＞

$（'html'）选择了该HTML元素，基本上是整个浏览器窗口。接下来添加事件。

6.在该jQuery选择器的后面输入.dblclick（）；，代码如下所示：

＜script src="../_js/jquery-1.6.3.min.js"＞＜/script＞

＜script＞

$（document）.ready（function（）{

$（'html'）.dblclick（）；//end double click

}）；//end ready

＜/script＞

.db lclick（）是一个jQuery函数，它让浏览器做好准备，当访问者在页面上双击的时候做一些事情。这里唯一缺失的是“做一些事情”部分，这需要将一个匿名函数作为参数传递给dblclick（）函数（如果需要回顾函数是如何工作的，以及传递一个参数的含义，参阅3.4.2节）。

7.输入如下粗体所示的代码，添加匿名函数：

＜script src="../_js/jquery-1.6.3.min.js"＞＜/script＞

＜script＞

$（document）.ready（function（）{

$（'html'）.dblclick（function（）{

}）；//end double click

}）；//end ready

＜/script＞

不必担心，本书后面不会以这种缓慢的速度介绍每个教程；但是，这对于你理解每一部分代码做些什么来说很重要。function（）{}只是一个外壳，在向{和}中添加程序之前，它什么也不做。而添加程序是下一步的工作。

8.最后，添加一条alert语句：

＜script src="../_js/jquery-1.6.3.min.js"＞＜/script＞

＜script＞

$（document）.ready（function（）{

$（'html'）.dblclick（function（）{

alert（'ouch'）；

}）；//end double click

}）；//end ready

＜/script＞

如果在Web浏览器中预览该页面，并且在页面上的任何位置双击，将会出现一个JavaScript警告框，其中带有单词“ouch”。如果不是这样，仔细检查一下你的录入，以确保没有漏掉什么。

注意：在经过长时间的工作后，“ouch”已经出现在屏幕上了，这可能让你觉得有些轻松了。但是，别忘了，这段脚本的“ouch”部分是不重要的，正是你所录入的所有其他代码展示了如何通过jQuery来使用事件的基本知识。随着学习关于编程和jQuery的更多内容，（当访问者双击页面的时候）我们可以很容易地用一系列的动作来替代警告框，例如，让一个元素在屏幕上来回移动，显示一个交互式的照片幻灯片放映，或者打开一款赛车游戏。

现在已经了解了基础的知识，我们来尝试一些其他的事件。

9.添加如下粗体所示的代码：

＜script src="../_js/jquery-1.6.3.min.js"＞＜/script＞

＜script＞

$（document）.ready（function（）{

$（'html'）.dblclick（function（）{

alert（'ouch'）；

}）；//end double click

$（'a'）.mouseover（function（）{

}）；//end mouseover

}）；//end ready

＜/script＞

这段代码选择了页面上的所有链接（即$（'a'）部分），然后给mouseover事件添加了一个匿名函数。换句话说，当某人将鼠标光标悬停在页面上的任何链接的时候，将会发生一些事情。

10.给上一步中添加的匿名函数添加两条JavaScript语句：

＜script src="../_js/jquery-1.6.3.min.js"＞＜/script＞＜script＞

$（document）.ready（function（）{

$（'html'）.dblclick（function（）{

alert（'ouch'）；

}）；//end double click

$（'a'）.mouseover（function（）{

var message="＜p＞You moused over a link＜/p＞"；

$（'.main'）.append（message）；

}）；//end mouseover

}）；//end ready

＜/script＞

这里的第一行var message="＜p＞You moused over a link＜/p＞"；，创建了一个名为message的新变量，并且在其中存储了一个字符串。这个字符串是一个HTML段落标签和一些文本。下一行代码，使用一个类名main选取了页面上的一个元素（$（'.main'）），然后附加了message变量的内容（或者说，添加到了该元素的末尾）。这个页面包含一个带有类main的＜div＞标签，因此，每当访问者将鼠标光标悬停在页面的一个链接上的时候，这段代码直接将“You moused over a link”添加到该div的末尾（参见4.7节，以回顾jQuery的append（）函数）。

11.保存该页面，在浏览器中预览，并且将鼠标光标悬停在页面的任何链接之上。

每次将鼠标光标悬停到一个链接上时，将会有一段话添加到页面（如图5-3所示）。现在，将添加最后一段程序：当访问者在页面的表单按钮上单击的时候，浏览器将会修改出现在按钮上的文本。

12.最后，添加如下粗体所示的代码，使得完成后的脚本如下所示：

＜script src="../_js/jquery-1.6.3.min.js"＞＜/script＞

＜script＞

$（document）.ready（function（）{

$（'html'）.dblclick（function（）{

alert（'ouch'）；

}）；//end double click

$（'a'）.mouseover（function（）{

var message="＜p＞You moused over a link＜/p＞"；

$（'.main'）.append（message）；

}）；//end mouseover

$（'#button'）.click（function（）{

$（this）.val（"Stop that！"）；

}）；//end click

}）；//end ready

＜/script＞

你应该理解代码的大概作用了：$（'#button'）选择了带有ID button的一个元素（在这个例子中，就是表单按钮），并且为其添加了一个click事件，以便当某人单击该按钮的时候，会发生一些事情。在这个例子中，单词“Stop that！”会出现在按钮上。下面介绍匿名函数中的代码是如何实现这些的：

在4.10.2节，我们看到了如何在jQuery中将$（this）用于一个循环。在一个事件中也采用同样的思路：$（this）引用要对事件做出响应的元素，也就是要选择并对其添加事件的元素。在这个例子中，这就是表单按钮。（我们将在9.1.2节学习关于jQuery val（）函数的更多内容，但是基本上，使用它来从一个表单元素读取值或者修改表单元素的值。在这个例子中，将字符串“Stop that！”传递给val（）函数，这会将该按钮的值设置为“Stop that！”）。

13.保存该页面，在浏览器中预览，并且单击该表单按钮。

按钮的文本应该立即改变（如图5-3所示）。作为额外的联系，请添加程序，以便当访问者单击或跳转到文本字段的时候，使得文本字段的背景颜色变为红色。这里给出一些提示你需要：a）选择文本字段；b）使用focus（）事件（参见9.1.4节）；c）使用$（this）（和第12步中相同）在匿名函数中引用文本字段；d）使用.css（）函数（参见4.8.2节）来更改文本字段的背景颜色。可以在chapter05目录下的complete_events_intro.html文件中找到答案（以及该页面的一个完整版本）。

图　5-3　jQuery使得页面很容易对用户交互做出响应，例如，当双击页面的时候打开一个警告框，向页面添加文本以对鼠标光标悬停到链接上做出响应，或者对单击表单按钮做出响应

5.4　更多的jQuery事件概念

由于事件是为Web页面增加交互性的至关重要的部分，因此jQuery包含了一些特殊的jQuery专用函数，它们使得编程更容易而页面也更具有响应性。

5.4.1　等待HTML载入

当页面载入的时候，Web浏览器立即尝试运行它所遇到的任何脚本。因此，在页面头部的脚本可能会在页面完全载入之前运行，在3.5节的一个简单测试教程中看到了这一点。遗憾的是，这种现象常常引发问题。由于很多JavaScript程序涉及操作Web页面的内容，例如，当单击一个特定链接的时候显示一条弹出消息、隐藏特定页面元素、为一个表格的行添加条纹等。如果程序试图操作页面中的还没有被浏览器载入和显示的那些元素，最终会得到JavaScript错误。

处理这一问题最常见的方式是：在执行任何JavaScript之前，load事件等待直到页面完全下载。遗憾的是，在运行JavaScript代码之前等待页面完全载入，这可能会产生一些真正奇怪的结果。只有在一个Web页面的所有内容都已经下载之后，这意味着所有的图像、电影、外部样式表等都已经下载之后，才触发load事件。因此，在带有很多图形的页面上，在运行任何JavaScript之前载入图像的时候，访问者可能真的要盯着页面几秒钟的时间。如果JavaScript对于页面做了很多改变，例如，样式化表格行，隐藏当前的可视化菜单，或者甚至控制页面的布局，访问者每次浏览页面会突然看到页面变化。

幸运的是，jQuery充当了救星。jQuery有一个名为ready（）的函数，它等待直到HTML已经载入浏览器之后才运行页面的脚本，而不是依赖load事件来触发一个JavaScript程序。通过这种方法，JavaScript可以立即操作Web页面，而不用等待图像或视频缓慢载入（这实际上是一个复杂而有用的功能，这也是使用JavaScript库的另一个原因）。

我们已经在本书的一些教程中使用了ready（）函数。ready（）函数的基本结构如下所示：

$（document）.ready（function（）{

//your code goes here

}）；

基本上，所有的程序代码都放入这个函数中。实际上，ready（）函数如此基础，我们可能在使用jQuery的每个页面上都包含它。只需要包含它一次，并且它通常是脚本的第一行和脚本的最后一行。必须将其放置在一对开始的和结束的＜script＞标签中（毕竟这是JavaScript程序），并且将其放置于将jQuery添加到页面的＜script＞标签之后。

因此，在一个完整的Web页面的环境中，该函数看上去如下所示：

＜！DOCTYPE HTML PUBLIC"-//W3C//DTD HTML 4.01//EN""http：//www.w3.org/TR/

html4/strict.dtd"＞

＜html＞

＜head＞

＜meta http-equiv="Content-Type"content="text/html；charset=UTF-8"＞

＜title＞Page Title＜/title＞

＜script type="text/javascript"src="js/jquery.js"＞＜/script＞

＜script type="text/javascript"＞

$（document）.ready（function（）{

//all of your JavaScript goes in here.

}）；//end of ready（）function

＜/script＞

＜/head＞

＜body＞

The web page content……

＜/body＞

＜/html＞

提示：由于ready（）函数几乎在添加了jQuery的页面中会随时用到，有一种简写方式来表示它。可以删除$（document）.ready部分，只是写作：

$（function（）{

//do something on document ready

}）；

5.4.2　jQuery事件

jQuery也提供了事件来处理两个非常常见的交互性问题：鼠标光标移动到某物之上并随后移开，以及单击的时候两个操作的切换。

hover（）事件

mouseover事件和mouseout事件通常一起使用。例如，当把鼠标光标移动到一个按钮上时可能出现一个菜单；当把鼠标光标从按钮上移开，菜单消失。由于成对的这两个事件如此常见，因此jQuery提供了引用这两个事件的一种快捷方式。jQuery的hover（）函数的工作和其他事件一样，只不过它不是接收一个函数作为参数，而是接收两个函数作为参数。当鼠标光标移动到元素上时，第一个函数运行；当鼠标光标从元素上移开时，第二个函数执行。其基本结构如下所示：

$（'#selector'）.hover（function1，function2）；

我们经常看到hover（）函数和两个匿名函数一起使用。这类代码看上去有些怪异，下面的示例将澄清它。假设当某人把鼠标光标移动到一个ID为菜单的链接上，我们希望一个（当前不可见的）ID为子菜单的DIV显示。当鼠标光标从链接上移开，子菜单再次隐藏。可以使用hover（）来做到这点：

$（'#menu'）.hover（function（）{

$（'#submenu'）.show（）；

}，function（）{

$（'#submenu'）.hide（）；

}）；//end hover

为了让包含多个匿名函数的一条语句容易阅读，可以把每个函数移到单独的一行。这样，上述代码的一个可读性更好的版本如下所示：

$（'#menu'）.hover（

function（）{

$（'#submenu'）.show（）；

}，//end mouseover

function（）{

$（'#submenu'）.hide（）；

}//end mouseout

）；//end hover

图5-4展示了这段代码如何用于mouseover和mouseout事件。

图　5-4　jQuery的hover（）函数允许一次指定两个函数。当鼠标光标移动到元素上的时候，第一个函数运行，而当鼠标光标从元素上移开的时候，第二个函数运行

如果匿名函数的方法太容易令人混淆，仍然可以使用普通的函数（参见3.4节）来完成任务。首先，创建一个命名的函数，当mouseover事件触发的时候运行它；为mouseout事件创建另一个具有名字的函数，把两个函数的名字传递给hover（）函数。换句话说，可以把上面的代码重新编写为如下的形式：

function showSubmenu（）{

$（'#submenu'）.show（）；

}

function hideSubmenu（）{

$（'#submenu'）.hide（）；

}

$（'#menu'）.hover（showSubmenu, hideSubmenu）；

如果你觉得这一技术很容易，那就使用它。二者之间没有真的区别，一些程序员喜欢通过使用匿名函数把所有的代码放在一条语句中，而不是把代码分散在几条不同的语句中。

toggle（）事件

jQuery的toggle（）事件的作用和hover（）事件相同，只不过它不是响应mouseover和mouseout事件，而是响应click事件。一次单击触发第一个函数，下一次单击触发第二个函数。当想要使用单击来在两种状态之间交替的时候，使用这个事件。例如，可以让页面上的一个元素在第一次单击的时候出现，而在下一次单击的时候消失。再次单击的话，元素重新出现。

例如，假设当第一次单击链接的时候，想要让submenu＜div＞（来自上面的hover（）示例）显示，然后，当下一次单击链接的时候消失。只要像下面这样，把“hover”换成“toggle”就行了：

$（'#menu'）.toggle（

function（）{

$（'#submenu'）.show（）；

}，//end first click

function（）{

$（'#submenu'）.hide（）；

}//end second click

）；//end toggle

或者，使用带有名字的函数，例如：

function showSubmenu（）{

$（'#submenu'）.show（）；

}

function hideSubmenu（）{

$（'#submenu'）.hide（）；

}

$（'#menu'）.toggle（showSubmenu, hideSubmenu）；

toggle（）函数可以接收两个以上的函数作为参数：例如，如果想要在第一次单击的时候发生一件事情，在第二次单击的时候发生另一件事情，在第三次单击的时候发生第三件事情。传递给toggle（）的每个函数都将响应自己所对应的单击。例如，第一个函数在第一次单击的时候运行，第二个函数在第二次单击的时候运行，第三个函数在第三次单击的时候运行，以此类推。一旦jQuery执行完所有的函数，在下一次单击的时候，它返回到第一个函数（例如，如果向toggle（）传递3个函数，那么，在第四次单击的时候，将会再次运行第一个函数）。

5.4.3　事件对象

无论何时Web浏览器触发事件，它都记录了有关事件的信息，并且将其存储在所谓的事件对象中。事件对象包含了事件发生的时候收集的信息，例如，鼠标光标的水平坐标和垂直坐标，发生事件的元素，或者事件触发的时候Shift键是否按下。

使用jQuery，事件对象可以提供给指定来处理事件的函数使用。实际上，这个对象作为参数传递给函数，因此，要访问它，只需要在函数中包含一个参数名。例如，假设想要找到鼠标光标在页面上任何地方单击时候的X坐标和Y坐标。

$（document）.click（function（evt）{

var xPos=evt.pageX；

var yPos=evt.pageY；

alert（'X：’+xPos+'Y：’+yPos）；

}）；//end click

这里重要的部分是evt变量。当函数调用的时候（通过在浏览器窗口的任何地方单击），事件对象可以存储到evt变量中。在函数体中，可以使用点号语法来访问事件对象的不同属性，例如，evt.pageX返回光标的水平位置（换句话说，从窗口左边界开始的像素数）。

注意：在这个例子中，evt只是程序员提供的一个变量。它不是一个特殊的JavaScript关键字，只是用来存储事件对象的一个变量。可以使用任何想要的名字，例如，event或者只是一个e。

事件对象有很多不同的属性，并且（遗憾的是）属性的列表会根据浏览器的不同而有所变化。表5-1列出了一些常见的属性。

提示：如果访问了keypress（）事件的事件对象的which属性，会得到按下的键的一个数值代码。如果想要确定按下的键（a、K、9等），需要用一个JavaScript方法来处理which属性，而该方法把按键数值转换为键盘上实际的字母、数值或者符号。

String.fromCharCode（evt.which）

5.4.4　阻止事件的常规行为

一些HTML元素对事件有预先编程的响应。例如，单击一个链接的时候，通常载入新的页面；单击表单的提交按钮会把表单数据发送给Web服务器处理。有时候，不希望Web浏览器进行常规的行为。例如，当提交表单的时候（submit（）事件），如果填写表单的某个人遗漏了重要数据，我们可能想要阻止发送表单数据。

可以使用preventDefault（）函数来阻止Web浏览器对一个事件的常规响应。这个函数实际上是事件对象的一部分（参见5.4.3节），因此，我们将在处理事件的函数中访问它。例如，假设页面有一个ID为menu的链接。这个链接实际上指向另一个menu页面（从而关闭了JavaScript的访问者将能够去到menu页面）。然而，已经添加了智能的JavaScript，因此，当访问者单击该链接时，菜单会出现在同一页面。通常，Web浏览器会根据该链接去到menu页面，因此，我们需要阻止这一默认的行为，如下所示：

$（'#menu'）.click（function（evt）{

//clever javascript goes here

evt.preventDefault（）；//don't follow the link

}）；

另一种技术是在事件函数的末尾直接返回值false。例如，下面的代码和上面的代码作用相同：

$（'#menu'）.click（function（evt）{

//clever javascript goes here

return false；//don't follow the link

}）；

5.4.5　删除事件

有时候，我们可能想要删除之前已经分配给一个标签的事件。jQuery的unbind（）允许做到这一点。要使用它，首先创建一个jQuery对象，带有想要从其上删除事件的元素。然后，添加unbind（）函数，传递给它一个带有事件名称的字符串。例如，如果想要阻止类为tabButton的所有标签响应任何click事件，可以这么编写：

$（'.tabButton'）.unbind（'click'）；

看一下这个简短的脚本，来了解unbind（）函数是如何工作的。

1$（'a'）.mouseover（function（）{

2

alert（'You moved the mouse over me！'）；

3}）；

4$（'#disable'）.click（function（）{5

$（'a'）.unbind（'mouseover'）；

6}）；

第1行到第3行为页面上所有的链接（＜a＞标签）的mouseover事件添加了一个函数。把鼠标光标移动到链接上，会打开一个带有消息“You moved your mouse over me！”的警告框。然而，由于警告消息的频繁出现可能会令人厌烦，第4行到第6行允许访问者关闭警告框。当访问者单击带有ID为disable的一个标签的时候（例如，一个表单按钮），mouseover事件会从所有的链接删除，并且警告框不再出现。

注意：要了解有关jQuery的unbind（）函数的更多信息，请访问http：//docs.jquery.com/Events/unbind。

高级用户提示阻止一个事件传播

Internet Explorer和W3C的事件模型都得到了Firefox、Safari和Opera的支持，这使得事件传递超出了初次接收事件的元素的范围。例如，假设要把一个事件辅助函数分配给一个特定链接的click事件，当单击该链接的时候，click事件触发，函数运行。然而，这个事件并没有在这里停止。每个祖先元素（标签包围着单击的元素）也可以响应同样的单击。因此，如果给包含该链接的一个＜div＞标签分配一个click事件辅助函数，用于该＜div＞标签的事件的函数也会运行。

这一概念叫做事件冒泡（event bubbing），意味着多个元素可以响应同一个操作。这里有个例子：假设为一幅图像添加了一个click事件，以便单击图像的时候，一幅新的图像会取代它。该图像位于一个＜div＞标签中，我们也给该标签分配了一个click事件。在这个例子中，当单击＜div＞的时候，会出现一个警告框。现在，当单击图像的时候，这两个函数都会运行。换句话说，即便单击了图像，＜div＞也接收到了click事件。

你可能不会经常遇到这种情况，但是遇到的时候，结果就会令人不安。假设在前面的例子中，当单击图像的时候，我们不希望＜div＞做任何事情。在这个例子中，我们必须阻止click事件传递到＜div＞标签上，而又不会妨碍处理图像的单击事件的函数执行。换句话说，当单击图像的时候，分配给图像的click事件的函数会替换一幅新的图像，然后就停止click事件。jQuery提供了一个名为stopPropagation（）函数来阻止事件传递到一个祖先标签上。这个函数是事件对象的一个方法（参见5.4.3节），因此，在一个事件处理函数中访问它：

$（'#theLink'）.click（function（evt）{

//do something

evt.stopPropagation（）；//stop event from

continuing

}）；

5.5　高级事件管理

使用jQuery时间方法和前面所介绍的概念，我们可以度过一段更长、更快乐的编程生活。但是，如果真的想要从jQuery的事件处理技术中得到最大的益处，那么需要了解bind（）函数。

注意：如果前面一节的内容令你头疼，可以先跳转到5.6节的教程，直到你对事件处理有更多的经验。

与jQuery的click（）和mouseover（）这样的特定于事件的函数相比，bind（）方法是处理事件的一种更加灵活的方式。它不仅允许指定一个事件以及响应该事件的一个函数，而且允许传递供事件处理函数使用的额外数据。这就允许不同的元素和事件（例如，链接上的一个click，或者图像上的一个mouseover）向相同的事件处理函数传递不同的信息，换句话说，一个函数可以根据触发了哪个事件而采取不同的响应。

bind（）函数的基本格式如下：

$（'#selector'）.bind（'click'，myData, functionName）；

第一个参数是包含了事件名称的一个字符串（例如，click、mouseover，或者5.1.1节列出的任何其他事件）。第二个参数是想要传递给该函数的数据，要么是一个对象直接量，要么是包含对象直接量的一个变量。对象直接量（参见5.8.3节）实际上是属性名和值的一个列表：

{

firstName：'Bob'，

lastName：'Smith'

}

可以像下面这样，将一个对象直接量存储到一个变量中：

var linkVar={message：'Hello from a link'}；

传递给bind（）的第三个参数是另一个函数，就是当事件触发的时候执行操作的那个函数。该函数可以是一个匿名函数，或者是一个命名函数，换句话说，这部分和使用一个常规jQuery事件的时候相同，参见5.2节的介绍。

注意：使用bind（）函数传递数据是可选的。如果你只是想要使用bind（）来附加一个事件和函数，那么可以直接省略数据变量：

$（'selector'）.bind（'click'，functionName）；

如下代码与它实现同样的功能：

$（'selector'）.click（functionName）；

假设你想要弹出一个警告框来响应事件，但是，想要根据触发事件的元素的不同而在警告框中显示不同的消息。做到这点的一种方法是，创建其中包含不同对象直接量的变量，然后，将这些变量发送给针对不同元素的bind（）函数，如下面的例子所示：

var linkVar={message：'Hello from a link'}；

var pVar={message：'Hello from a paragraph'}；

function showMessage（evt）{

alert（evt.data.message）；

}

$（'a'）.bind（'click'，linkVar, showMessage）；

$（'p'）.bind（'mouseover'，pVar, showMessage）；

图5-5详细说明了这段代码是如何工作的。它创建了两个变量，第一行的linkVar和第二行的pVar。每个变量都包含了一个对象直接量，具有相同的属性名、消息，但是消息文本不同。函数showMessage（）接收事件对象（参见5.4.3节），并且将其存储到一个名为evt的变量中。该函数运行alert（）命令，显示message属性（其本身是事件对象的data属性的一个属性）。记住，message是对象直接量中定义的属性的名称。

图　5-5　jQuery的bind（）函数允许传递数据给响应事件的函数。通过这种方式，可以将单个函数用于几个不同的元素（甚至是针对不用类型的事件），而允许该函数根据每个事件辅助函数来使用数据

使用bind（）函数的其他方法

jQuery的bind（）函数提供了很大的编程灵活性。除了前面内容中列出的技巧，它还允许把两个或多个事件绑定到同一个函数。例如，假设你编写了一个程序，当访问者单击一幅缩略图图像的时候，它使得一幅较大的图像出现在屏幕上（即成千上万的Web站点上常见的Lightbox效果，我们将会在7.4节介绍它是如何实现的）。当访问者单击页面的任意位置或者敲击键盘上的任意键的时候，你想要让较大的图像消失（如果两种选择都提供的话，你的程序对于喜欢使用键盘和鼠标的访问者都能做出响应）。做到这一点的代码如下所示：

$（document）.bind（'click keypress'，function（）{

$（'#lightbox'）.hide（）；

}）；//end bind

关键部分是'click keypress'。通过提供多个事件名称，每个名称用一个空格隔开，我们告诉jQuery，当列表中的任何一个事件发生的时候，运行该匿名函数。在这个例子中，就是当文档上的click或keypress事件触发的时候。

此外，如果想要附加几个事件，而每个事件触发不同的行为，就不需要多次使用bind（）函数。换句话说，如果想要让访问者单击一个元素的时候发生一件事情，而访问者将鼠标光标悬停到同一个元素上的时候发生另一件事情，你可能会尝试这样编写代码：

$（'#theElement'）.bind（'click'，function（）{

//do something interesting

}）；//end bind

$（'#theElement'）.bind（'mouseover'，function（）{

//do something else interesting

}）；//end bind

但是，可以通过向bind（）传递一个对象直接量（参见4.8.3节）来做同样的事情，这个对象直接量包括一个事件名，后面跟着一个分号，然后是一个匿名函数。重写上面的代码，只需要调用bind（）函数一次，并传递给它一个对象直接量（粗体所示）：

$（'#theElement'）.bind（{

'click'：function（）{

//do something interesting

}，//end click function

'mouseover'：function（）{

//do something interesting

}；//end mouseover function

}）；//end bind

注意：除了这里所介绍的外，jQuery还提供了其他的方式将事件附加到元素中。特别是，当你想要将一个事件附加给一个元素，而该元素是在页面加载之后才添加到页面上的（也就是说，这个元素要么是通过JavaScript编程添加到页面上的，要么是使用本书第四部分所介绍的Ajax技术下载到页面上的），这时候，delegate（）函数很适用。你可以通过13.5节了解delegate（）函数的相关内容。

5.6　教程：一页的FAQ

“常见问题”（FAQ）页面是Web站点的一个常见页面。它们通过全天候地提供直接的回答来改善客户服务。遗憾的是，很多FAQ页面要么非常长，充满了问题和完整的接待；又或者是单页问题链接到各个答案页面。这两种解决方案都减慢了访问者找到答案的速度：在第一种情况下，访问者被迫滚动一个长长的页面来找到问题和解答；在第二种情况下，访问者要等待一个新页面的加载。

在这个教程中，通过创建一个JavaScript驱动的FAQ页面来解决这个问题。当页面载入的时候，所有的问题都可以看到，因此，很容易找到一个特定的问题。而答案隐藏着，直到单击了问题，然后，期望的答案会慢慢地出现在视线中（如图5-6所示）。

5.6.1　任务概览

完成这个任务的JavaScript需要做到几件事情：

·当单击一个问题的时候，显示相应的答案。

·当单击答案可见的一个问题时，答案应该消失。

此外，我们想要使用JavaScript在页面载入的时候隐藏所有答案。为什么不使用CSS呢？例如，对于答案，把CSS display属性设置为none，这是隐藏答案的另一种方法。这一技术的问题在于对于那些不支持JavaScript的访问者会显示什么呢？页面载入的时候，他们不会看到答案，也不能通过单击问题而看到答案。为了使得那些支持JavaScript和关闭JavaScript的访问者都能够看到页面，最好是使用JavaScript来隐藏任何页面内容。

注意：参见1.3节中的“注意”部分以了解如何下载教程文件的信息。

5.6.2　编程

1.在文本编辑器中打开chapter05目录下的faq.html文件。

这个文件已经包含了一个到jQuery文件的链接，并且$（document）.ready（）函数也准备好了（参见5.4节）。首先，在页面载入的时候隐藏所有的答案。

2.单击$（document）.ready（）函数后面的空格，并输入：$（'.answer'）.hide（）；。

每个答案的文本都包含在带有类answer的一个＜div＞标签中。这行代码选择每个＜div＞标签，并且将其隐藏（6.1.1节介绍hide（）函数）。保存页面并在Web浏览器中打开它。答案应该是隐藏的。

下一步是确定要为哪个元素添加一个事件监听器。既然当访问者单击问题的时候答案出现，我们必须选择FAQ中的每个问题。在这个页面上，每个问题是页面的主体中的一个＜h2＞标签。

3.按下回车键创建一个新行，把如下粗体所示的代码添加到脚本中：

＜script src="../_js/jquery-1.6.3.min.js"＞＜/script＞

＜script＞

$（document）.ready（function（）{

$（'.answer'）.hide（）；

$（'.main h2'）

}）；//end of ready（）

＜/script＞

这是一个基本的子孙选择器，用来找到ID为main的一个元素中的所有＜h2＞标签（因此，它不会影响到页面中的任何＜h2＞标签）。现在，该添加事件了。click事件是很好的候选，然而，可以更好地满足需求，即单击问题可以显示或隐藏答案，因此使用jQuery toggle（）函数（参见5.4.3节的“toggle（）事件”部分）。这个函数使得每次单击鼠标的时候能够在两个不同的函数之间切换。

4.紧接着在步骤2输入的代码（在同一行）后面输入.toggle。

这段代码标志着toggle（）函数的开始，它接收两个匿名函数（参见4.10.1节）作为参数。第一个匿名函数在第一次单击时运行，第二个函数在下一次单击时运行。首先为第一个函数添加基本结构。

5.按回车键创建新行，然后输入：

function（）{

}

这段代码是函数的基本框架，表示了传递给toggle（）函数的第一个参数。接下来，为第二个函数添加基本结构。

6.添加如下粗体所示的代码，脚本如下所示：

1＜script src="../_js/jquery-1.6.3.min.js"＞＜/script＞

2＜script＞

3$（document）.ready（function（）{

4$（'.answer'）.hide（）；

5$（'.main h2'）.toggle（6

function（）{

7 8

}，

9

function（）{

10 11

}

12）；//end of toggle（）

13}）；//end of ready（）

14＜/script＞

确保没有漏掉上面第8行末尾的逗号。记住，这里的两个函数就像是传递给一个函数的参数（参见3.4.2节）。当调用一个函数时，可以用逗号把每个参数隔开，例如prompt（'Question'，'type here'）。换句话说，第8行的逗号隔开了两个函数（可以漏掉第12行的注释//end of toggle（），如果愿意这么做的话。它只是为了清楚地表明这一行标志着toggle（）函数的结束）。

现在，是时候添加我们所追求的效果了：初次单击＜h2＞标签的时候，需要显示相关的答案。尽管每个问题包含在一个＜h2＞中，而相关的答案则在紧接着＜h2＞标签的一个＜div＞标签中。此外，该＜div＞使用了一个answer类。因此，我们需要找到跟在单击的＜h2＞后面的＜div＞标签的一种方式。

7.在第一个函数中（步骤5中的第6行）添加：（'.answer'）.fadeIn（）；到脚本正如4.10.2节所述，$（this）应用了当前响应事件的元素，在这个例子中，是一个特定的＜h2＞标签。jQuery提供了几个函数使得在一个页面的基本结构中移动很容易。.next（）函数找到了紧跟在当前的标签之后的标签。换句话说，它找到了跟在＜h2＞标签之后的标签。可以通过把一个额外的选择器传递给.next（）函数来进一步改进这一查找，代码.next（'.answer'）会找到跟在＜h2＞后面的、具有一个answer类的第一个标签。最后，.fadeIn（）渐渐把答案淡入视线（fadeIn（）函数在6.1.1节介绍）。

注意：.next（）函数只是帮助我们在页面的DOM中导航的众多jQuery函数中的一个。要了解更多其他的辅助函数，可以访问http：//docs.jquery.com/Traversing。

现在，应该保存页面并在Web浏览器中查看它。单击页面上的一个问题，其下面的答案将会打开（如果没有，仔细检查你的输入并参考1.6节的故障排除提示）。

在下一个步骤中，将完成开关效果的第二部分，即当再次单击问题的时候隐藏答案。

8.添加如下所示代码段的第10行粗体所示的代码：

1＜script src="../_js/jquery-1.6.3.min.js"＞＜/script＞2＜script＞

3$（document）.ready（function（）{

4$（'.answer'）.hide（）；

5$（'.main h2'）.toggle（6

function（）{

7$（this）.next（'.answer'）.fadeIn（）；

8}，

9 function（）{

10$（this）.next（'.answer'）.fadeOut（）；

11}

12）；//end of toggle（）

13}）；//end of ready（）

14＜/script＞

现在，再次单击的时候，答案淡出。保存页面并再次尝试。尽管页面功能正常，还是可以添加一个小的不错的设计改进。现在，每个问题的左边都有一个小加号。这个加号是一个常用的图标，用来表示“嗨，这里有更多内容”。要表示访问者可以单击来隐藏答案，用一个减号替代这个加号。通过添加＜h2＞标签添加或删除类，可以很容易地做到这点。

9.添加最后两行代码（下面的第8行和第12行）。最后的代码如下所示：

1＜script src="../_js/jquery-1.6.3.min.js"＞＜/script＞

2＜script＞

3$（document）.ready（function（）{

4$（'.answer'）.hide（）；

5$（'.main h2'）.toggle（

6 function（）{

7$（this）.next（'.answer'）.fadeIn（）；

8$（this）.addClass（'close'）；

9}，

10 function（）{

11$（this）.next（'.answer'）.fadeOut（）；

12$（this）.removeClass（'close'）；

13}

15）；//end toggle

15}）；

16＜/script＞

这段代码只是在＜h2＞标签第一次单击的时候向其添加了一个名为close的类，然后，在其第二次单击的时候删除这个类。减号图标在样式表中定义为一幅背景图像（再次，CSS使得JavaScript编程更容易）。

保存页面并预览。现在，当单击问题的时候，不仅会出现答案，而且问题图标也会变化（如图5-6所示）。

图　5-6　只用数行JavaScript，就可以使得页面元素随着鼠标单击显示或消失

第6章　动画和效果

在前两章中，我们学习了使用jQuery的基础知识：如何加载jQuery库、选择页面元素，以及响应诸如访问者单击按钮或将鼠标光标悬停到链接上等事件。大多数jQuery程序包含3个步骤：从页面上选择一个元素；向该元素添加一个事件；然后做些事情来响应该事件。在本章中，我们将学习如何使用jQuery内置的效果和动画函数。此外，本章还会介绍与创建视觉效果相关的几个重要的CSS属性，从而回顾CSS知识。

6.1　jQuery效果

让Web页面上的元素出现和消失，这是常见的JavaScript任务。下拉式导航菜单、弹出式工具提示和自动幻灯片播放，这些效果都依赖于按照意愿显示和隐藏元素的能力。jQuery提供了一些函数来实现隐藏和显示元素的目的。

要使用这些效果中的每一个，可以将其应用到一个jQuery选择，这和任何其他的jQuery函数一样。例如，要隐藏类为submenu的所有标签，可以这样写：

$（'.submenu'）.hide（）；

每个效果函数都接收一个可选的速度设置和一个回调函数。速度表示该效果完成所需的时间，而回调则是当效果完成的时候运行的一个函数（参见6.4节中关于回调的详细介绍）。

要给效果分配一个速度，可以应用3个字符串值中的一个，它们是'fast'、'normal'和'slow'；或者使用表示效果发生的毫秒数的一个数值（1000表示1秒，500表示半秒，以此类推）。例如，让一个元素缓慢地淡出视线的代码如下所示：

$（'element'）.fadeOut（'slow'）；

或者，如果希望元素真的很慢地淡出实现，此过程超过10秒：

$（'element'）.fadeOut（10000）；

当使用一种效果使元素消失时，元素并不是真的从DOM（Document Object Model，详细介绍参见4.7节）中删除了。它的代码仍然在浏览器的内存里，但是，它的display属性设置（和CSS的显示设置相同）设置为none。由于这一设置，元素所占用的空间删除了，因此，页面上的其他内容可能移动到隐藏的元素之前所占据的位置。可以在testbed教程文件夹中的effects.html文件上看到所有jQuery效果的作用（如图6-1所示）。

注意：用来设置某种效果的速度的关键字'fast'、'normal'和'slow'，分别与200毫秒、400毫秒和600毫秒是相等的。因此，

$（'element'）.fadeOut（'slow'）；

等同于

$（'element'）.fadeOut（600）；

6.1.1　基本显示和隐藏

jQuery为基本的显示和隐藏元素提供了3个函数：

·show（）使得一个隐藏的元素可见。如果元素已经在页面上可见，那么它没有任何效果。如果没有提供一个速度值，元素会立即出现。然而，如果提供一个速度值，例如show（1000），元素从左上角到右下角动画地出现。

·hide（）隐藏一个可见的元素。如果元素已经是隐藏的，它没有任何效果，这和show（）函数一样。如果没有提供一个速度值，元素会立即消失。然而，如果提供一个速度值，元素以收缩运动的方式动画地淡出视线。

·toggle（）切换元素的当前显示值。如果该元素当前是可见的，toggle（）隐藏该元素；如果该元素是隐藏的，toggle（）显示该元素。当想要有一个单独控件（例如，按钮）来切换一个元素的显示和隐藏的时候，这个函数是理想的选择。

在5.6节的教程中，我们看到了hide（）和toggle（）函数的使用方式。那段脚本使用hide（）实现：在页面的HTML载入时，FAQ页面上的所有答案都消失。然后，使用toggle（）实现：当单击问题时，那些答案交替地显示和隐藏。

6.1.2　淡入淡出元素

为了达到更为生动的效果，可以把一个元素淡出或淡入，在任何一种情况下，都只是随着时间来改变元素的透明度。jQuery提供了3个和淡入淡出相关的函数：

图　6-1　可以在testbed中的教程文件effects.html上测试jQuery的可视化效果。单击函数的名字，例如fadeOut（'#photo'），会看到图像淡出、向上滑动或出现的时候，文本和图像的外观变化。一些效果将会变为灰度的，表示它们不能应用于该元素。例如，如果页面上的图像已经可以看到了，那么使图像显示的代码就没有什么意义了

·fadeIn（）使得一个隐藏的元素淡入视野。首先，元素占据的空间出现在页面上（这可能意味着页面上的其他元素要移走）；然后，元素逐渐地变得可见。如果元素在页面上已经可见，则这个函数没有任何效果。如果没有提供一个速度值，元素使用“常规”设置来淡入（400毫秒）。

·fadeOut（）通过使得一个可视的元素像幽灵般地淡出视线来隐藏它。如果元素在页面上已经隐藏，这个函数没有任何效果，这和fadeIn（）函数一样。如果没有提供一个速度值，元素以400毫秒的速度淡出。

·fadeToggle（）组合了淡入和淡出的效果。如果该元素当前是隐藏的，它会淡入视线中；如果它当前是可见的，该元素会淡出视线。可以使用这个函数让一个提示框出现在页面上或从页面消失。例如，假设有一个按钮，其上显示单词“instructions”。当访问者单击该按钮的时候，带有说明的一个div将淡入视线；再次单击该按钮，将会使说明淡出视线。要让该提示框每半秒钟淡入或淡出一次，可以这样编写代码：

$（'#button'）.click（function（）{

$（'#instructions'）.fadeToggle（500）；

}）；//end click

·fadeTo（）的工作方式和其他两个效果函数略有不同。它把图像淡化到一定的透明度。例如，可以让图像淡化到半透明。和其他效果不同，必须提供一个速度值。此外，还提供0～1之间的一个值来表示元素的透明度。例如，要把所有的段落淡化到75%的透明度，可以这样编写代码：

$（'p'）.fadeTo（'normal'，.75）；

这个函数改变了一个元素的透明度，而不管这个元素是可见的还是不可见的。例如，假设要把当前隐藏的一个元素淡化到50%的透明度。然后，如果使用show（）或fadeIn（）显示该元素，它会以50%的透明度显示。同样，如果隐藏一个半透明的元素，然后再显示它，它的透明度设置还是会恢复。

如果把一个元素淡化到0透明度，该元素就不再可见了，但是，它在页面上占据的空间仍然保留。换句话说，和其他的消失效果不同，淡化到0将会在页面上元素所在的位置留下空白。

6.1.3　滑动元素

为了得到更多一些的视觉动作，可以把一个元素滑入或滑出视线。这些函数和淡化元素的函数类似，因为它们也是让页面元素从视线范围内出现或消失，并且也可能有一个速度值。

·slideDown（）使得一个隐藏的元素滑入视线。首先，元素的顶部出现，并且随着元素其他部分的出现，元素下面的任何内容都向下推动。如果元素在页面上已经可见，它不会有任何效果。如果没有提供一个速度值，元素会使用“常规”设置（400毫秒）滑入。

·slideUp（）通过隐藏元素的底部并且把元素之下的任何内容向上移动，直到元素消失，从而把元素从视线中移除。如果元素已经是隐藏的，它和slideDown（）函数一样不会有任何效果。如果没有提供一个速度值，元素会在400毫秒内滑出。

·如果元素当前是隐藏的，slideToggle（）应用slideDown（）函数；如果元素是可见的，则使用slideUp（）函数。这个函数允许用一个单独的控件（按钮）来显示和隐藏一个元素。

快速学习CSS的绝对定位

通常，当隐藏Web页面上的一个元素时，其他的元素会移动过来填充这一空白。例如，如果隐藏了页面上的一幅图像，该图像消失，图像下面的内容会移动到页面上边。同样，让一个元素出现，会迫使其他的内容为新显示的元素腾出位置。你可能不想让页面上的内容像这样跳来跳去。在这种情况下，可以使用CSS和绝对定位，把一个元素放置在常规页面内容流之外。换句话说，使用CSS的position属性，可以让div、图像或段落出现在页面的顶部，就好像它位于自己单独的层上一样。

要让一个元素出现在页面之上，给它一个单独的position值。可以使用left、right、top和bottom属性来指定该元素在页面上的位置。例如，假设有一个＜div＞标签，它包含一个登录表单。这个登录表单通常不显示，但是，当访问者单击了一个链接，该表单就会滑动显示于该位置，刚好位于页面其他内容之上。可以像下面这样定位该div：

#login{

position：absolute；

left：536px；

top：0；

width：400px；

}

这个样式将div放在浏览器窗口顶部，且距离左边界536像素。可以使用right属性来将元素放在距离右边界一定像素的位置，或者，使用bottom属性来确定它和浏览器窗口底边的位置关系。

当然，你可能想要相对于浏览器窗口以外的某些内容来放置一个元素。例如，弹出的工具提示通常都相对于某个其他元素来定位的：页面上的一个单词后面可能跟着一个"?"，当单击这个"?"的时候，会打开一个小的对话框，其中带有针对该单词的定义。在这种情况下，工具提示不会相对于浏览器窗口的顶端、左边、右边或底部来定位，而是紧挨着该单词。要实现这一点，需要相对于一个元素来提供定位，而该元素包含了绝对定位的项目。例如，看如下这段HTML：

＜span class="word"＞Heffalump

＜span class="definition"＞An

imaginary, el-

elephant-like creative from Winnie the

Pooh＜/span＞

＜/span＞

要让definition span出现在单词的下方，首先需要相对地定位word span，然后，像下面这样绝对定位definition：

.word{position：relative；}

.definition{

position：absolute；

bottom：-30px；

left：0；

width：200px；

}

有关绝对定位的更多信息，请访问www.elated.com/articles/css-positioning/，或者参考《CSS：The Missing Manual》一书。

6.2　教程：登录滑块

在本教程中，将通过创建一个常用的用户界面元素来练习使用jQuery效果：通过单击鼠标而滑入或滑出视线的一个面板（如图6-2所示）。

图　6-2　一会儿看不到表单，一会儿又看到表单了。这个登录表单通常会隐藏（在顶部），但是，简单地单击一下鼠标，就会显示该表单，等待访问者填入信息并登录

这个基本的任务相当简单：

1.选择带有“Login”消息的段落。

记住，很多jQuery编程首先是从选择页面上的一个元素开始的。在这个例子中，“Login”段落将会接收访问者的单击。

2.给该段落添加一个click事件。

没有事件，JavaScript就没有交互性。访问者需要和选取的元素（Login段落）交互，以便做些事情。

3.打开和关闭表单的显示。

前面的两个步骤只是回顾（但确实需要这么多的jQuery编程）。最后一个步骤才会用到我们所学习的效果。我们可以让表单立即显示（show（）函数）、滑入视线（slideDown（）函数）或淡入视线（fadeIn（）函数）。

注意：参见本书1.3节了解如何下载教程文件。

编程步骤

1.在文本编辑器中打开chapter06目录下的signup.html文件。

这个文件已经包含了到jQuery文件的一个链接，并且$（document）.ready（）函数（参见5.4节）也有了。首先，选择带有“Login”文本的段落。

2.单击$（document）.ready（）函数后面的空行，然后输入$（'#open'）。

“Login”文本位于带有ID为open的一个段落标签中：＜p id=“open”＞Login，因此，刚才所输入的代码将会选择该元素。现在该要添加事件处理程序了。＜/p＞

3.添加如下的粗体代码，使得脚本如下所示：

$（document）.ready（function（）{

$（'#open'）.click（function（）{

}）；//end click

}）；//end ready

这段代码添加了一个click处理程序，以便每次访问者单击该段落的时候，都会发生些事情。在这个例子中，当第一次单击的时候，表单会出现；再次单击的时候，表单会消失；下一次单击的时候，表单又会出现，以此类推。换句话说，表单在可见和不可见之间切换。jQuery提供了3个函数来实现这一目的：toggle（）、fadeToggle（）和slideToggle（）。其差别只是在于效果如何显现。

4.在click函数中的空行上单击，并且录入：

$（'#login form'）.slideToggle（300）；

这段代码选择了登录表单，然后，如果它当前不可见，将其滑入视线；并且如果它当前可见，将其滑出视线。最后，将修改段落的class，以便可以使用一个CSS类样式来修改“Login”的外观。

5.添加如下粗体所示的代码，以使得最终的脚本如下所示：

1$（document）.ready（function（）{

2$（'#open'）.click（function（）{

3$（'#login form'）.slideToggle（300）；

4$（this）.toggleClass（'close'）；

5}）；//end click

6}）；//end ready

4.10.2节曾介绍过，在一个事件处理程序中，可以使用$（this）来引用要响应该事件的元素。在这个例子中，$（this）引用访问者在其上单击的段落——在第2行的$（'#open'）之上。toggleClass（）函数直接给一个元素添加一个class，或者从该元素删除一个class。和其他的toggle函数一样，如果没有这个特定的class, toggleClass（）会添加它；如果有，toggleClass（）会删除它。在这个例子中，在页面的样式表中有一个class样式：.close（查看文件的＜head＞，可以看到该样式以及它做些什么）。

6.保存该页面并在Web浏览器中预览。

确保单击“Login”段落几次，以了解它是如何工作的。在chapter06文件夹下，可以找到本教程完成后的版本complete_signup.html。用toggle（）或fadeToggle（）来替代slideToggle（），尝试一下其他的切换效果。

但是，如果我们想要用两种不同的效果，该怎么办呢？一种是针对表单的显示，例如，让其从底部向上滑入视线；另一种效果是令其消失，例如，淡出视线。步骤5中的代码显然是不行的，因为click（）函数并不允许选择两种不同的操作。然而，5.4.3节介绍过，jQuery提供了一种特殊的事件：toggle（）事件，可以用来处理这种情况。不要和toggle（）效果搞混淆了，该效果是让一个元素出现或消失的；toggle（）事件允许在奇数次和偶数次单击之间切换运行不同的代码。因此，第一次单击的时候，表单出现；第二次单击的时候，表单消失。

要让表单滑入视线，然后在再次单击的时候淡出视线，可以使用如下的代码：

$（document）.ready（function（）{

$（'#open'）.toggle（

function（）{

$（'#login form'）.slideDown（300）；

$（this）.addClass（'close'）；

}，

function（）{

$（'#login form'）.fadeOut（600）；

$（this）.removeClass（'close'）；

}

）；//end toggle

}）；//end ready

6.3　动画

我们不只可以使用jQuery提供的内置效果。jQuery还提供了一个animate（）函数，允许对任何CSS属性动画，它接收像素、em和百分比值。例如，可以对文本大小、页面上元素的位置、对象的透明度或者边框的宽度设计动画。

注意：jQuery本身不能对颜色动画，例如，文本的颜色、背景颜色或者边框颜色。然而，jQuery Color插件可以做到这些。本教程包含了该插件的一个副本，并且6.5节将介绍起作用的生动的颜色的一个示例。可以从https：//github.com/jquery/jquery-color获取该颜色插件的最新版本（简而言之，插件是包含了额外的JavaScript程序的一个单独文件，而这些程序可以为jQuery增添功能）。

要使用这个函数，必须传递一个对象直接量（参见4.8.3节），其中包含想要改变的CSS属性以及希望动画的值的列表。例如，假设想要动画一个元素：将其从页面左边界移动650个像素，将其透明度改为50%，并将其字体大小增加到24像素。下面的代码使用这些属性和值创建了一个对象：

{

left：’650px'，

opacity：.5，

fontSize：’24px'

}

注意，如果一个值包含px、em或%这样的单位，必须将值放在引号中。在这个例子中，需要用引号将’650px'括起来，是因为它包含'px'，但是，不用将opacity的值括起来，因为0.5只是一个数字，而不包含任何其他的字母或其他字符。同样，用引号将属性（left、opacity和fontSize）括起来也是可选的。

注意：JavaScript不会接收CSS属性的连字符。例如，font-size是一个有效的CSS属性，但是JavaScript不会理解它，因为连字符具有特殊的含义（它是JavaScript的减号运算符）。在JavaScript中使用CSS属性的时候，删除连字符并且把跟在连字符后面的单词的第一个字母大写。例如，font-size变成了fontSize，而border-left-width变成了borderLeftWidth。然而，jQuery允许使用连字符，但是，必须把属性名放在引号中，如下所示：

{

'font-size'：’24px'，

'border-left-width'：’2%’

}

假设想要使用这些设置对ID为message的一个元素实现动画。可以像下面这样使用animate（）函数：

$（'#message'）.animate（

{

left：’650px'，

opacity：.5，

fontSize：’24px'

}，

1500

）；

animate（）函数可以接收几个参数。第一个参数是一个对象直接量，其中包含了要动画的CSS属性。第二个参数是动画的时间长度（以毫秒为单位）。在上面的例子中，动画持续1500毫秒，即1.5秒。

注意：为了使用CSS的left、right、top或bottom属性来对元素的位置实现动画，必须把元素的CSS position属性设置为absolute或relative。只有这两个定位属性允许给其赋定位值（参见本书6.1.3节）。

可以使用+=或-=作为动画选项的一部分，从而根据属性的当前值来设置它。例如，假设想要这样对一个元素实现动画：每次单击它的时候都将它向右移动50像素。可以这么做：

$（'#moveIt'）.click（function（）{

$（this）.animate（

{

left：’+=50px'

}，

1000）；

}）；

注意：我们不能对border-width属性实现动画。它是一个缩写的属性，用来一次性设置所有4个边框的宽度：

border-width：2px 5px 2px 6px；

如果想要对一个边框的宽度实现动画，需要对想要实现动画的每个边框使用全名。例如，如果想要对所有4个边框实现动画，以最终使得每个边框的宽度变为20像素，对于传递给animate函数的对象直接量，需要为其添加每个边框宽度属性（border-width-left、border-width-top等）：

$（'#element'）.animate（

{

borderTop：20px，

borderRight：20px，

borderBottom：20px，

borderLeft：20px

}，1000）；

缓动

jQuery效果函数（slideUp（）、fadeIn（）等）和animation（）函数都接收另一个用来控制动画过程的速度的参数，这就是缓动（easing），它确定了动画过程不同时刻的速度。例如，将一个元素移过页面的时候，可能让这个元素缓慢地开始移动，然后变得很快，最后随着动画的完成再次慢下来。给动画添加缓动，使得动画在视觉上更有趣且更有动态感。

jQuery只包含了两个缓动方法：swing和linear。linear方法提供了一个稳定的动画，以使得动画的每个步骤都是相同的（例如，如果要让一个元素以逐渐变化的方式穿过屏幕，每一步的距离和前一步都是相同的）。swing要更加动态一些，随着动画的开始变得更加快一些，然后再慢下来。swing是一个常用设置，因此，如果没有指定任何缓动，jQuery会使用swing方法。

对于任何jQuery效果来说，缓动方法是其第二个参数，因此，要使用linear方法将一个元素滑出视线，可以这样编写代码：

$（'#element'）. slideUp（1000，'linear'）；

当使用animate（）函数的时候，缓动方法是第三个参数，第一个参数是一个对象直接量，包含了我们想要实现动画的CSS属性；第二个参数是动画的整体速度。例如，要对6.3节的动画代码使用linear缓动方法，可以像下面这样编写代码：

$（'#message'）.animate（

{

left：’650px'，

opacity：.5，

fontSize：’24px'

}，

1500，

'linear'

）；

然而，并不仅限于使用jQuery所提供的两个缓动方法。得益于其他程序员的辛勤工作，可以添加一系列的其他的缓动方法，其中的一些看上去生动有趣。使用jQuery easing插件（可以从http：//gsgd.co.uk/sandbox/jquery/easing/以及教程下载中获取），可以以某种相当生动的方式来实现动画效果。例如，easeInBounce方法使得一个动画的速度和方向都快速变化，就好像这个元素在不断弹跳起来一样。

提示：使用easing插件，有很多不同的缓动方法，并且大多数插件的名称并不能帮助你了解这个缓动方法看上去是什么样的。要更好地了解不同的缓动方法的视觉效果，可以访问一下www.robertpenner.com/easing/easing_demo.html，在那里，可以自行尝试每种方法。

要使用easing插件（这是一个外部JavaScript文件），可以把该文件附加到页面中，跟在连接到jQuery库的代码的后面就行了。一旦连接到了easing插件，就可以使用任何可用的缓动方法（参见http：//gsgd.co.uk/sandbox/jquery/easing/以获取一个完整的列表）。例如，假设想要在访问者单击一个div标签的时候使其尺寸变大，并且想要通过使用easeInBounce方法让动画更加有趣。假设这个div有一个ID为animate，代码可能会如下所示：

1＜script src="js/jquery-1.6.3.min.js"＞＜/script＞

2＜script src="js/jquery.easing.1.3.js"＞＜/script＞

3＜script＞

4$（document）.ready（function（）{

5$（'#animate'）.click（function（）{

6$（this）.animate（

7{

8 width：’400px'，

9 height：’400px'

10}，

11 1000，

12'easeInBounce'）；//end animate

13}）；//end click

14}）；//end ready

15＜/script＞

第1行和第2行载入了jQuery和easing插件。第4行是我们曾经见过的ready（）函数（参见本书5.4节），第5行将一个click事件处理程序添加给该div。关键的动作在第6行到第12行。正如我们在4.10.2节所介绍的，在一个事件内部，$（this）引用响应该事件的元素，在这个例子中，就是这个＜div＞标签。换句话说，通过单击div，你也可以通过改变其宽度和高度（第8行和第9行）来对其实现动画。第11行使得动画持续1秒钟（1000毫秒），第12行将缓动方法设置为easeInBounce（你可以用任何其他的缓动方法，如easeInOutSine、easeInCubic等，来替换它）。

注意：在教程文件的chapter06文件夹下，可以找到应用这段代码的一个示例。在浏览器中打开文件easing_example1.html。文件easing_example2.html展示了如何使用toggle（）事件（参见本书5.4.3节）对该div应用两个不同的缓动方法。

6.4　在效果完成后执行一个操作

有时候，我们需要一旦一个效果完成就做些事情。例如，假设当特定照片淡入视线，我们希望出现一个标题。换句话说，标题必须在照片进入视线之后才弹出。通常，效果不会一个接着一个地执行，它们在调用的时候同时发生。因此，如果脚本有一行代码把照片淡入到视野，并且有另一行代码使得标题出现，那么，标题将会在照片还在淡入的时候出现。

要解决这个难题，我们可以给任何效果传递一个回调函数，这是只有在效果完成的时候才运行的一个函数。对于大多数效果来说，回调函数作为第二个参数传递（对于fadeTo（）函数来说，是第三个参数）。

例如，假设页面上有一个ID为photo的图像，并且其下面的一个段落拥有一个ID为caption。要把照片淡入到视线中，然后再显示标题，可以像下面这样使用一个回调函数：

$（'#photo'）.fadeIn（1000，function（）{

$（'#caption'）.fadeIn（1000）；

}）；

当然，如果想要在页面载入的时候运行函数，应该希望先隐藏照片和标题，然后执行fadein效果：

$（'#photo，#caption'）.hide（）；

$（'#photo'）.fadeIn（1000，function（）{

$（'#caption'）.fadeIn（1000）；

}）；

如果使用animate（）函数，那么，回调函数出现在所有其他的参数之后，先是包含了要实现动画的CSS属性的对象直接量，然后是动画周期、缓动设置。然而，缓动设置是可选的，因此，你可以传递animate（）函数、属性列表、持续时间和回调函数。例如，假设你不仅需要让照片淡入视线，而且想要将其宽度和高度从0增加到完整的大小（一种放大效果）。可以使用animate（）函数来做到这点，然后，像下面这样显示标题：

1$（'#photo'）.width（0）.height（0）.css（'opacity'，0）；

2$（'#caption'）.hide（）；

3$（'#photo'）.animate（

4{

5 width：’200px'，

6 height：’100px'，

7 opacity：1

8}，

9 1000，

10 function（）{

11$（'#caption'）.fadeIn（1000）；

12}

13）；//end animate

上面的第1行代码将照片的width、height和opacity设置为0（这将照片隐藏了起来，并且让其准备好进行动画）。第2行隐藏了标题。第3～13行是实际的动画函数，其中在第10～12行发生回调。这可能看上去有点奇怪，但是，遗憾的是，回调函数是在一个效果完成的时候执行动作（包括不同页面元素上的效果）的唯一方法。

注意：chapter06文件夹下的callback.html文件展示了上面的代码的应用。

当你想要在一行中实现几个元素的动画的时候，例如，让一个图像移动到屏幕中央，然后一个标题淡入视线，然后图像和标题都淡出视线，回调函数可能需要些技巧。为了做到这点，我们需要给回调函数传递一个回调函数，如下所示：

$（'#photo'）.animate（

{

left：’+=400px'，

}，

1000，

function（）{//first callback function

$（'#caption'）.fadeIn（1000，

function（）{//second callback function

$（'#photo，#caption'）.fadeOut（1000）；

}//end second callback

）；//end fadeIn

}//end first callback function

）；//end animate

注意：chapter06文件夹下的multiple-callbacks.html文件展示了这段代码的应用。

然而，如果想要给同一个页面元素添加额外的动画，则不需要使用回调函数。例如，假设想要将一幅图像移动到屏幕上，然后，让其淡出视线。在这种情况下，只要使用animate（）函数移动图像，然后将其淡出视线就可以了。可以像下面这样来做到这点：

$（'#photo'）.animate（

{

left：’+=400px'，

}，

1000

）；//end animate

$（'#photo'）.fadeOut（3000）；

在这个例子中，尽管浏览器立即执行这段代码，但是jQuery会把每个效果放置到一个队列中，因此，第一个动画运行，然后，fadeOut（）函数运行。使用jQuery函数串联技术（参见本书4.6.4节），可以将上面的代码重新编写为下面这样：

$（'#photo'）.animate（

{

left：’+=400px'，

}，

1000）.fadeOut（3000）；

如果想要一幅照片淡入、淡出，然后再次淡入，可以像下面这样使用串联技术：

$（'#photo'）.fadeIn（1000）.fadeOut（2000）.fadeIn（250）；

注意：要了解有关效果队列如何工作的相关信息，请访问jQuery的Web站点：http：//api.jquery.com/jQuer y.queue/。

在对一个元素上的效果排队的时候，另一个派得上用场的jQuery函数是delay（）。在开始队列中的下一个效果之前，这个函数直接等待指定的毫秒数。例如，假设你想要将图像淡入视线，等待10秒，然后将其淡出视线。可以像下面这样使用delay（）函数：

$（'#photo'）.fadeIn（1000）.delay（10000）.fadeOut（250）；

6.5　教程：动画的仪表盘

在本教程中，我们将使用animate（）函数移动一个＜div＞标签，使其从页面的左边界向右移动进入人们的视线。这个div是绝对定位的（参见本书6.1.3节了解关于绝对定位的更多内容），以便该框的大部分都悬挂在浏览器窗口边界之外的页面左边界之外（参见图6-3左图）。当访问者将鼠标悬停在div的可见的边界上，这个div会完全移入视线（参见图6-3右图）。要让这个效果更加有趣，我们可以使用两个插件对该div的背景颜色实现动画，并且使用几个不同的缓动方法。

图　6-3　通过将页面元素隐藏到浏览器窗口的一边之外（就像左图中的div，它基本上在视线之外），我们可以得到很多乐趣。通过使用animate（）函数可以将该页面元素完全显示出来（参见右图）

注意：参见本书1.3节的介绍以了解如何下载教程文件。

基本的任务相当简单：

1.选择该＜div＞标签。

别忘了，很多jQuery编程都是从选择页面上的一个元素开始的。在这个例子中，就是鼠标选定的div标签。

2.添加一个hover事件。

Hover事件（参见本书5.4.2节）是一个特殊的jQuery函数，而不是一个真正的JavaScript事件。当一个访问者将鼠标光标悬停在该元素上的时候，它允许执行一组操作，然后，当访问者将鼠标光标从元素上移走时，会执行另一组动作（hover事件实际上只是本书5.1.2节所介绍的mouseOver和mouseOut事件的一个组合）。

3.为mouseover事件添加animate函数。

当访问者将鼠标光标悬停到这个div上的时候，我们将对div左边的位置进行动画，将其从视线之外移动到浏览器窗口的左边。此外，我们还将对该div的背景颜色进行动画。

4.为mouseout事件添加另一个animate函数。

当访问者将鼠标光标从div上移走的时候，我们将对div进行动画，使其回到最初的位置并具有其最初的背景颜色。

编程

1.在文本编辑器中打开chapter06目录下的animate.html文件。

这个文件已经包含了到jQuery文件的一个链接，并且也已经有了$（document）.ready（）函数（参见本书5.4节的介绍）。然而，由于我们要对div的背景颜色进行动画，并且要使用几个有趣的缓动方法，因此需要添加两个jQuery插件：color插件和easing插件。

2.单击第一个＜script＞标签之后的空行，并且添加如下粗体所示的代码：

＜script src="../_js/jquery-1.6.3.min.js"＞＜/script＞

＜script src="../_js/jquery.easing.1.3.js"＞＜/script＞

＜script src="../_js/jquery.color.js"＞＜/script＞

jQuery插件（我们将在下一章中了解更多）只不过是为jQuery添加功能的外部JavaScript文件，通常它们允许你给站点添加复杂的效果或功能，而不需要太多的编程（由你所编写的）。接下来，我们将选择div并为其添加hover（）函数。

3.在$（document）.ready（）函数内的空行上单击，并且输入$（'#dashboard'）.hover（）；//end hover，以使得代码如下所示：

$（document）.ready（function（）{

$（'#dashboard'）.hover（）；//end hover

}）；//end ready

$（'#das hboard'）选择了＜div＞标签（该标签有一个id为dashboard）。hover（）函数接收两个参数，这是两个匿名函数（参见本书4.10.1节），它们描述了当访问者将其鼠标光标悬停在div上的时候，以及随后将鼠标光标移开的时候，div做些什么。我们一段一段地构建这些代码，而不是一次输入所有的代码，首先添加hover（）函数，然后添加两个匿名函数的框架。这种方法很有用，因为如果你一不小心，圆括号、花括号、逗号和分号的嵌套就会层出不穷。

4.在hover（）函数的括号之间单击，添加两个空白的匿名函数：

$（document）.ready（function（）{

$（'#dashboard'）.hover（

function（）{

}，

function（）{

}

）；//end hover

}）；//end ready

在为jQuery的.hover（）函数添加完其所有程序之后，它看上去有点杂乱（参见步骤12）。然而，它只不过是接收两个匿名函数作为参数的函数。在添加函数中的所有程序之前，先添加匿名函数的“外壳”，这是个好办法，可以确保已经正确地设置了匿名函数。

提示：经常测试你的代码以确保没有进行任何录入错误，这是个好主意。在步骤4中，我们可以在第一个匿名函数中输入alert（'mouseOver'），并且在第二个匿名函数中输入alert（'mouseOut'），然后，在Web浏览器中预览该页面。当鼠标光标放到该div上的时候，应该会看到一个警告框出现，而当把鼠标光标移开的时候（例如，关闭第一个警告对话框），将会看到另一个警告框。如果没有出现警告框，那说明录入有误。按照这些步骤仔细检查代码，或者使用本书1.6节介绍的步骤，使用浏览器的错误控制台找出错误。

5.在第一个匿名函数中输入type$（this）.animate（）；//end animate。

正如4.10.2节所介绍的，在一个事件中，$（this）引用我们想要为其添加事件的页面元素。在这个例子中，$（this）引用带有ID dashboard的＜div＞标签。换句话说，将鼠标悬停在这个div上也会使其动画。

6.添加带有你想要对其实现动画的CSS属性的一个对象直接量：

$（document）.ready（function（）{

$（'#dashboard'）.hover（

function（）{

$（this）.animate（

{

left：’0’，

backgroundColor：'rgb（255，255，255）’

}

）；//end animate

}，

function（）{

}

）；//end hover

}）；//end ready

animate（）函数的第一个参数是包含了CSS属性的一个对象直接量（参见本书4.8.3节）。在这个例子中，该div当前有一个left值为—92像素，因此，div的大部分是隐藏的，悬挂在浏览器窗口的左边界之外。通过将其left值动画为0，我们实际上将它从左边完全移入视线之中。同样，通过color插件，我们将其背景颜色由蓝色改为白色。接下来，我们将为动画设置一个时间值。

7.在对象直接量的结束的}之后输入一个逗号，按下回车键并输入500。

这个逗号标志着传递给animate（）函数的第一个参数结束了，而500将动画的长度设置为半秒钟，也就是500毫秒。最后，我们将设置一个缓动方法。

8.在500的后面输入逗号，按下回车键，并且输入'easeInSine'，使你的代码如下所示：

$（document）.ready（function（）{

$（'#dashboard'）.hover（

function（）{

$（this）.animate（

{

left：’0’，

backgroundColor：'rgb（255，255，255）’

}，

500，

'easeInSine'

）；//end animate

}，

function（）{

}

）；//end hover

}）；//end ready

animate（）函数的最后一个参数'easeInSine'告诉该函数使用这样一个缓动方法，它开始较慢，随后加速。

9.保存该文件。在浏览器中预览，并且将鼠标光标放在div上。

这个div应该会迅速进入视线。如果不是这样，使用本书1.6节介绍的技术来调试错误。当然，当鼠标离开div的时候，不会发生什么。我们必须给第二个匿名函数添加animate函数。

10.将如下的代码添加到第二个匿名函数中：

$（this）.animate（

{

left：’-92px'，

backgroundColor：'rgb（110，138，195）’

}，

1500，

'easeOutBounce'

）；//end animate

这段代码将第一个动画的过程反转，将div移回到窗口的左边界之外，并且将背景颜色恢复为蓝色。时间略有不同，1.5秒取代了半秒，缓动方法也不同。

11.保存该文件。在Web浏览器中预览，并且将鼠标光标移动到div之上再移开。

你将会看到div进入视线，然后又离开视线。然而，如果重复且快速地将鼠标光标移来并移走，你会注意到一些奇怪的行为：在你完成鼠标光标移动之后，这个div将保持滑入和滑出视线很长一段时间。这个问题是jQuery对一个元素上的动画进行排队的方式所引起的。

正如本书6.5节所介绍的，给一个元素添加的任何动画，都会加入到针对该元素的一种队列中去。例如，如果让一个元素淡入视线、淡出视线，然后再淡入视线，jQuery依次执行每个效果。

本教程的代码中所发生的事情是，每次当鼠标光标移到div上或移开的时候，都会有一个动画添加到队列中。因此，快速地把鼠标光标在div上移来移去，将会创建一个很长的效果列表供jQuery执行：用动画将div移入视线，移出视线，移入视线，移出视线等。这个问题的解决方法是，在执行一个新的动画之前，停止该div上的所有动画。换句话说，当鼠标光标移动到div之上的时候，div就处于进行动画的过程之中，那么jQuery应该停止当前的动画，并且处理mouseover所请求的动画。幸运的是，jQuery提供了一个stop（）函数，专门针对这个问题。

12.在两个匿名函数中的$（this）和.animate之间添加.stop（）。完成后的代码如下所示（粗体显示为新添加的部分）：

$（document）.ready（function（）{

$（'#dashboard'）.hover（

function（）{

$（this）.stop（）.animate（

{

left：’0’，

backgroundColor：'rgb（255，255，255）’

}，

500，

'easeInSine'

）；//end animate

}，

function（）{

$（this）.stop（）.animate（

{

left：’-92px'，

backgroundColor：'rgb（110，138，195）’

}，

1500，

'easeOutBounce'

）；//end animate

}

）；//end hover

}）；//end ready

这里的.stop（）函数在开始一个新的动画之前就直接结束了div上的任何动画，并且防止多个动画放入到一个队列中。保存该页面，并且在Web浏览器中尝试它。在chapter06文件夹下可以找到本教程的一个完成后的版本，即complete_animate.html。

第三部分：构建网页功能

第7章　改进图像

Web设计师使用图像来改进页面的设计，装饰导航栏，突出显示页面上的元素，并展示他们的上一次度假多么有趣。向Web页面添加图像立即就增添了兴趣和视觉吸引力。当添加了JavaScript来混用，可以通过在页面上动态地改变图像、显示一个动画的照片集，或者在自动运行的幻灯片中显示一系列照片等来增加激动人心的内容。在本章中，我们将学习在Web站点上操作和显示图像的一系列技术。

7.1　交换图像

单个的、最常见的JavaScript的用法可能就是简单的图像翻滚：当把鼠标光标移动到一幅图像上时，它改变为另外一幅图像。这一基本技术从JavaScript诞生就开始用来创建交互式的导航栏，当鼠标光标悬停在导航栏上时，其按钮会改变外观。但是，在过去的几年里，越来越多的设计师倾向于使用CSS来实现同样的效果（例如，参见www.monkeyflash.com/css/image-rollover-navbar/）。然而，即便使用CSS来创建交互式的导航栏，但如果要创建幻灯片、图像集以及为Web页面添加其他类型的交互图像，我们还是需要理解如何使用JavaScript来交换一个图像。

7.1.1　改变图像的src属性

显示在Web页面上的每个图像都有一个src属性（source的缩写），它表示一个图形文件的路径。换句话说，它指向了Web服务器上的一幅图像。如果改变这个属性指向一个不同的图形文件，浏览器将会显示新的图像。假设在页面上有一幅图像并且给它一个ID为photo。使用jQuery，可以动态地修改src属性来指向一幅图像。例如，假设页面上有一幅图像，并且给它分配了一个ID为photo。HTML可能如下所示：

＜img src="images/image.jpg"width="100"height="100"id="photo"＞

要将其换为另一幅图像，只需要使用attr（）函数（参见本书4.9节）来将该标签的src属性设置为一个新的文件，如下所示：

$（'#photo'）.attr（'src'，'images/newImage.jpg'）；

注意：当使用JavaScript改变一幅图像的src属性的时候，图像文件的路径是基于页面的位置的，而不是基于JavaScript代码的位置。当我们使用位于不同目录的一个外部JavaScript文件的时候（参见1.2节），这点可能容易令人混淆。在上面的例子中，Web浏览器将会尝试从一个名为images的文件夹下载newImage.jpg文件，该文件夹和Web页面都位于同一文件夹下。即便代码包含在一个外部文件中，而该文件位于站点其他地方的另一个文件夹中，这种方法也是有效的。因此，在外部JavaScript文件中使用根相对链接常常更容易（参见本书1.2节中的“快速熟悉：URL类型”部分了解关于不同链接类型的更多信息）。

然而，改变图像的src属性并不会改变＜img＞标签的任何其他属性。例如，如果alt属性是在HTML中设置，交换后的图像和最初的图像具有相同的alt文本。此外，如果图像的width和height属性在HTML中设置了，改变图像的src属性使得新图像会放入到和最初图像同样大小的空间中。如果两个图像具有不同的尺寸，那么，交换后的图像会扭曲。

在导航栏中翻滚图像的情况下，两个图像很可能具有相同的大小并且共享同样的alt属性，因此，我们不会遇到问题。但是，可以通过直接在HTML中保留最初图像的width和height属性来完全避免图像扭曲的问题。那么，当新的图像交换进来的时候，Web浏览器会按照文件中的尺寸设置来显示图像。

另外一个解决方案是先下载新的图像，获取其尺寸，然后改变＜img＞标签的src、width、heigth和alt属性：

1 var newPhoto=new Image（）；

2 newPhoto.src='images/newImage.jpg'；

3 var photo=$（'#photo'）；

4 photo.attr（'src'，newPhoto.src）；

5 photo.attr（'width'，newPhoto.width）；

6 photo.attr（'height'，newPhoto.height）；

注意：左边的行号不是代码的一部分，因此，不要录入它们。它们只是为了使得代码容易阅读。

这一技术的关键在于第一行，它创建了一个新的image图像。对于Web浏览器来说，代码new Image（）就是在说“浏览器，我打算给页面添加一幅新的图像，请做准备”。下一行告诉Web浏览器真正地下载新的图像。第3行获取页面上当前图像的引用，第4行到第6行交换新的图像并且改变宽度和高度以适应新的图像。

提示：jQuery attr（）函数可以一次设置多个HTML属性。只要把包含每个属性的名字和新值的一个对象直接量（参见4.8.3节）传递给它就行了。可以用jQuery代码更简洁地重写上面的代码，如下所示：

var newPhoto=new Image（）；

newPhoto.src='images/newImage.jpg'；

$（'#photo'）.attr（{

src：newPhoto.src，

width：newPhoto.width，

height：newPhoto.height

}）；

通常，我们应该将图像交换技术和一个事件处理程序结合起来使用。例如，当访问者的鼠标光标悬停在图像上的时候，我们可以让该图像变为另一幅图像。这种翻滚效果经常用于导航栏，并且，我们将在本书7.1.3节学习如何创建该效果。然而，我们可以更改图像来响应任何事件，例如，每次单击页面上的一个箭头的时候，我们可以让一幅新的照片出现，就像是幻灯片一样。

7.1.2　预载入图像

使用上面介绍的技术来交换一个新的图像存在一个问题：当把新文件的路径交换到src属性中时，浏览器必须下载该图像。如果等到某人把鼠标光标移动到图像上之后才下载新图像，在新图像出现之前，可能会有一段不愉快的延迟。在导航栏的情况下，翻滚效果会令人感到迟缓而响应很慢。

为了避免这一延迟，预先载入任何想要立即出现以作为对操作的响应的图像。例如，当访问者的鼠标光标移动到导航栏上的一个按钮，翻滚图像应该立即出现。预载入一个图像只是意味着强迫浏览器在我们计划显示该图像之前下载它。当图像下载完，它存储在Web浏览器的缓存中，这样后续对该文件的任何请求都由访问者的硬盘驱动器提供，而不需要等待从Web服务器下载的数秒时间。

预载入图像和创建一个新的image对象并设置该对象的src属性一样容易。实际上，我们已经知道如何做到这点：

var newPhoto=new Image（）；

newPhoto.src='images/newImage.jpg'；

所谓预载入，就是在需要替换Web页面上的一个当前图像之前进行。预载入的一种方法是在脚本的开始处创建一个数组，其中包含了要预载入的所有图像的路径，然后遍历这个列表，为每个项目创建一个新的image图像：

1 var preloadImages=['images/roll.png'，

2'images/flower.png'，

3'images/cat.jpg']；

4 var imgs=[]；

5 for（var i=0；i＜preloadImages.length；i++）{6

imgs[i]=new Image（）；7

imgs[i].src=preloadImages[i]；8}

第1行到第3行是一条JavaScript语句，它创建了一个名为preloadImages的数组，包含了3个值，分别是每个要预载入的图形文件的路径（正如2.8.1节所介绍的，如果把每个数组项单独一行放置，往往更容易阅读）。第4行创建了一个新的空数组：imgs，它将存储每个预载入的图像。第5行到第8行给出了一个基本的JavaScript for循环（参见3.3.3节），它对于数组preloadImages中的每个项目都运行一次。第6行创建了一个新的image对象，而第7行获取了来自preloadImages数组的文件路径，这就是导致图像下载的魔术所在。

7.1.3　翻滚图像

翻滚图像只是通过鼠标光标移动到图像上来触发的一个图像交换（参见7.1.1节）。换句话说，直接给mouseover事件分配一个图像交换。例如，假设你在页面上有一个ID为photo的图像。当鼠标光标移动到图像上时，我们希望出现一个新的图像。可以使用jQuery来完成，如下所示：

1＜script src="js/jquery-1.6.3.min.js"＞＜/script＞

2＜script＞

3$（document）.ready（function（）{4

var newPhoto=new Image（）；5 newPhoto.src='images/newImage.jpg'；

6$（'#photo'）.mouseover（function（）{

7$（this）.attr（'src'，newPhoto.src）；

8}）；//end mouseover

9}）；//end ready

10＜/script＞

第3行和第4行预载入了要交换的图像。第5行等待HTML载入，然后，JavaScript可以访问HTML的当前图像。代码剩下的部分给图像分配一个mouseover事件，以及改变图像的src属性以匹配新图像的函数。

既然滚动图像通常会在鼠标光标移开的时候恢复到旧的图像，我们也需要一个mouseout事件来换回图像。正如5.4.2节所介绍的，jQuery提供了自己的事件，叫做hover（），它负责mouseover和mouseout事件：

1＜script src="js/jquery-1.6.3.min.js"＞＜/script＞2＜script＞

3$（document）.ready（function（）{4

var newPhoto=new Image（）；5

newPhoto.src='images/newImage.jpg'；6

var oldSrc=$（'#photo'）.attr（'src'）；7$（'#photo'）.hover（

8 function（）{

9$（this）.attr（'src'，newPhoto.src）；

10}，

11 function（）{

12$（this）.attr（'src'，oldSrc）；

13}）；//end hover

14}）；//end ready

15＜/script＞

hover（）函数接收两个参数：第一个参数是一个函数，告诉浏览器当鼠标光标移动到图像上的时候做些什么；第二个参数也是一个函数，告诉浏览器当鼠标光标从图像上移开的时候做些什么。这段代码还添加了一个变量oldSrc，通过它来记录原始图像的src属性，也就是当页面载入的时候出现的图像的文件路径。

我们不仅限于翻滚图像，还可以为任何标签（包括一个链接、一个表单元素，甚至一个段落）添加一个hover（）函数。通过这种方式，页面上的任何标签都可以触发页面其他位置的一幅图像发生变化。例如，假设当鼠标光标移动到页面的＜h1＞标签上，我们希望更换一幅照片。假设目标图像和前面的例子相同。修改的代码如粗体所示：

1＜script src="js/jquery-1.6.3.min.js"＞＜/script＞2＜script＞

3$（document）.ready（function（）{4

var newPhoto=new Image（）；5

newPhoto.src='images/newImage.jpg'；6 var oldSrc=$（'#photo'）.attr（'src'）；

6 var oldSrc=$（'#photo'）.attr（'src'）；

7$（'h1'）.hover（

8 function（）{

9$（'#photo'）.attr（'src'，newPhoto.src）；

10}，

11 function（）{

12$（'#photo'）.attr（'src'，oldSrc）；

13}）；//end hover

14}）；//end ready

15＜/script＞

7.2　教程：添加翻滚图像

在本教程中，我们将为一系列图像添加翻滚效果（如图7-1所示）。我们将添加程序来预载入翻滚图像，从而消除鼠标光标移动到图像上和看到翻滚图像之间的任何延迟。此外，我们将学习一种新技术，来使得预载入和添加翻滚效果的过程更高效。

图　7-1　通过翻滚使得一个导航栏、链接或一幅简单的图像更具有视觉交互性

7.2.1　任务概览

教程文件rollover.html（位于教程目录chapter07下）包含了一组6幅图像（如图7-2所示）。每幅图像包含一个链接，该链接指向了图像的一个较大的版本，并且所有的图像都包含在一个ID为gallery的＜div＞标签中。基本上，我们要试图做到两件事情：

·预载入和＜div＞中的每幅图像相关的翻滚图像。

·给＜div＞中的每幅图像添加一个hover（）函数。当鼠标光标移动到图像上的时候，hover（）函数交换翻滚图像，当鼠标从图像上移开的时候，交换回最初的图像。

根据上面的介绍，我们可以看到两个步骤都和＜div＞中的图像有关，因此，解决这个问题的方法之一是，首先选择＜div＞中的图像，然后遍历选择，预载入每幅图像的翻滚，并且附加一个hover（）函数。

图　7-2　这个教程的HTML的基本结构包含了一个＜div＞标签，该标签包含了一系列的链接，链接中带有图像。为了使得交换新图像更容易，新图像的文件名只是最初的图像文件名的一个版本

注意：参见1.3节中的“注意”部分，了解如何下载教程文件的相关信息。

7.2.2　编程

1.在文本编辑器中打开chapter07文件夹下的文件rollover.html。

这个文件已经包含了一个到jQuery文件的链接，并且还包含了$（document）.ready（）函数（参见本书4.3节）。第一步是选择＜div＞标签中的所有图像并且用jQuery each（）函数设置一个循环（参见4.10节）。

2.在$（document）.ready（）函数后面的空行单击，并输入$（'#gallery img'）.each（function（）{。

选择器#gallery img选择了ID为gallery的标签中的所有＜img＞标签。jQuery的each（）函数提供了一种快捷方式来遍历一组页面元素，在每个元素上执行一系列的操作。each（）函数接收一个匿名函数（参见4.10.1节）作为参数。提供该完整函数的代码是个好办法，下面我们将这么做。

3.按下回车键两次，然后输入}）；//end each来结束匿名函数，也结束对each（）函数的调用，并且结束这条JavaScript语句。现在代码如下所示：

1＜script src="../_js/jquery-1.6.3.min.js"＞＜/script＞

2＜script＞

3$（document）.ready（function（）{4

$（'#gallery img'）.each（function（）{5

6}）；//end each

7}）；//end ready

此时，这段脚本循环遍历gallery中的每幅图像，但是还没有真正做些事情。首先该做的事情是捕获src属性并且将其存储到一个变量中，以供随后在脚本中使用。

注意：JavaScript注释//end each和//end ready不是这段脚本所必需的。然而，它们使得我们很容易识别脚本每行的作用。

4.单击内部的空行（步骤3中的第5行）并输入

var imgFile=$（this）.attr（'src'）；

正如4.10.2节所述，我们可以使用$（this）来引用循环中的当前元素，换句话说，$（this）将按顺序引用每个图像标签。jQuery的attr（）函数（参见4.9节）获取特定的HTML属性。在这个例子中，它获取图像的src属性并将其存储到一个名为imgFile的变量中。例如，对于第一幅图像，src属性是images/small/blue.jpg，这就是出现在页面上的图像的路径。

可以使用每个src值来预载入图像。

5.按下回车键得到一个空行，然后添加如下所示两行代码：

var preloadImage=new Image（）；

var imgExt=/（\.\w{3，4}$）/；

preloadImage.src=imgFile.replace（imgExt，’_h$1'）；

正如7.1.2节所述，要预载入图像，必须先创建一个image对象。在这个例子中，创建变量preloadImage用来存储该image对象。接下来，通过设置Image对象的src属性来预载入图像。

预载入图像的一种方式（参见7.1.2节的介绍）是创建想要载入的图像的一个数组，然后，遍历数组中的每一项，创建一个image对象并且把图像的来源添加到对象中。然而，这种方法需要很多工作，因为你需要知道到每个翻滚图像的路径，并且将这些路径输入数组。

在这个例子中，我们使用了更多的创意（并且是费力较少的方法）来预载入图像。我们只是必须确保在和最初图像相同的位置存储了翻滚图像，并且名字也相似。对于这个Web页面，页面上的每幅图像都有一幅对应的翻滚图像，翻滚图像的名字的末尾添加了一个_h。例如，对于图像blue.jpg，有一幅名为blue_h.jpg的翻滚图像。两个文件都存储在同一文件夹下，因此，两个文件的路径是相同的。

创新的部分是：可以通过直接改变最初图像的来源的名字以反映翻滚图像的名字，从而让JavaScript搞清楚src，而不是像preloadImage.src='images/small/blue_h.jpg'这样手动输入翻滚图像的src以预载入它。换句话说，如果你知道页面上的图像的路径，那么，其翻滚图像直接在该路径的.jpg前面加一个h。因此，_images/small/blue.jpg变成了_images/small/blue_h.jpg，并且_images/small/orange.jpg变成了_images/small/orange_h.jpg。

这就是其他两行代码所做的事情。第一行var imgExt=/（\.\w{3，4}$）/；创建了一个正则表达式。正则表达式（将在本书14.2节学习）是我们可以在字符串中查找的一个字符模式，例如，连续的3个数字。正则表达式可能有些技巧，但这个正则表达式实际上只是匹配一个字符串末尾的一个点号后面跟着3个或4个字符。例如，它将匹配/images/small/blue.jpeg中的.jpeg和/images/orange.png中的.png。

接下来的一行，preloadImage.src=imgFile.replace（imgExt，’_h$1'）；，使用replace（）方法（参见14.2节）把匹配的文本用其他内容替代。这里，路径名中的一个.jpg将替换为_h.jpg，这样images/small/blue.jpg就改为images/small/blue_h.jpg。由于使用一个正则表达式子模式（参见14.2.6节中“高级用户提示：使用子模式替换文本”部分了解详细内容），这种技术需要一些技巧，因此，如果你对它的工作原理不是很清楚，不要担心，后面还会介绍。

既然已经预载入了翻滚图像，我们就可以给图像分配hover（）事件了。6.按下回车键，然后添加如下第9行到第11行的代码：

1＜script src="../_js/jquery-1.6.3.min.js"＞＜/script＞

2＜script＞

3$（document）.ready（function（）{

4$（'#gallery img'）.each（function（）{5

var imgFile=$（this）.attr（'src'）；6

var preloadImage=new Image（）；7

var imgExt=/（\.\w{3，4}$）/；8

preloadImage.src=imgFile.replace（imgExt，’_h$1'）；

9$（this）.hover（

10 11

）；//end hover

12}）；//end each

13}）；//end ready

jQuery的hover（）函数只是对一个元素应用mouseover和mouseout事件的一种缩写（参见5.1.1节的“hover（）事件”部分）。为了使该函数能够工作，传递给它两个函数作为参数。当鼠标光标移动到元素上的时候，第一个函数运行，在这个例子中，图像变为翻滚图像；当鼠标光标从元素上移开的时候，第二个函数运行，在这个例子中，翻滚图像变回到最初的图像。

7.在空行（步骤6中的第9行后）添加如下的3行代码：

function（）{

$（this）.attr（'src'，preloadImage.src）；

}，

第一个函数直接把当前图像的src属性改为翻滚图像的src属性。最后一行末尾的逗号是必需的，因为我们刚刚添加的函数是充当调用hover（）函数的第一个参数，逗号则分隔开传递给函数的每个参数。

8.最后，添加第二个函数（下面的第13行到第15行）。完成后的脚本如下所示：

1＜script src="../_js/jquery-1.6.3.min.js"＞＜/script＞

2＜script＞

3$（document）.ready（function（）{

4$（'#gallery img'）.each（function（）{

5 var imgFile=$（this）.attr（'src'）；

6 var preloadImage=new Image（）；

7 var imgExt=/（\.\w{3，4}$）/；

8 preloadImage.src=imgFile.replace（imgExt，’_h$1'）；

9$（this）.hover（

10 function（）{

11$（this）.attr（'src'，preloadImage.src）；

12}，

13 function（）{

14$（this）.attr（'src'，imgFile）；

15}

16）；//end hover

17}）；//end each

18}）；//end ready

第二个函数只是把src属性改回到最初的图像。在第5行中，最初出现在页面上的图像的路径存储到了变量imgFile中。在这个函数中（第14行），我们再次获取这个值来把src设置回其最初的值。保存页面，在Web浏览器中查看它，并且把鼠标光标移动到每个黑白的图像上看看它们如何变为全彩的。

7.3　教程：带有效果的照片集

现在，我们要扩展上一个教程来创建一个单页的照片集。当访问者单击缩略图像的时候，我们能够载入一幅较大的图像（如图7-3所示）。此外，我们使用几个jQuery效果函数来实现较大图像之间的转换，从而带来有趣的视觉效果。

7.3.1　任务概览

这个照片集的工作方式相当简单，单击缩略图来查看一幅较大的图像。然而，这个教程通过使用淡化效果将较大的图像换入换出页面，从而展示了如何添加一些功能使得显示更有趣。

图　7-3　完成后的照片集页面。单击缩略图将会让一幅较大的图像淡入视线，并且当前的图像淡出。这个教程文件的完成版本complete_gallery.html位于chapter07目录下

我们将要在这里使用的另一项重要的技术是无干扰的JavaScript。这只是意味着，关闭了JavaScript的用户仍然能够访问图像的较大版本。为了做到这点，每个缩略图都包含在一个链接中，该链接指向较大的图像文件（如图7-4所示）。对于那些没有使用JavaScript的访问者，单击链接会退出当前的Web页面并且按照链接载入较大的图像文件，但是照片至少是可以访问到的。对于那些支持JavaScript的访问者，单击链接将会使得较大的图像在页面上淡入视线。

图　7-4　照片集的基本结构。所有的缩略图都包含在链接中，该链接指向该照片的较大的版本。单击每个链接，将会载入ID为photo的一个＜div＞中的较大图像

所有操作都发生在单击链接的时候，因此，这段脚本使用链接的click事件来实现如下的步骤：

·停止链接的默认行为。通常，单击一幅链接会把我们带到另外一个页面。在这个页面上，单击包围一个缩略图的链接会退出Web页面并且显示一幅较大的图像。既然将要使用JavaScript来显示图像，可以添加一些JavaScript代码阻止浏览器打开该链接。

·获取链接的href值。链接实际上指向较大的图像，因此通过获取链接的href，我们也就获取了较大图像文件的路径。

·创建一个新的图像标签以插入页面中。这个图像标签将包含来自href值的路径。

·淡出旧的图像而淡入新的图像。随着单击的缩略图的较大版本淡入视线，当前的图像淡出视线。

这个教程包含了一些其他细微差别，但是，这4个步骤包含了基本过程。

7.3.2　编程

这个教程扩展了前一个教程，但是，开始的Web页面稍微重新组织了一下：缩略图现在在左边一列，而ID为photo的一个＜div＞标签添加到了页面上（如图7-4所示）。

注意：参见1.3节中的“注意”部分以获取有关如何下载教程文件的信息。

1.在文本编辑器中打开chapter07目录下的文件gallery.html。

这个文件包含了前一个教程的程序，添加了一个新的＜div＞标签，用来显示每个缩略图的较大版本。既然显示照片集图像的过程是通过单击包含了缩略图的一个链接而触发的，那么第一步就是创建这些链接的一个选择并为每个链接添加click事件。

2.找到JavaScript注释“insert new programming below this line”，并且添加如下的代码：

$（'#gallery a'）.click（function（evt）{

}）；//end click

选择器#gallery a选择了ID为gallery的另一个标签中的所有链接标签。.click是添加事件处理程序的一个jQuery函数（如果需要复习一下关于事件的知识，参见5.2节）。此外，这段代码把一个匿名函数传递给了click事件（5.4.3节介绍过，作为对事件的响应而执行的函数，自动地有一个事件对象传递给它）。

在这个例子中，变量evt存储了事件对象。我们将在下面的步骤中使用它来阻止浏览器打开单击的链接。

3.在步骤2中添加的两行代码之间输入evt.preventDefault（）；。

通常，单击一个链接会使得We b浏览器载入链接所指向的内容（We b页面、图像文件、PDF文档等）。在这个例子中，这个链接仍然存在，以便不支持JavaScript的访问者仍然能够访问缩略图的较大版本。为了防止对于那些支持JavaScript的访问者来说Web浏览器打开该链接，运行事件对象的preventDefault（）函数（参见5.4.4节的介绍）。

接下来，我们将获取该链接的href属性。

4.按下回车键创建一个新的空白行，并且输入如下粗体所示的代码：

$（'#gallery a'）.click（function（evt）{

evt.preventDefault（）；

var imgPath=$（this）.attr（'href'）；

}）；//end click

这里的$（this）指向了单击的元素，换句话说，是一个链接。链接的href属性指向了链接所去向的页面或资源。在这个例子中，每个链接包含了较大图像的一个路径。这是重要的信息，因为可以使用它来添加指向图像文件的一个图像标签。但是，在这么做之前，需要得到当前显示在页面上的较大图像的一个引用。毕竟，我们需要知道它是什么，以便能够将其淡出视线。

提示：你将会看到步骤4中的click（）事件中的每行代码都缩进了。这是可选的，但是它有助于使代码更容易阅读，如5.4.2节中的“常见问题：JavaScript中的空格、制表符和回车”部分所述。很多程序员对于每一级缩进使用两个空格（或者一个制表符）。

5.按下回车键并输入var oldImage=$（'#photo img'）；。

变量oldImage包含了一个jQuery选择，其中包含了photo＜div＞中的＜img＞标签（如图7-4所示）。现在，应该为新图像创建一个标签了。

6.再次按下回车键，并且向脚本中添加var newImage=$（'＜img src="’+imgPath+’"＞'）；。

这里进行的事情很少。jQuery允许选择页面的HTML中的一个元素。例如，$（'img'）选择了页面上的所有图像。此外，jQuery对象可以为页面添加一个新的元素。例如，$（'＜p＞Hello'）创建了包含单词Hello的一个新的段落标签。这行创建了一个新的＜img＞标签，并且将其存储在名为newImage的一个变量中。

既然jQuery对象期待一个字符串作为参数（例如’＜p＞Hello＜/p＞'），这行代码连接或组合几个字符串得到一个字符串。第一个字符串（由单引号包围着）是＜img src="；第二个字符串存储在变量imgPath中（在步骤4中创建），并且是到图像文件的路径（例如../_images/large/slide1.jpg）；第三个字符串（也用单引号包围着）是"＞。组合到一起，它们构成了一个HTML标签：＜img src="../_images/large/slide1.jpg"＞。当脚本把该字符串传递给jQuery对象，例如$（'＜img src="../_images/large/slide2.jpg"＞'），浏览器就创建了一个页面元素。它没有在页面上显示，但是浏览器准备好随时将其添加到页面上。

7.添加下面第6行到第8行的代码，于是，目前为止所添加的代码如下所示：

1$（'#gallery a'）.click（function（evt）{

2

evt.preventDefault（）；3

var imgPath=$（this）.attr（'href'）；

4

var oldImage=$（'#photo img'）；5

var newImage=$（'＜img src="’+imgPath+’"＞'）；

6

newImage.hide（）；7

$（'#photo'）.prepend（newImage）；

8

newImage.fadeIn（1000）；

9}）；//end click

在第6行中，新创建的图像（存储在变量newImage中）使用6.1.2节介绍的hide（）函数隐藏。这个步骤是必需的，意味着如果只是添加了在第5行创建的图像标签，图像将会立即在页面上可见，而没有漂亮的淡入效果。因此，首先隐藏图像，然后将其添加到页面的photo＜div＞中（第7行）。prepend（）函数（参见4.7节）把HTML添加到一个标签中。特别是，它把HTML添加到标签的开头。此时，页面上的photo＜div＞内有两幅图像，图7-5展示了一幅图像如何位于另一幅图像之上。上面的图像是不可见的，但是在第8行，fadeIn（）函数使得图像在1000毫秒（1秒）的过程内慢慢淡入。

现在该让最初的图像淡出了。

图　7-5　要实现这样的效果：两幅照片出现在页面上的同一位置，但是，一幅照片淡入而另一幅照片淡出，我们需要使用一些创造性的CSS。绝对定位允许一个元素位于页面之上，甚至是在另一个元素之上。在这个例子中，两幅图像都是在＜d i v＞标签内部绝对定位的，这使得它们中的一个浮动在另一个之上。在chapter07文件夹中的gallery.html文件的＜head＞中所潜入的样式表，拥有所需的所有CSS，确保检查#photo img样式。此外，包含图像的标签需要一个相对定位，以便让图像相对于页面上的该位置来定位

8.按下回车键，并且添加如下所示的3行代码：

oldImage.fadeOut（1000，function（）{

$（this）.remove（）；

}）；//end fadeout

在步骤5中，我们创建了一个名为oldImage的变量，并且把页面上最初图像的一个引用存储在其中。这就是想要淡出的图像，因此，使用fadeOut（）函数。我们给该函数传递了两个参数：第一个参数是效果的持续过程，为1000毫秒（1秒）；第二个参数是一个回调函数（参见6.4节）。这个回调函数在淡出效果完成后运行，并且移除该图像的＜img＞标签。

注意：remove（）函数将在13.4节中介绍。它实际上从DOM移除了标签，这会从浏览器的内存擦除HTML，释放计算机资源。如果没有采取这一步骤，每次访问者单击缩略图，都会添加一个新的＜img＞标签（参见步骤7），旧的图像直接隐藏，而没有删除。我们最终会得到很多隐藏的＜img＞标签，但它们仍然嵌入在Web页面中，这大大降低了Web浏览器的响应速度。

还剩下最后一步，载入第一幅图像。图像应该放入其中的＜div＞标签当前为空。我们可以在该位置输入一个＜img＞标签，这样，当页面载入的时候，针对第一个缩略图，将会有一幅较大的图像。但是，为什么这么麻烦呢？我们已经可以使用JavaScript了。

9.在click（）函数的末尾（下面的第13行）添加最后一行代码。完成后的程序如下所示：

1$（'#gallery a'）.click（function（evt）{

2 evt.preventDefault（）；

3 var imgPath=$（this）.attr（'href'）；

4 var oldImage=$（'#photo img'）；

5 var newImage=$（'＜img src="’+imgPath+’"＞'）；

6 newImage.hide（）；

7$（'#photo'）.prepend（newImage）；

8 newImage.fadeIn（1000）；

9 oldImage.fadeOut（1000，function（）{

10$（this）.remove（）；

11}）；//end fadeout

12}）；//end click

13$（'#gallery a：frst'）.click（）；

最后一条语句有两部分。第一部分是选择器：#gallery a：first，选择位于gallery＜div＞中的第一个链接。第二部分是click（）函数。到目前为止，我们已经使用了jQuery的click（）分配了当事件发生时运行的一个函数。然而，如果没有给事件函数传递任何参数，jQuery只是触发事件，引起任何之前定义的事件处理程序运行。因此。这一行代码在第一个链接上触发了一次单击，使得Web浏览器运行我们在前面第1行到第11行所创建的函数。这就使得在页面载入后，第一个缩略图的较大图像淡入视线。

保存页面并且在Web浏览器中预览。当鼠标光标移动到缩略图上的时候，它不仅改变了颜色，单击一个缩略图还会使得相关的较大图像淡入视线（如果代码遇到麻烦，文件complete_gallery.html包含了这段脚本的一个可工作的副本）。

7.4　使用jQuery lightBox的高级照片集

显示照片集是如此常见的一个任务，以至于可以看到多种不同的方法来展示你的想象力。一种非常流行的技术是，淡化Web页面并显示缩略图的较大版本，就好像它们是漂浮在浏览器窗口之上（如图7-6所示）。这种方法的最著名版本就是一个叫做Lightbox（http：//lokeshd hakar.com/projects/lightbox2/）的JavaScript程序。最初的程序有了很多版本，还有几个编写成了jQuery插件。本节使用的是强大的FancyBox jQuery插件（http：//fancybox.net/）。FancyBox创建了一种引人入胜的方式来把图像显示为一个文件夹、照片集或幻灯片的一部分，而这只需要使用一行代码。

注意：在继续介绍之前，你可能想要打开包含在本书教程文件的chapter07目录下的文件complete_fancybox.html。它是FancyBox的一个演示版本。先看看其效果，这可能会使得本节剩下的内容更容易理解。

图　7-6　由Janis Skarnelis开发的FancyBox jQuery插件给出了一种非常容易的方式，用来创建一个吸引人的单页照片集。当把鼠标光标放到图像上的时候，可以通过单击此时出现的Next或Previous按钮在一系列照片中导航。此外，还不仅仅限于照片。我们可以使用FancyBox弹出打开YouTube视频，或者嵌入常规的HTML内容（来自其他站点的一般内容）

7.4.1　基础

FancyBox非常容易使用，只需要建立好Web页面，其中带有到想要显示的图像的链接，给页面附加.css和.js文件，并且添加一行代码来启动light box。

1.设置照片集页面。

确实没有太多事情要去做，只是添加希望显示在页面上的较大图像的链接。这应该是添加到缩略图的链接，以便在单击缩略图的时候，会出现较大的图像（我们在前面的教程中编写的照片集就是这么工作的）。重要的是，要记住，链接指向一个图像文件，即.png、.jpeg或者.gif文件，而不是指向一个Web页面（在本书8.4节，你将学习如何使用FancyBox来显示诸如其他Web页面以及Flash电影视频这样的非图像内容）。

此外，需要一种方式来表示这仅仅是照片集链接（相对于页面上的其他链接而言）。一种方式是，把链接包含到带有特定ID（例如gallery）的一个＜div＞标签中。然后，我用’#gallery a'的一个选择器就可以找到那些链接。另一种方式是，为每个照片集链接指定一个特定的类名，例如＜a href="images/potato.jpg"class="gallery"＞。那么，可以使用'a.gallery'这样的一个选择器找到这些链接。如果链接散布在页面的各处而不是包含在单个的＜div＞中的，后一种方式更方便。

提示：要为照片添加标题，只要给那些链接到较大图片的＜a＞标签添加一个title属性即可。例如：

＜a href="images/potato.jpg"title="A handsome potato”＞

2.下载FancyBox文件并将它们放入到站点。

可以在http：//fancybox.net找到这些文件。本书的教程文件中也提供了这些文件，它们位于chapter07下的fancybox目录中。一个好办法是，直接将这个fancybox文件夹复制到你自己的Web站点中（根目录是个不错的选择）。这里有需要的几个文件：一个JavaScript文件、一个CSS文件和几个图像文件。

·JavaScript文件的名字类似于jquery.fancybox-1.3.4.js，这里1.3.4表示版本号。在chapter07教程目录中，可以找到文件名为jquery.fancybox-1.3.4.min.js的一个精简版（或压缩版）。可以把这个文件放在站点的任何地方，但是，最简单的方式是将其和其他文件一起放在fancybox文件夹中。

·CSS文件jquery.fancybox-1.3.4.css，包含了用于格式化FancyBox效果的各个部分的CSS，例如阴影效果、Next、Previous和Close按钮，以及标题的外观。由于这个文件引用很多不同的图像文件（fancybox.png、fancy_title_main.png等），将CSS文件存放在和其他图像相同的目录下，这是最好的

·做法（这也是最好将所有和FancyBox相关的文件一起放在同一目录中的原因）。FancyBox还需要一系列的图像文件：这些文件中的一些专门用于克服IE 6的bug，而另一些则用来添加诸如阴影、用于标题的图形框以及用于导航按钮的图像等视觉元素。我们将在本书7.4.3节了解不同文件的更多信息。

3.给页面附加外部样式表。

jQuery FancyBox使用一些有趣的CSS来实现黑色、透明覆盖效果以及显示弹出式图像。可以像任何CSS文件一样附加这些文件。例如：

＜link href="fancybox/jquery.fancybox-1.3.4.css"rel="stylesheet"＞

大多数JavaScript程序员在进行JavaScript编程之前放置好了所有的样式表信息，一些JavaScript程序依赖于首先可用样式表信息，这样程序才能正确工作。对于很多jQuery插件来说，尤其是这样，因此，养成习惯在放置JavaScript文件和程序之间放置好所有的样式表。

4.添加JavaScript文件。

jQueryFancyBox的大部分功能来自于jQuery库（毫不奇怪），因此，必须首先给页面添加jQuery文件（参见4.3节）。此外，FancyBox JavaScript文件（像任何使用jQuery的JavaScript一样）必须添加在jQuery文件之后。例如：

＜script src="js/jquery-1.6.3.min.js"＞＜/script＞

＜script src="fancybox/jquery.fancybox-1.3.4.js"＞＜/script＞

此外，FancyBox还使用了本书6.3节介绍的缓动方法，用来控制较大的图像如何进入或退出页面。如果你想要使用jQuery的标准的swing和linear之外的任何缓动方法，那么，必须添加easing插件。例如：

＜script src="js/jquery-1.6.3.min.js"＞＜/script＞

＜script src="js/jquery.easing.1.3.js"＞＜/script＞

＜script src="fancybox/jquery.fancybox-1.3.4.js"＞＜/script＞

5.添加一个＜script＞标签，然后是jQuery ready（）函数，并且调用FancyBox.。

不管你是否相信，上面的步骤1到步骤4是整个过程的最难的部分。让FancyBox工作只需要一行JavaScript代码。当然，正如5.4.1节所介绍的，我们应该把代码放置到一个jQuery ready（）函数中，这样，浏览器就已经处理了HTML并准备好操作DOM。例如：

＜script＞

$（document）.ready（function（）{

$（'#gallery a'）.fancybox（）；

}）；

＜/script＞

fancybox（）函数必须只是应用于那些指向要显示的图像的链接。我们使用一个jQuery选择器（例如$（'#gallery a'））来告诉FancyBox用于哪个链接：在这个例子中，位于ID为gallery的标签中的任何＜a＞标签都会变为FancyBox效果的一部分。正如步骤1中所提到的，我们需要设置HTML以便可以使用jQuery来找到组成lightbox的特定链接。

就是这样。现在，当单击每个照片集链接时，页面上都会出现一个透明的背景，并且图像的一个较大版本会出现在窗口的中央。

7.4.2　创建一个图像集

通常，当我们要把FancyBox应用于一组链接的时候，每个缩略图都当做一幅单独的、不相关的图像来对待。换句话说，如果将FancyBox应用于一组链接，以查看每幅图像的较大版本，访问者首先必须单击一个链接以打开较大的图像，通过单击一个关闭按钮（或者页面中的任何位置）来关闭这幅较大的图像，然后单击另一个不同的缩略图来查看一幅较大的图像，诸如此类。这一个常用的打开和关闭图像的过程，可能很烦人并且很费时间。

一种更好的方式是，将一组缩略图和链接当做一个统一的图像集来对待。访问者单击一个缩略图来打开该图像的一个较大的版本，然后，单击较大的图像上的Next或Previous按钮，在图像集中其他的较大图像之间来回跳转。图7-6所示的FancyBox示例就是以这种方式工作的，注意Next按钮（圆圈圈出来了）。

要创建相关图像的一个图集，直接给＜a＞标签添加rel属性，并且给图像集中的每个图像的rel属性分配同样的值。例如：

＜a href="large_slide1.jpg"rel="gallery"＞

＜img src="slide1.jpg"width="70"height="70"＞

＜/a＞

＜a href="large_slide2.jpg"rel="gallery"＞

＜img src="slide2.jpg"width="70"height="70"＞

＜/a＞

＜a href="large_slide3.jpg"rel="gallery"＞

＜img src="slide3.jpg"width="70"height="70"＞

＜/a＞

有了上面的代码，如果对这3个链接应用FancyBox函数，那么，这些较大的图像会被看做同一图像集的组成部分，从而使你可以在图像之间来回跳转，而不需要先关闭一幅较大的图像然后再单击一个缩略图（你将会在本书7.5节的教程中看到这样的一个示例）。

7.4.3　定制FancyBox

尽管FancyBox效果的外观确实不错，但是我们可能还是想用自己的显示外观进行一些修改。可以定制FancyBox外观的各个不同部分，包括用来关闭FancyBox窗口或导航到前一张图像或下一张图像的按钮；也可以修改覆盖页面的透明背景的颜色和透明度，或者改变图片框和标题框的背景颜色。一些改变涉及给FancyBox函数提供不同的选项，而另一些改变则需要你直接对CSS文件做出修改。

FancyBox选项

FancyBox插件允许提供定制选项，以影响light box效果的外观。基本上，我们给FancyBox函数传递一个对象直接量（参见4.8.3节），其中包含了希望设置的选项的名字和想要为其设置的值。例如，要改变放置在页面之上的背景的颜色和透明度，可以创建一个包含了新的设置的变量，并将其传递给FancyBox，如下所示：

$（'#gallery a'）.fancybox（{

overlayOpacity：.5，

overlayColor：’#F64’，

transitionIn：'elastic'，

transitionOut：'elastic'

}）；

在这个例子中，覆盖层的颜色设置为鲜红色（#F64），其透明度设置为50%（.5）。此外，transitionIn和transitionOut设置为elastic，这会影响到大图像如何出现在屏幕上。在这个例子中，elastic设置使得单击缩略图的时候较大图像出现在屏幕上，并且单击Close按钮（或者单击页面上的任何其他地方）的时候，图像从屏幕上消失（通常，较大的图像只是出现和消失，而没有任何动画效果）。

jQuery FancyBox接收很多不同的选项（参见http：//fancybox.net/api/的完整列表），这里只是介绍一些最有用的：

·overlayColor。在FancyBox显示图像的时候覆盖页面的背景颜色。这个选项接受一个十六进制的颜色值，例如#FF0033，如果没有设置这个选项，插件将会使用灰色（#666）。像下面这样设置这个选项：

overlayColor：’#ff6346’

·overlayOpacity。覆盖的透明度。这个选项设置了能够通过覆盖看到下面的页面的数量。我们指定了0到1之间的一个数值：例如，.5就是50%的透明度。如果不希望能够透过覆盖看到内容，例如，希望在图像显示的时候，Web页面的其他部分完全是黑色的，可以把这个选项设置为1。如果没有设置这个选项，FancyBox会把透明度设置为30%（.3）。将这个值设置为0，则会隐藏覆盖。

overlayOpacity：.5

·Padding。围绕着图像的空间，它创建了围绕着图像的一个可见的边框。通常，FancyBox将padding设置为10像素，但是，你可以将其更改为想要的任何值。0会完全去除弹出的图像的边框（参见本书7.5节，了解如何更改围绕着图像的边框的颜色）。直接提供一个数字（FancyBox假设这是像素值，因此，你不需要像通常在CSS中那样加上px）：

padding：5

·changeSpeed。当你在支持FancyBox的页面中从一幅图像移动到另一幅图像的时候，包含图像的边框会进行动画，它随着当前图像的尺寸而改变大小，以便与下一幅图像相匹配。你可以通过设置这一选项来控制这种变化的速度。默认值是300，表示300毫秒，或者说是略短于半秒。例如：

changeSpeed：500。

·transitionIn和transitionOut。这两个选项控制了较大的、弹出式图像如何出现在屏幕上。fade设置使得较大的图像从屏幕上淡入视线。这也是FancyBox通常使得一个弹出式图像显示的方式。none设置使得图像突然弹出到屏幕上。最后，elastic选项是视觉上最有趣的：它使得图像放到呈现在页面上。和缓动方法一起使用（参见下一小节），我们可以创建一些非常动态（并且可能有些恼人的）视觉效果。transitionIn选项控制较大的图像如何出现在屏幕上，而transitionOut选项控制图像如何消失。我们可以对二者进行不同的设置：

transitionIn：'elastic'，

transitionOut：'none'

·easingIn和easingOut。当transitionIn和transitionOut选项设置为elastic的时候，这两个选项有效（参见前面的列表项的介绍）。如果已经添加了easing插件（参见本书6.3节），它们接收诸如jQuery的swing或linear选项这样的一个缓动方法，你可以使用该插件所提供的任何缓动方法：

easingIn：'easeInBounce'，

easingOut：'easeOutSine'

·titlePosition。通常，当你给图像添加一个标题的时候（参见本书7.5节的注意），FancyBox将标题放在一个图形框的内部（一幅菱形的图像）。你可以更改这幅图像（参见本书7.4.3节），或者通过将titlePosition选项设置为outside（标题出现在包含图像的框之下）、inside（标题出现在包含图像的框之中）或者over（标题出现在包含图像的下边缘上）而直接不使用它。可以像下面这样设置这个选项：

titlePosition：'outside'

·Cyclic。通常，当你到达一个图像集的最后一幅图像的时候，只显示Previous按钮；并且，当你位于第一幅图像的时候，只显示一个Next按钮。当你设置将cyclic属性设置为true，即cyclic：true，访问者到达了图像集的最后一幅图像的时候，就可以按下Next按钮回到第一幅图像，并且，当浏览第一幅图像的时候，可以按下Previous按钮到达最后一幅图像。换句话说，这个选项允许你继续循环遍历图像而不会终结。

cyclic：true

下面是如何设置这些选项的一个例子。假设你将Fancy-Box应用于一组链接，以创建可以导航的弹出式图像的一个集合。你不想让图像集终结，因此，当访问者到达最后一幅照片的时候，她按下Next按钮跳到图像集的开始。此外，你想要删除围绕着弹出式照片的任何边框，使得它们放大或缩小，并且在每张照片的下面放置标题。你可以使用如下代码来做到这些：

$（'#gallery a'）.fancybox（{

cyclic：true，

padding：0，

transitionIn：'elastic'，

transitionOut：'elastic'，

titlePosition：'outside'

}）；

样式化FancyBox

FancyBox使用图像和CSS的组合来样式化其外观。它为Close按钮、导航按钮使用图像，并且在弹出式图像的下方显示下拉式阴影，同时用CSS样式来格式化这些空间的位置和大小。在访问较大的图像的时候，会动画地载入图像。你也可以调整这些图像，但是，首先需要理解FancyBox是如何组织其图像文件的。

首先，对于IE 6以外的所有浏览器，主图像都存储在一个单个的文件fancybox.png中（参见图7-7）。FancyBox使用一种叫做CSS精灵的方法，它将多幅图像存储在一个单个的文件中，但是，使用CSS和back-ground-image属性，在每个实例中只是显示该图像的一部分。例如，要显示Close按钮，FancyBox将fancybox.png作为一个背景图像载入到页面的一个链接中，但是，将该链接的width和height设置为30像素，并且，使用background-position属性来移动图像，以便只显示在fancybox.png文件的左上角。这种方法背后的原理是，只载入一个文件fancybox.png，而不是针对每个控件载入不同的文件，你的Web站点将会下载得更快，并且对于访问者来说响应更为积极（你可以在http：//css-tricks.com/158-css-sprites/获得关于CSS精灵技术的更多内容）。

除了图像外，FancyBox还在其外部样式表中包含了样式，用于格式化它所展示的不同元素。这里列出了最常见的元素，以及如何格式化它们：

·Close按钮。使用一个图形化的Close按钮，它出现在弹出图像的右上角。该图像是30像素宽，30像素高，并且位于fancybox.png文件的右上角。还有几种方式来修改它。如果你不喜欢30像素×30像素的大小，可以直接编辑fancybox.png文件：删除该文件右上角的30×30的图像，并且用你自己的设计来替换它。

在FancyBox样式表中（教程文件中的jquery.fancybox-1.3.4.css文件），你可以编辑#fancybox-close样式以获得更好的控件。在这个样式中，你可以控制Close按钮的位置（更改top和right设置），更改按钮的高度和宽度，甚至通过编辑background属性来指向一个不同的图像文件。

IE 6注意：FancyBox使用专门的CSS样式来克服IE 6中的bug。这个样式不会引用fancybox.png文件，而是为每个控件使用不同的图像。IE 6几乎是要消失的Web浏览器，因此，你很可能安全地忽略它，但是，如果你还是担心，并且想要更改控件图像的外观，那么，你需要编辑如下的图像文件：fancy_close.png（关闭按钮）、fancy_nav_left.png（前一个按钮）、fancy_nav_right.png（下一个按钮）、fancy_loading.png（动画加载过程中的图像）、fancy_title_main.png（用于标题图像的中央部分）、fancy_title_right.png（用于标题图像的右边部分）以及fancy_title_left.png（用于标题图像的左边部分）。或者，你可以完全免去这些麻烦，干脆忽略IE 6。

·Previous和Next按钮。和Close按钮一样，Previous和Next按钮都是fancybox.png文件中的图像（参见图7-7）。你可以编辑该文件（确保位于30×30像素的边界之中）。你也可以通过编辑这两个控件的样式，来使用不同的图像文件和不同的大小尺寸：一组选择器（#fancybox-left-ico、#fancybox-right-ico）提供了两个控件通用的属性（如宽度和高度），单个的样式（#fancybox-left-ico用于Previous按钮，而#fancybox-right-ico用于Next按钮）为这些控件设置图像。

·“加载中”图像。当访问者单击一个缩略图以使得一幅较大的图像弹出到页面上的时候，浏览器必须下载该文件。在等待的时候，一幅动画的图像会出现在屏幕上，表示正在访问该图像。这个图像实际上是fancybox.png文件中的一系列图像帧（参见图7-7）。其12个图像帧都是40像素高和40像素宽。要更改这个图像，我们需要编辑fancybox.png文件，并创建自己的12帧动画。CSS样式控制加载哪个图像，并且，其宽度是#fancybox-loading div样式。但是，如果你想要控制图像的位置、宽度和高度，还需要编辑#fancybox-loading样式。这有很多工作，并且，由于常规的“加载中”图像看上去很不错，你可能不想要修改它。

图　7-7　不需要使用FancyBox的选项设置来定制化FancyBox的外观。直接替换提供的图像，并且替换CSS文件，以允许你将设计调整得更加符合你的站点

·下拉阴影。围绕着弹出式图像出现的下拉阴影，是由一系列单独的图像文件所产生的，包括fancybox.png文件的几个部分。编辑这些图像很复杂，最好避免这么做。如果你确实为这里所介绍的不同空间使用不同的图像文件，并且编辑针对这些文件的CSS，请不要删除fancybox.png文件，它为下拉式阴影提供备用方案。

·围绕弹出式图像的边框颜色。通常，FancyBox显示了围绕着弹出式图像的一个白色边框。正如前面的小节所介绍的，你可以通过给FancyBox函数发送一个补白值来控制这个边框的大小。然而，要控制其颜色，你需要编辑FancyBox样式表。找到样式#fancybox-content，并且将其边框颜色从#FFF修改为你想要的任何颜色。

·图像背后的颜色。当想要创建一种图像集效果，其中，访问者可以单击向前和向后按钮，从一幅较大的图像跳转到另一幅较大的图像，当一幅图像淡出而另一幅图像淡入的时候，Fancy-Box显示了一种背景颜色。这种颜色由#fancybox-outer样式中的background属性来定义。将这个值设置为与你为边框颜色（上一个列表介绍）相匹配的任何颜色，这是一个好主意。

·Captions。我们可以通过编辑各种CSS样式来控制标题的颜色、字体和大小。#fancybox-title样式定义了标题的字体和字体大小，而.fancybox-title-inside、.fancybox-title-outside和.fancybox-title-over样式，根据其titlePosition设置影响到标题的外观（本书7.4.3节介绍）。例如，如果你将“titlePosition”选项设置为inside，标题将出现在图像的边框之中。如果你修改了边框颜色（参见前面的介绍），那么，你应该将.fancybox-title-inside样式上的background属性更改为和边框颜色相匹配。如果没有设置titlePosition选项，FancyBox使用fancybox.png文件中的图像来产生一个图形化的标题框。它将该图像划分为3部分（第一个用于该框的左边缘，第二个用于该框的中间部分，第三个用于该框的右边缘）。要修改所使用的图像，你可以编辑这样的样式：#fancybox-title-float-left、#fancybox-title-float-main和#fancybox-title-float-right。

提示：FancyBox允许我们创建一个定制的函数，从而以多种不同的方式来定制用于显示标题的HTML。在http：//fancybox.net/blog可以看到如何做到这点，还可以找到关于使用FancyBox的更多提示和技巧。

7.5　教程：FancyBox照片集

尽管FancyBox确实很容易使用，但通过一个分步骤的教程来展示如何使用它总是有帮助的。在这个教程中，我们将把带有一组基本的缩略图的页面转换为一个有趣的弹出照片集。

注意：参见1.3节中“注意”部分以获取如何下载教程文件的信息。

1.在文本编辑器中打开chapter07目录下的文件fancybox.html。

这个文件和本章第一个教程开始时使用的文件相同，这是一组简单的缩略图。每个图像都链接到照片的一个较大版本，并且所有的缩略图都包含在ID为gallery的一个＜div＞标签中。

第一步是添加FancyBox所使用的CSS文件。

2.在文档的＜head＞部分找到＜link＞标签下的空行，该标签附加site.css样式文件（空行正好出现在第一个＜script＞标签的上方）。在这一行输入：

＜link href="fancybox/jquery.fancybox-1.3.4.css"rel="stylesheet"＞

jquery. fancybox-1.3.4.css文件包含了用来格式化FancyBox界面的各种元素的所有样式，包括关闭按钮的放置位置，照片标题文本的外观。接下来，我们需要附加该插件的JavaScript文件。注意，该页面已经添加了jQuery和jQuery缓动（参见本书6.3节）文件。

3.在添加了jquery.easing.1.3.js文件的＜script＞标签之后的空行里输入：

＜script src="fancybox/jquery.fancybox-1.3.4.min.js"＞＜/script＞

这个JavaScritp文件放在chapter07目录下的一个名为fancybox的文件夹中。由于FancyBox需要一组文件，包括JavaScript、CSS和图像，最简单的做法是将它们都放在同一个位置。这样，你只需要将本教程的fancybox文件夹复制到自己的站点，就可以使用FancyBox了。

这个页面已经有了另一个＜script＞标签，带有jQuery ready（）函数以及本章第一个教程中创建的预载入/翻滚魔术。只需要添加fancybox（）函数就够了。

4.单击$（document）.ready（function（）下面的空行并输入：

$（'#gallery a'）.fancybox（）；

所有指向较大图像的链接都包含在了ID为gallery的一个＜div＞标签中，因此，$（'#gallery a'）选择了它们，并且.lightBox（）函数对页面应用了FancyBox效果。

不管你是否相信，已经完成了。保存页面并在Web浏览器中预览。单击一个缩略图来看看发生了什么魔术。现在你明白为什么插件有用了吧，我们不需要进行任何编程就能够得到神奇的效果。

单击Close按钮或者页面上的任何其他地方，都会使得较大的图像消失。单击另一个缩略图，可以看到另外一个较大的图像。这种设置的一个问题是，每个缩略图都是彼此独立的。换句话说，为了看到另一个弹出的图像，你需要首先关闭当前的弹出图像，然后，单击一个缩略图。如果每次出现一个弹出图像，这会更好些，你只需要从一个较大的图像导航到另一个图像。简单地修改HTML就可以做到这些。

5.找到gallery＜div＞中的第一个链接：＜a href="../_images/large/slide1.jpg"＞，并且添加rel="gallery"，使得HTML如下所示：

＜a href="../_images/large/slide1.jpg"rel="gallery"＞

你提供给rel属性的实际值是无关紧要的（在这个例子中是gallery）。重要的是，对于你想要包含在组中的每幅图像来说，这个值是相同的。

6.为gallery div中剩下的5个链接，每一个都添加rel="gallery"。保存并预览该页面。

现在，在单击一个缩略图之后，你可以将鼠标悬停在弹出的图像的右边，此时可以看到一个Next按钮。单击该按钮，将会跳转到组中的下一张照片。将鼠标悬停在左边，将会看到Previous按钮。

所缺的是每张照片的标题。要添加一个标题，不需要任何JavaScript，只需要给每个＜a＞标签添加一个HTML title属性。

7.再次找到第一个链接，即＜a href="../_images/large/slide1.jpg"rel="gallery"＞，并且给该标签添加title="Lotsa golf balls"，使其如下所示：

＜a href="../_images/large/slide1. jpg"rel=“gallery"title="Lotsa golf balls"＞保存文件并在We b浏览器中预览。单击左边的第一个缩略图，看，标题出现了。给这个＜div＞中的其他＜a＞标签页添加title属性。现在，可以调整FancyBox的一些默认设置来定制其外观了。

8.通过在fancybox（）函数的圆括号中添加一个对象直接量，从而修改在步骤4中添加的代码。新的代码如下所示：

$（'#gallery a'）.fancybox（{

overlayColor：’#060’，

overlayOpacity：.3

}）；

这段代码给fancybox（）函数传递了一个对象直接量（参见4.8.3节）。对象直接量由三部分构成：首先是一个属性名，然后跟着一个冒号，最后是一个值。因此，overlayColor是FancyBox的一个选项的名称（参见7.4.3节），并且我们将其值设置为’#060’。这个特定的选项修改了弹出图像和页面之间的背景颜色。overlayOpacity设置控制了覆盖的透明度，在这个例子中，.3表示30%的透明度。

注意：对象直接量看上去有些奇怪，并且有一些奇怪的规则。确保在每个属性/值对的后面添加一个逗号，除了最后一个属性/值对以外。例如，上面的最后一行overlayOpacity：.3的末尾必须没有逗号。然而，如果你要添加更多的选项（就像在步骤10中所做的那样），则必须添加一个逗号。可以在4.8.3节看到关于对象直接量的更多信息。

9.保存页面并在Web浏览器中预览。

现在，当你单击一个缩略图的时候，会有一个绿色部分覆盖在页面上。接下来将要调整弹出图像如何在屏幕上弹出。

10.再次修改代码，添加一些额外的选项。新的代码如下面的粗体所示：

$（'#gallery a'）.fancybox（{

overlayColor：’#060’，

overlayOpacity：.3，

transitionIn：'elastic'，

transitionOut：'elastic'，

easingIn：'easeInSine'，

easingOut：'easeOutSine'，

titlePosition：'outside'，

cyclic：true

}）；

别忘了，在overlayOpacit y：.3的末尾添加逗号。当你将t ran si tio nI n和transitionOut设置为elastic的时候，当你单击一个缩略图的时候，弹出图像会出现在页面上，当你关闭弹出的图像时，它会从页面上消失。easingIn和easingOut选项使用了jQueryeasing插件（参见本书6.3节）来控制变换动画如何展开和恢复。最后，cyclic选项使得访问者一旦到达了组中的最后一张弹出图像，可以单击Next按钮循环回到组中的第一张图像。

11.保存页面并在Web浏览器中预览。

谁说JavaScript很难。文件complete_fancybox.html是本教程的完整的、可工作的版本。

FancyBox是很有趣和有用的插件。正如你将在下一章中看到的，它不仅限于显示图像。我们也可以使用它在页面的一个弹出窗口中显示HTML、视频或者整个Web页面。

第8章　改进导航

链接使得Web能够走动。如果不能够访问页面到页面和站点到站点的链接所提供的信息，Web无法走远。实际上，它根本无法成为Web了。既然是HTML的最常见和强大的部分之一，那么，自然有很多的JavaScript技术来增强链接的工作。在本章中，我们将学习使用JavaScript控制链接的基本知识，以及如何在新的窗口中和在页面的窗口中打开链接。

8.1　一些链接基础知识

毫无疑问，我们已经知道了关于链接的很多知识。毕竟，它们是Web的核心，并且普通的＜a＞标签是一位Web设计师学习HTML的第一部分。给页面添加JavaScript可以把一个基本的链接转变为通往超级交互能力的门户，但是，你必须知道如何使用JavaScript来控制链接。一旦有了这些基础知识，本章稍后将介绍一些使用JavaScript控制链接的实用技术。

8.1.1　用JavaScript选择链接

要对Web页面上的链接做些事情，首先必须选择它。我们可以选择一个页面上的所有链接，也可以仅仅选择一个链接或者是特定的一组相关链接，例如，在页面的同一部分组织到一起的链接；或者具有同样特征的链接，例如指向其他Web站点的外部链接。

在选择文档元素方面，jQuery给了我们很大的灵活性。例如，代码$（'a'）创建了选取页面上所有链接的一个jQuery选择。此外，jQuery允许对选择进行细化，因此，我们可以快速选择页面的一个特定区域内的所有链接。例如，可以像$（'#mainNav a'）这样来选择包含在一个ID为mainNav的符号列表中的所有链接。同样，可以使用属性选择器（参见本书4.6.2节，来选择HREF值（它们所指向的文件的路径）匹配某个模式的链接，例如指向其他站点的链接，或者指向PDF文件的链接（参见本书8.2节中的示例）。

一旦使用jQuery选择那些链接之后，可以使用jQuery函数来操作这些链接。例如，可以使用each（）函数来遍历每个链接（参见2.10节），使用addClass（）函数对这些链接应用一个类（参见2.8.1节），或给它们添加事件函数（参见5.1.4节）。在本章稍后内容中，我们将会看到可以对链接做的事情的很多示例。

8.1.2　确定链接的目标

在选择了一个或多个链接之后，我们可能会对它们链接到哪里感兴趣。例如，在7.4节构建的一个幻灯片中，每个链接都指向一个较大的图像。通过获取路径，我们可以使用JavaScript来显示较大的图像。换句话说，我们提取了链接的href值并且使用该路径在页面上创建一个新的＜img＞标签。同样，可以获取链接到另一个Web页面的href值，并且可以真正地在当前页面上显示新的Web页面，而不是当单击链接的时候去往该页面（具体步骤参见8.4节）。

在每种情况中，我们都需要访问href属性，这是使用jQuery的attr（）函数的一个轻松过程（参见4.9节）。例如，假设对回到站点主页的链接应用了一个ID。可以像下面这样获取该链接的路径：

var homePath=$（'#homeLink'）.attr（'href'）；

我们会在很多示例中使用这个信息。例如，假设想要紧挨着链接文本本身添加指向站点外的一个链接的完整的URL。换句话说，假设有一个带有文本“Learn more about bark beetles”的链接，而这个链接指向http：//www.barkbeetles.org/。现在，假设想要把页面上的文本改为“Learn more about bark beetles（www.barkbeetles.org）”（那么当人们打印该页面的时候，将知道该链接指向何处）。

使用如下的JavaScript代码，可以很容易地做到这点：

1$（'a[href^="http：//"]'）.each（function（）{

2 var href=$（this）.attr（'href'）；

3 href=href.replace（'http：//’，’'）；

4$（this）.after（'（'+href+'）'）；

5}）；

注意：左边的行号不是代码的一部分，因此，不要录入它们。它们只是为了方便我们一行行地查看代码。

第1行选择了所有的外部链接（参见4.9节），然后运行each（）函数（参见4.10节），它只是遍历每个链接并对其应用一个函数（也就是说，它遍历了链接的列表）。在这个例子中，第2行到第4行组成了函数的正文。第2行获取了该链接的href（例如，http：//www.barkbeetles.org）。第3行是可选的，它只是通过删除http：//简化了URL以便于显示，因此，href变量现在保存了像www.barkbeetles.org这样的一些内容（关于JavaScript的replace（）函数参见14.2.6节）。最后，第4行在链接的后面添加了herf的内容（括号包围的部分）：（www.barkbeetles.org），第5行结束了函数。

我们甚至可以更进一步地使用这些基本过程，用来在页面的底部创建一个参考文献，列文章中提到的所有的链接。我们在页面底部的一个单独的div中列出每个Web地址，而不是在每个链接的后面添加一个Web地址。

8.1.3　不要打开链接

当给链接添加一个click事件，我们可能不希望Web浏览器按照其常规模式退出当前页面并载入链接的目的地。例如，在7.3节的照片集中，当单击了一个缩略图上的链接时，页面会载入一幅较大的图像。通常，单击链接会退出页面并在一个空白页面上显示图像本身的较大版本。然而，在这个例子中，停留在同一页面，较大的图像在这里载入，而不是按照链接打开图像。

有几种方法可以阻止链接按照其惯例进行工作，可以返回一个false值或使用jQuery的preventDefault（）函数（参见5.4.5节）。例如，假设有一个链接把访问者带到一个登录页面。为了让站点给人感觉更具有响应性，当访问者单击该链接的时候，我们希望使用JavaScript来显示一个登录页面。换句话说，如果访问者的链接支持JavaScript，当他单击了该链接，该页面上将会出现一个表单；如果浏览器关闭了JavaScript，单击链接将会把访问者带到一个登录页面。

实现这一目标有几个步骤：

1.选择登录链接。

如果需要了解如何做到这些，请参见本节前面的内容。

2.附加一个click事件。

可以使用jQuery的click（）函数来做到这点。click（）函数接收另一个函数作为参数。这个函数包含了当用户单击链接时发生的步骤。在这个例子中，只需要两个步骤。

3.显示登录表单。

当页面载入的时候，登录表单应该隐藏不可见，可能是位于链接之下的一个绝对定位的＜div＞标签。可以使用show（）函数或者jQuery的其他显示效果之一来显示该表单（参见6.1.3节）。

4.停止链接。

这是最重要的一步。如果没有停止链接，Web浏览器将直接离开当前页面而载入登录Web页面。

下面是如何使用“返回false”的方法来停止链接起作用。假设该链接的I D为showForm并且隐藏的带有登录表单的＜div＞标签拥有一个ID为loginForm：

1$（'#showForm'）.click（function（）{

2$（'#loginForm'）.fadeIn（'slow'）；

3 return false；

4}）；

第1行完成上述步骤1和步骤2的任务，第2行显示了隐藏的表单。第3行告诉浏览器“停下来，不要打开这个链接”。必须让return false；语句作为该函数的最后一行，因为一旦JavaScript解释器遇到了返回语句，它就会退出函数。

也可以使用jQuery的preventDefault（）函数，如下所示：

1$（'#showForm'）.click（function（evt）{

2

$（'#loginForm'）.fadeIn（'slow'）；

3

evt.preventDefault（）；

4}）；

这个脚本的基本细节和前面的一个相同。主要区别在于，分配给click事件的函数现在接收一个参数：evt，它表示事件本身（参见5.4.3节对事件对象的讨论）。事件有其自己的一组函数和属性，preventDefault（）函数直接停止了和事件相关的任何默认行为：对于链接上的一次单击，它载入一个新的Web页面。

8.2　在新窗口中打开外部链接

对于任何依靠访问者人数的站点来说，失去访问者是很可怕的。从广告收入赚钱的在线组织，如果可能的话绝不想把人们从他们的站点送走，电子商务站点也不希望通过让购物者单击链接离开其背后的站点而失去一个潜在客户；在展示一个完整站点的设计方案的时候，在一个潜在的客户浏览设计者刚完成的项目的时候，Web设计者可能不想让他离开这个站点。

很多站点这样来避免发生这些可怕的事情，在一个新的窗口中找开到另一个站点的链接。通过这种方式，当访问者完成浏览其他站点并关闭其窗口的时候，最初的站点仍然存在。HTML早已提供了一种方法做到这点，即使用一个链接的target属性，如果把这个属性设置为_blank, Web浏览器知道在新窗口打开该链接（或者，对于那些使用标签的浏览器来说，在一个新的标签页打开链接）。

注意：在Web可用性专家之间，确实存在一些关于打开新窗口的策略是好办法还是馊主意的争论。例如，参见www.useit.com/alertbox/990530.html。

对于指向站点之外的每个链接都手动地添加target="_blank"，这会花较长的时间，并且很容易忘记。幸运的是，使用JavaScript和jQuery，有一种快速且容易的方法来迫使Web浏览器在新的窗口或新的浏览器标签页中打开一个外部链接（或者是我们所希望的任何链接）。基本的过程很简单：

1.标识想要在新窗口中打开的链接。

在本章中，我们将使用一个jQuery选择器（参见4.6节）来标识这些链接。

2.为链接添加一个值为_blank的target属性。

你可能会想，“嘿，这是无效的HTML，我不能这么做”。首先，它只是对HTML 4.01和XHTML 1.0的严格版本来说无效，因此，对于其他的文档类型来说（包括对新的HTML 5 doctype），它是没问题的。其次，我们的页面将会仍然有效，因为一个HTML验证器（例如http：//validator.w3.org/）只能分析Web页面文件中真正的HTML代码，并且不识别JavaScript添加的任何HTML。最后，每种浏览器都理解target属性，因此，我们知道链接将在一个新窗口中打开，而不管是不是标准的严格文档类型。

使用jQuery，可以在一行代码中完成前面的两步：

$（'a[href^="http：//"]'）.attr（'target'，’_blank'）；

jQuery选择器$（'a[href^=http：//]'）使用一个属性选择器（参见4.6.2节）来识别以http：//开头的＜a＞标签（例如，http：//www.yahoo.com）。选择器识别了这些链接的所有类型，然后使用jQuery attr（）函数（参见4.9节）把每个链接的target属性设置为_blank。就是这样。

如果使用绝对路径来指定自己的站点上的一个文件，还需要其他步骤。例如，如果站点的地址是www.your_site.com，然后，链接到站点上的其他页面或文件，例如http：//www.your_site.com/a_page.html，那么，前面的代码也强迫这些链接在一个新的窗口中打开。如果没有为站点的每个页面打开一个新的窗口（你的访问者真可怜），则需要像下面这样编写代码：

var myURL=location.protocol+’//’+location.hostname；

$（'a[href^="http：//"]'）.not（'[href^="’+myURL+’"]'）.attr（'target'，’_blank'）；

这段代码首先为站点指定了URL，并且将其赋给了一个变量myURL。站点的URL通过浏览器窗口对象的帮助来完成。浏览器知道用来访问一个URL的协议，即http：，或者对于安全站点来说是https：。它存储在location对象的protocol属性中。同样，站点的名字（例如www.sawmac.com）存储在hostname属性中。因此，JavaScript代码location.protocol+’//’+location.hostname将产生一个诸如http：//www.sawmac.com的字符串。当然，这个例子中的主机名根据这段JavaScript代码所来自的页面而变化。例如，如果把这段代码放到了来自http：//www.your_site.com的页面上，那么，当某人浏览来自站点location.hostname的页面的时候，将会是www.your_site.com。代码的第2行以一个jQuery选择器开始，该选择器获取了以http：//开头的所有链接。那么，not（）函数删除了以你的URL开始的任何链接，在这个例子中，就是指向http：//www.sawmac.com的链接（not（）函数是排除来自一个jQuery选择的一些元素的一种有用方式，要了解其详细内容，请浏览http：//api.jquery.com/not）。

因此，要真正地在页面中使用这段代码，只需要链接到jQuery文件，添加$（document）.ready（）函数（参见5.4.1节），然后像下面这样插入前面的代码：

＜script src="js/jquery-1.6.3.min.js"＞＜/script＞

＜script＞

$（document）.ready（function（）{

var myURL=location.protocol+’//’+location.hostname；

$（'a[href^="http：//"]'）.not（'[href^="’+myURL+’"]'）.attr（'target'，’_blank'）；

}）；

＜/script＞

另一种方式可能就是创建一个外部JavaScript文件（参见1.2节），在该文件中，创建一个函数，该函数运行使得外部链接在新窗口中打开的代码。把这个文件附加到页面，然后在该页面上调用函数。

例如，可以使用如下的代码创建一个名为open_external.js的函数：

function openExt（）{

var myURL=location.protocol+’//’+location.hostname；

$（'a[href^="http：//"]'）.not（'[href^="’+myURL+’"]'）.attr（'target'，’_blank'）；

}

然后，把如下的代码添加到站点上想要应用这个函数的每个页面中：

＜script src="js/jquery-1.6.3.min.js"＞＜/script＞

＜script src="js/open_external.js"＞＜/script＞

＜script＞

$（document）.ready（function（）{

openExt（）；

//add any other JavaScript code to the page

}）；

＜/script＞

使用一个外部文件的优点是，如果在整个站点的成百的页面中使用这个函数，可以很容易地更新这个脚本以使它更有趣，例如，我们可以随后更改openExt（）函数，以便在当前页的同一框架中打开外部页面（参见8.4节了解如何做到这点）。换句话说，一个外部.js文件使得很容易让脚本在整个站点中保持一致。

8.3　创建新窗口

Web浏览器允许我们打开一个新的窗口并定制其很多属性，例如，宽度、高度、在屏幕上的位置，甚至是否显示滚动条、菜单或者定位（地址）栏。基本的技术是使用open（）方法，其基本结构如下：

open（URL, name, properties）

open（）方法接收3个参数。第一个参数是希望在新打开的窗口中显示的页面的URL，和用于一个链接的href属性是同样的值（例如，http：//www.google.com、/pages/map.html或者../../portfolio.html）。第二个参数是窗口的名字，它可能是想要使用的任何名字，遵从本书2.4.1节所述的同样的命名规则。最后，可以传递包含了新窗口的设置的一个字符串（例如，其高度和宽度）。

此外，当打开一个新的窗口的时候，通常创建一个变量来存储对该窗口的引用。例如，如果想要在一个200像素的方形窗口中打开Google的主页，可以这样编写代码：

var newWin=open（'http：//www.google.com/’，8

'theWin'，'height=200，width=200'）；

注意：一行代码末尾的8符号表示下一行应该是作为第一行的一部分输入。但是，由于JavaScript代码的实际长度超过了本书的宽度，只好将其分为两行。

这段代码打开了一个新的窗口并且把对该窗口的一个引用存储在变量newWin中。本节下面“使用窗口引用”部分将介绍如何使用这个引用来控制新打开的窗口。

注意：提供给新窗口的名字（在这个例子中是'theWin'）不会做太多事情。然而，一旦提供了一个名字，如果试图使用同样的名字打开另一个窗口，将不会得到一个新的窗口。相反，使用open（）方法请求的页面只是载入之前创建的具有相同名字的窗口。

窗口属性

浏览器窗口有很多不同的部分：滚动条、调整大小指针、工具栏等（参见图8-1）。此外，窗口还有宽度和高度，以及在屏幕上的位置。当创建一个新窗口时，可以通过创建一个字符串，其中包含逗号隔开的每个属性及其设置的列表，以此作为open（）方法的第三个参数，从而来设置这些属性中的大多数。例如，要设置一个新窗口的宽度和高度，并且确保地址栏的出现，可以这样编写：

var winProps='width=400，height=300，location=yes'；

var newWin=open（'about.html'，'aWin'，winProps）；

图　8-1　浏览器窗口的不同属性，例如滚动条、工具栏和调整大小指针组合在一起叫做“边框”（chrome）。每个浏览器对这些属性都有自己的选择，并且通常浏览器开发者几乎不能操控它们的功能和外观。然而，当使用JavaScript创建一个新的窗口的时候，可以关闭这些功能中的某些

使用像素值来设置控制窗口的大小或位置的属性，而其他的属性的值使用yes（打开该属性）或no（关闭该属性）。在yes/no属性的任何情况下（例如，toolbar或location），如果没有指定一个属性值，Web浏览器就关闭该属性（例如，如果没有设置location属性，Web浏览器会隐藏通常出现在窗口顶端的location字段）。只有height、width、left、top和toolbar在不同浏览器中都一致地工作。正如下面的列表所介绍的，一些浏览器会完全忽略这些属性中的一些，因此，如果你要使用JavaScript创建弹出窗口的话，确保在可能使用的每款浏览器中进行测试。

·height表示窗口的高度，以像素为单位。可以指定百分比值，或者任何其他单位。如果没有指定高度，Web浏览器会匹配当前窗口的高度。

·width指定窗口的宽度。和height一样，只能使用像素，并且，如果漏掉了这个属性，Web浏览器会匹配当前窗口的宽度。

·left是距离屏幕的左边界的位置，以像素为单位。

·top是距离屏幕的上边界的位置，以像素为单位。

·scrollbars出现在浏览器窗口的右边界或下边界，不管页面是否大于窗口本身。要完全隐藏滚动条，可以把这个属性设置为no。不能控制隐藏哪个滚动条（要么都出现，要么都隐藏）。

·status控制窗口底部的状态栏的外观。Firefox和Internet Explorer通常不允许隐藏状态栏，因此，它在浏览器中总是可见的。

·toolbar设置包含导航按钮、书签按钮和特定的浏览器可用的其他空间的工具栏的可见性。在Safari上，工具栏和地址栏设置是相同的：打开任何一个显示，都会同时显示工具栏按钮和地址栏字段。

·location指定地址栏是否可见。这个字段显示了页面的URL，并且允许访问者通过输入一个新的URL到达另一个页面。Opera、IE和Firefox不允许完全隐藏一个页面的地址栏。如果不想打开location属性，那么页面的URL将出现在标题栏的上方。这种功能用来防止对JavaScript的滥用，例如，打开一个新的窗口并且把你送到另外一个和刚刚离开的站点看上去相似的站点。此外，当这个属性打开的时候，Safari会把工具栏和地址栏一起显示。

·menubar适用于那些窗口顶端有一个菜单的浏览器（例如，出现在大多数程序上的常见的File和Edit菜单）。这个设置只适用于Windows浏览器，Mac在屏幕顶端而不是单独的窗口中拥有菜单。并且，它也不适用于IE 7及其以上的版本，这些浏览器通常不会显示一个菜单栏。

注意：要查看使用window.open（）方法的令人惊讶的JavaScript编程示例，请访问http：//experiments.instrum3nt.com/markmahoney/ball/和http：//thewildernessdowntown.com/。

使用窗口引用

一旦打开了一个新的窗口，可以使用对这个窗口的引用来控制它。例如，假设使用如下的代码打开了一个新窗口：

var newWin=open（'products.html'，'theWin'，'width=300，height=300'）；

在这个例子中，变量newWin保存了对新窗口的引用。然后可以对该变量应用任何浏览器窗口方法，来控制窗口。例如，如果想要关闭该窗口，可以使用close（）方法，如下所示：

newWin.close（）；

浏览器支持很多不同的方法用于窗口对象，下面列出了最常用来控制窗口本身的方法：

·close（）关闭特定的窗口。例如，命令close（）关闭当前窗口。但是，可以把这个函数应用到一个窗口引用，例如，newWin.close（）。可以使用任何事件来触发这个关闭事件，例如在显示“Close this window.”的按钮上的一次鼠标单击。

注意：如果使用这些命令本身的任何一个，它都应用于运行脚本的窗口。例如，给一个脚本添加语句close（），将会关闭脚本所在的窗口。然而，如果已经打开了一个窗口并且有了对该窗口的一个引用（例如，在窗口打开的时候创建的一个变量，例如newWin），那么，可以从最初创建该窗口的页面关闭该窗口，只要像这样使用该引用：newWin.close（）。

·blur（）迫使窗口“失去焦点”。也就是说，该窗口移动到已经打开的任何窗口的背后。这是隐藏一个打开的窗口的一种方法，并且Web广告商使用它来创建“弹出窗口”广告，它在当前窗口的下方打开一个窗口，这样，当访问者关闭所有的窗口时，就没有恼人的广告等待他了。

·focus（）和blur（）相反，它迫使窗口进入到其他窗口的栈的上方。

·moveBy（）允许向右和向下按照一组像素数来移动窗口。我们为该方法提供两个参数，第一个指定了向右移动的像素数，第二个指定了把窗口向下移动多少像素。例如，newWin.moveBy（200，300）；把newWind变量引用的窗口向右移动200像素，向下移动300像素。

·moveTo（）通过指定一个向左值和一个向上值，把窗口移动到显示器上的一个指定位置。这条命令和打开一个新窗口的时候设置left和top属性是相同的。例如，要把窗口移动到显示器的左上角，可以运行这段代码：moveTo（0，0）；。

注意：要查看这些方法中的大多数的实际应用，请访问All Is Not Lost网站：http：//www.allisnotlost/。

·resizeBy（）改变窗口的宽度和高度。它接收两个参数：第一个参数指定了让窗口加宽多少像素，第二个参数指定窗口应该增高多少像素。例如，resizeBy（100，200）；使得当前窗口增高100像素并加宽200像素。使用负数会使得窗口变小。

·resizeTo（）把窗口的尺寸更改为一组宽度和高度。例如，resizeTo（200，400）；把当前窗口改为200像素宽和400像素高。

·scrollBy（）通过指定向右和向下的像素数在窗口中滚动文档。例如，scrollBy（100，200）；把当前文档向下滚动200像素，向右滚动100像素。如果文档没有滚动（换句话说，文档恰好在窗口之中而没有滚动条，或者文档已经滚动到头），那么这个函数没有效果。

·scrollTo（）在窗口中把文档从页面的顶部和左端向下和向右滚动指定的像素位置。例如，scrollTo（100，200）；把当前文档滚动到距顶部200像素、距离左边界100像素的位置。如果这个文档没有滚动（换句话说，文档刚好在窗口中，而没有滚动条或者文档已经滚动到头），那么这个函数没有效果。

注意：jQuery ScrollTo插件提供了一种简单的方式来使用JavaScript控制文档滚动。要找到关于这个插件的更多信息，请参见http：//plug-ins.jquery.com/project/ScrollTo。

可以打开一个新窗口的事件

在Web的简短历史中，弹出式窗口恶名远扬。遗憾的是，很多Web站点滥用open（）方法来强制对那些没有疑心的访问者使用讨厌的弹出式广告。现在，很多浏览器都有弹出窗口阻截功能，防止讨厌的窗口，因此，即便我们可以添加JavaScript代码使得只要页面载入或者当访问者关闭了一个窗口，就打开一个新窗口，而大多数浏览器是不会让这种情况发生的。访问者要么看到一条消息告诉他浏览器阻止打开新窗口，或者可能根本不会得到任何关于阻止弹出窗口的提示。

实际上，很多浏览器不允许使用大多数事件（例如，mouseover、mouseout和kepress）来打开浏览器窗口。使用JavaScript来打开窗口的唯一可靠方式是，当用户单击一个链接或提交一个表单的时候触发操作。为了做到这一点，我们给任何HTML元素（不一定必须是一个链接）添加一个click事件，并且打开一个新窗口。例如，假设想让页面上的某些链接在一个300像素的方形的新窗口中打开，带有滚动条，并且可以调整大小，但是没有像工具栏这样的任何其他的浏览器边框。例如，可以为那些特定的链接添加一个名为popup的类，然后，向页面添加如下的jQuery代码：

$（'.popup'）.click（function（）{

var winProps='height=300，width=300，resizable=yes, scrollbars=yes'；

var newWin=open（$（this）.attr（'href'），'aWin'，winProps）；

}

8.4　在页面上的一个窗口中打开页面

打开新窗口可能有些问题。不仅很多浏览器试图阻止弹出式窗口，而且和很多设计者一样，我们也不喜欢不能够真正控制浏览器窗口外观这个事实。如果只是想要一种干净、简单的方式来显示一个新的Web页面，而又不退出当前的页面，该怎么办呢？当然是使用JavaScript，通过使用JavaScript为页面添加一个iframe并且在该iframe中显示另一个Web页面，我们可以创建一个包含窗口的页面效果。最终的效果就好像是链接页面直接浮动在当前页面的上方（如图8-2所示）。

图　8-2　可以使用FancyBox jQuery插件快速地创建“位于页面中的一个页面”的效果

＜iframe＞（inline frame的缩写形式）类似于老式的HTML frame，但是，你可以在页面的HTML中的任何地方插入一个iframe。通过设置iframe的尺寸并指定一个src属性（一个Web页面地址），你可以加载另一个Web页面，以便它看上去像是当前页面的一部分。为了使这个过程更容易，你可以使用一个jQuery插件来处理所有繁琐的工作，这样，你就可以集中精力关注设计了。

实际上，在第7章中，我们就已经可以使用一个插件来做这些工作了，这就是FancyBox。在第7章中，我们使用FancyBox来创建一个图像集，还可以使用它的弹出窗口的优点，显示自己的站点或者Web上的任何其他站点的整个Web页面。

注意：可以从http：//www.w3schools.com/tags/tag_iframe.asp了解关于iframes的更多知识。＜iframe＞对于HTML 4.01严格版或XHTML 1.0严格版来说，不是有效的标签。然而，Greybox插件使用JavaScript添加＜iframe＞标签，因此，实际的HTML可以通过验证。此外，既然HTML 5支持＜iframe＞标签，所有主流的浏览器将来都将继续支持它。

使用FancyBox在一个页面中显示链接，几乎与本书7.4节介绍的使用该插件制作图像集相同。概括起来，步骤如下：

1.从http：//fancybox.net下载FancyBox文件。

参见本书7.4.1节步骤2，以了解所需文件的详细信息。

2.把FancyBox CSS文件添加到Web页面。

FancyBox使用一些奇特的CSS来实现页面中的一个页面的外观，并且没有该文件的话，将无法正确显示或运行。

3.添加JavaScript文件。

当然，FancyBox需要jQuery以及其自己的外部JavaScript文件，因此，要将这些添加到页面。例如：

＜script src="js/jquery-1.6.3.min.js"＞＜/script＞

＜script src="fancybox/jquery.fancybox-1.3.4.js"＞＜/script＞

正如本书7.4.3节所述，当打开或关闭屏幕上的Web页面的时候，如果想要添加更为有趣的效果，你可能还想要使用jQueryeasing插件。

4.对于任何想要在页面上的一个弹出窗口中打开的链接，添加class="iframe"。

由于FancyBox需要在＜iframe＞标签中打开外部Web页面（这对一个简单的图像集来说不是必须的），你需要标识需要iframe的链接。做到这点的最容易的方法是，直接给每个链接添加一个class="iframe"，如下所示：

＜a href="http：//www.google.com"class="iframe"＞Google＜/a＞

5.添加＜script＞标签、jQuery ready（）函数，并且调用FancyBox。

＜script＞

$（document）.ready（function（）{

$（'.iframe'）.fancybox（）；

}）；//end ready

＜/script＞

正如你在第7章中所了解到的，FancyBox真的很容易使用：实际上只需要一行代码。在这个例子中，由于对每个链接都应用了类iframe，因此你可以使用jQuery选择器$（'.iframe'）。

6.插入一个对象直接量，其中带有想要弹出的窗口的宽度和高度。

FancyBox接收许多意义不同的设置。正如你在本书7.4.3节所了解到的，通过给fancybox（）函数传递一个对象直接量，你可以控制效果运行的速度、页面重叠时候的透明度，以及更多内容。通常，对于图像来说，弹出窗口的高度和宽度是由图像的大小来确定的。在链接的页面的例子中，没有设置大小，因此，你需要提供一个width值和一个height值。例如，假设你想要让弹出窗口成为760像素宽和400像素高，可以在步骤5中添加如下粗体所示的代码：

＜script＞

$（document）.ready（function（）{

$（'.iframe'）.fancybox（{

width：760，

height：400

}）；//end fancybox

}）；//end ready

＜/script＞

也可以使用百分比值，以便该窗口是整个浏览器窗口的宽度和高度的一个百分比，这是一种好办法，可以让该窗口充分利用一些访问者使用大屏幕的优点，而且对于较小的屏幕来说也很合适。除了使用数字，还需要对width和height使用一个字符串值，以便可以包含一个%符号，如下所示：

＜script＞

$（document）.ready（function（）{

$（'.iframe'）.fancybox（{

width：’85%’，

height：’75%’

}）；//end fancybox

}）；//end ready

＜/script＞

教程：在页面中打开一个页面

在本教程中，我们将真正实践一次Fanc yBox插件，将其应用到页面并定制其外观。

注意：参见1.3节中的“注意”部分以获取如何下载教程文件的信息。

1.在文本编辑器中打开chapter08目录下的文件in-page-links.html。

读到本书这里的时候，你可能已经对录入代码感觉累了，因此，这个文件给出了到FancyBox样式表的一个链接、加载jQuery和FancyBox插件的script标签，以及带有$（document）.ready（）函数的一个＜script＞标签。第一步是标记出你想要在页面的窗口中打开的链接。

2.找到第一个链接＜a href="http：//www.google.com/"＞Search Google＜/a＞，并且添加class="iframe"以使得代码如下所示：

＜a href="http：//www.google.com/"class="iframe"＞Search Google＜/a＞

和本书7.4.1节介绍的FancyBox图像集一样，你可以通过给这个＜a＞标签添加一个title属性，从而给弹出窗口添加标题。

3.给步骤2中所编辑的链接添加title="Google"，从而使其如下所示：

＜a href="http：//www.google.com/"class="iframe"title="Google"＞Search Google＜/a＞

|现在，只需要类似地设置其他的链接。

4.对于后续的4个链接，重复步骤2和步骤3。

对于t i t l e属性，使用链接中的文本（或者自己整理）。现在，可以把FancyBox应用于这些链接了。

5.在靠近文件顶部的地方单击$（document）.ready（）函数后面的空行，并且添加如下第3行到第7行所列的代码：

1＜script＞

2$（document）.ready（function（）{

3$（'.iframe'）.fancybox（{

4 width：’90%’，

5 height：’90%’，

6 titlePosition：'outside'

7}）；//end fancybox

8}）；end ready（）

9＜/script＞

这里，width和height设置为90%，以便弹出窗口会根据访问者的浏览器窗口而调整。titlePosition选项（参见本书7.4.3节）将该标题放到了弹出窗口之外。

6.保存该页面，并且在Web浏览器中预览。单击那些链接。

这些链接在一个弹出窗口中打开。很漂亮。可以使用7.4.3节所讨论的任何其他的FancyBox选项来修改该页面的外观：修改覆盖颜色、在打开和关闭链接的时候增加一个放大和缩小效果，并且修改Close按钮。文件complete_in-page-links.html包含了这个教程的最终版本。

注意：本书之前的版本包含了关于创建“较大的链接”的一个部分，也就是说，这个链接包含了诸如div、标题和段落这样的一个块级元素。这一技术使得你可以创建一个较大的、可单击的目标，还带有翻滚的效果。你不再需要JavaScript来做这些事情。HTML5（以及所有当前的浏览器）允许你用＜a＞标签来包含任何包含了div的块级元素。

8.5　基本的动画的导航栏

随着Web站点越来越大，要访问到站点的每个部分，而又不想让页面中塞满链接（或者让访问者满眼都是链接），变得越来越难了。为了更好地导航一个站点，很多Web设计师使用下拉式菜单系统来隐藏链接，直到人们要求这些链接出现（参见图8-3所示）。而这些都是针对这一问题只使用CSS的解决方法，并且，这些方法并不总是很理想的。首先，只使用CSS的弹出菜单不稳定，如果你仅仅在菜单上离开一秒，菜单就会消失。此外，CSS不允许你添加任何视觉效果，例如将菜单淡入到视野，或者令其以动画的形式出现在该位置。

图　8-3　导航一个充满了很多页面和部分的菜单，可能很容易令人混淆。带有下拉菜单的导航栏是简化站点链接显示的一种优雅方法。它允许你包含众多的导航选项，并且减少页面的杂乱程度

幸运的是，只需要一点点JavaScript代码，你就可以创建一个动画的菜单系统，而且它可以在访问者使用的所有浏览器中正常工作。这个导航菜单更大程度上依赖于HTML和CSS，而不是我们目前在本书中所学习到的JavaScript技术。你将使用HTML来创建一个嵌套的链接集合以及CSS，这些CSS用来将那些链接格式化成一个导航栏一样，并且用来定位和隐藏任何子菜单。然后，我们将添加一些JavaScript来把菜单的显示动画成为鼠标光标划过导航栏按钮的样子。

8.5.1　HTML

针对导航菜单的HTML是使用＜ul＞标签创建的一个简单的项目列表。每一个顶级的＜li＞标签表示导航栏上的主要按钮。要创建一个子菜单，在该菜单所属的＜li＞标签中添加一个＜ul＞标签。例如，图8-3中的菜单的HTML如下所示：

＜ul id="navigation"＞

＜li＞＜a href="#"＞Home＜/a＞＜/li＞

＜li＞＜a href="#"＞About Us＜/a＞

＜ul＞

＜li＞＜a href="#"＞Our History＜/a＞＜/li＞

＜li＞＜a href="#"＞Driving Directions＜/a＞＜/li＞

＜li＞＜a href="#"＞Hours＜/a＞＜/li＞

＜/ul＞

＜/li＞

＜li＞＜a href="#"＞Our Products＜/a＞

＜ul＞

＜li＞＜a href="#"＞Gizmos＜/a＞

＜ul＞

＜li＞＜a href="#"＞Gizmo Basic＜/a＞＜/li＞

＜li＞＜a href="#"＞Gizmo Standard＜/a＞＜/li＞

＜li＞＜a href="#"＞Gizmo Supreme＜/a＞＜/li＞

＜/ul＞

＜/li＞

＜li＞＜a href="#"＞Gadgets＜/a＞

＜ul＞

＜li＞＜a href="#"＞Gadget Basic＜/a＞＜/li＞

＜li＞＜a href="#"＞Gadget Standard＜/a＞＜/li＞

＜li＞＜a href="#"＞Gadget Supreme＜/a＞

＜ul＞

＜li＞＜a href="#"＞Gadget Supreme A＜/a＞＜/li＞

＜li＞＜a href="#"＞Gadget Supreme B＜/a＞＜/li＞

＜/ul＞

＜/li＞

＜/ul＞

＜/li＞

＜li＞＜a href="#"＞Time Machines＜/a＞＜/li＞

＜/ul＞

＜/li＞

＜/ul＞

注意：为了让这个示例显得简单，这段HTML使用一个#符号来替代每个＜a＞标签的href属性的一个实际的URL，例如＜a href="#"＞。在一个实际的导航栏中，每个＜a＞标签都指向一个真正的Web页面，诸如＜a href="products/gadgets/basic.html"＞。

三个主要的导航按钮是Home、About Us和Our Products。About Us之下是一个菜单，由包含了Our History、Driving Directions和Hours选项的一个嵌套列表来表示。Our Products按钮包含了带有Gizmo、Gadget和Time Machines选项的另一个菜单。Gizmo和Gadgets都拥有其自己的菜单（两个其他的嵌套列表），并且，在Gadget菜单之下的Gadget Supreme选项之下，还有两个其他的选项（也是另一个嵌套的列表）。嵌套的列表只不过是有意包含更多的一个层级的另一个列表。从视觉上看，上面的HTML可以产生如下所示的一个列表：

·Home

·About Us

·Our History

·Driving Directions

·Hours

·Our Products

·Gizmos

·Gizmo Basic

·Gizmo Standard

·Gizmo Supreme

·Gadgets

·Gadget Basic

·Gadget Standard

·Gadget Supreme

·Gadget Supreme A

·Gadget Supreme B

·Tim e Machines

记住，嵌套的列表放在其父项的＜l i＞标签之中。例如，＜u l＞标签包含了项目Gizmos、Gadgets和Time Machines的一个列表，该列表包含在用于Products列表项的＜li＞标签之中（如果你需要回顾一下创建HTML列表的支持，请查看www.htmldog.com/guides/htmlbeginner/lists/）。

注意：确保顶级的链接（在这个例子中是Home、About Us和Our Products）总是指向这样一个列表，该页面链接到其部分中的一个子页面（例如，About Us下面的Our History、Driving Directions和Hours链接）。通过这种方式，如果浏览器没有打开JavaScript，它仍然可以访问子菜单中的链接。

8.5.2　CSS

Daniel Thomson创建的jQuery Navigation Plugin（www.pollenizer.com/jquery-navigation-plugin/）完成了放置列表项的大多数工作，因此，你不需要负责去创建并排按钮、下拉菜单或弹出菜单所必须的那些CSS。然而，你可能想要定制按钮的外观，例如，添加一个背景颜色、删除链接的下划线等。可以参考一些有帮助的样式，用以创建控制菜单的不同元素（例如，打开chapter08教程文件夹中的complete_menu.html文件，然后看看页面的＜head＞中的CSS）。

注意：下面列出的样式假设主＜ul＞标签包含了拥有一个诸如＜ul id="navigation"＞的navigation ID的所有菜单和按钮。你可以任意地使用其他的ID名称，例如mainNav、menu，只需要相应地调整下面列出的样式名称。例如，如果你使用mainNav ID，将下面列出的样式#navigation a修改为#mainNav a。

·#navigation ul控制每个菜单的整体外观。将这个样式的padding和margin设置为0是个好主意。

·#navigation li控制每个按钮的样式（然而，正如你将在这个示例中看到的，最好将这个＜a＞标签上的大多数样式放到每个＜li＞标签中）。然而，要取消每个按钮旁边的项目符号，至少应该将padding和margin设置为0，将list-style-type属性设置为none。下面是I和li标签的一个很好的默认样式：

#navigation ul，#navigation li{

margin：0；

padding：0；

list-style-type：none；

}

·#navigation a控制每个链接的外观。你可以轻松地使用这一样式：应用背景颜色、字体、字体颜色、加粗线，以便让按钮更突出。然而，避免将margin和padding值设置为0以外的任何值，否则，按钮将会彼此重叠。

·#navigation a：hover控制了链接的悬停或鼠标光标划过的外观。当鼠标放在按钮之上的时候，你可以更改背景颜色，按钮会突出显示，这样可以提供很好的视觉反馈。

·#navigation. stack＞a和#navigation.stack＞a：hover控制了拥有菜单的链接的外观。你可能想要添加一个背景图片（例如，向下的箭头）来表示这些按钮可以打开一个菜单，或者在视觉上与那些没有菜单的按钮能够区分开。

·#navigation ul. stack＞a和#navigation ul.stack＞a：hover为子菜单提供了样式。也就是说，通过按钮打开一个菜单，而这个菜单本身是按钮的一部分。例如，在图8-4中，Gizmos和Gadgets按钮都有子菜单。你不需要给这些按钮使用不同的样式，该插件会以某种不同的方式显示这些菜单。在图8-4中，你会看到从上面打开的一个菜单（例如，当鼠标光标放在Our Products上的时候），它在按钮下生成一个下拉式菜单，但是，子菜单是从按钮的（Gizmo和Gadgets）的右边打开的。如果你想要使用一个箭头图形来表示一个菜单，对于顶层的菜单，你可能想要使用一个向下的箭头；而对于其他的菜单，可能是一个向右的箭头。可以将complete_menu.html文件中的CSS作为示例。

·清除菜单后面的漂移。该插件使用CSS float属性来并排地放置主菜单中的菜单按钮。因此，跟在菜单后面的文本或其他内容可能会滑到紧挨着菜单的右侧。为了预防这种折返，应该给紧跟在菜单后面的内容的样式格式添加clear：left。做到这点的一种很容易的方式，就是创建如下所示的一个类样式：

.clearLeft{clear：left；}

然后，将此类应用于紧跟在菜单的＜ul＞之后的标签。例如，假设有一个段落标签跟在nav菜单之后。可以编写如下代码来创建开始的＜p＞标签：

＜p class="clearLeft"＞

8.5.3　JavaScript

使用JavaScript来控制菜单显示，其背后的基本思想是很简单的。鼠标光标放在一个列表项上，如果它有一个嵌套的列表（一个弹出式菜单），那么显示该嵌套的列表；鼠标光标离开该列表，就会隐藏任何嵌套的列表。

还有一些微妙的地方使得这一基本思路变得更加复杂。例如，当鼠标光标从弹出式菜单的父列表项上移开的时候，它立即会消失，这需要有精确的鼠标技术。在试图导航到一个弹出式菜单的时候，很容易将鼠标光标从一个列表项上移开。如果这个菜单突然消失，将迫使访问者把鼠标光标移回到最初的列表项，以再次打开菜单。当有数个层级的弹出式菜单的时候，容易漏掉目标并丢掉菜单，这是很令人沮丧的。

为了解决这个问题，大多数导航菜单脚本都添加了一个计时器功能，它延迟了弹出式菜单的消失时间。这个计时器提供了并非那么精确的鼠标技术，并且使得弹出式菜单显得更加稳定一些。

jQuery Navigation插件是创建基本的下拉式菜单的一种非常容易的方法，并且，它还提供了淡入或滑入效果的选项。

8.5.4　教程

既然已经了解了创建导航菜单的基础知识，下面介绍如何实现它。在这个教程中，你将添加CSS和JavaScript，从而将本书8.5.1节所介绍的基础的HTML菜单列表转换为一个导航栏。

注意：参见本书1.3节以“注意”部分以获取如何下载教程文件的信息。

1.在文本编辑器中打开chapter08文件夹下的文件menu.html。

这个文件包含了链接的一个项目列表，这个项目列表将转换为一个导航栏。为了看看没有任何JavaScript代码它看起来是什么样子，第一步是附加该插件的文件。

2.单击＜script src="../_js/jquery-1.6.3.min.js"＞＜/script＞之后的一个空行，并且输入：

＜script src="../_js/nav1.1.min.js"＞＜/script＞

这会把jQuery导航插件链接到该页面。现在，你将能够添加程序了，我们已经提供了带有$（document）.ready（）函数的一组＜script＞标签。

3.单击$（document）.ready（）函数中的空行，并且添加如下粗体所示的代码：

$（document）.ready（function（）{

$（"#navigation"）.navPlugin（{

'itemWidth'：150，

'itemHeight'：30

}）；}）；

要激活这个菜单，首先使用jQuery来选择用于主导航栏的＜ul＞标签，在这个示例中，该标签拥有一个ID navigation，因此，代码$（'#navigation'）会选择该标签，并且.navPlugin（）将jQuery Navigation插件编程应用于这个菜单。

为了控制该插件，我们将一个对象直接量作为参数传递。该插件的选项并不多，但是，最重要的两个选项是itemWidth和itemHeight，它们控制着按钮的宽度和高度。你不能为菜单上的每个按钮设置不同的宽度和高度，因此，确保提供一个宽度和高度能够放得下拥有最多文本的按钮的所有内容。也可以针对如何显示菜单来添加效果。

4.编辑刚刚添加的代码，以使其看上去如下面的代码所示（添加粗体显示的代码）：

1$（document）.ready（function（）{

2$（'#navigation'）.navPlugin（{

3'itemWidth'：150，

4'itemHeight'：30，

5'navEffect'：'slide'

6}）；

7}）；

别忘了在第4行的30后面加上逗号。navEffect选项控制了菜单在页面上如何显示。通常，它们只是弹出到视线中，但是，通过一个'slide'或者'fade'的值，你也可以让菜单从下面滑入视线中，或者让其淡入视线中。也可以控制菜单显示和消失的速度。

5.编辑这段代码，以使其看上去如下面的代码所示（添加粗体所示的代码）：

1$（document）.ready（function（）{

2$（"#navigation"）.navPlugin（{

3

'itemWidth'：150，

4

'itemHeight'：30，5

'navEffect'：'slide'，

6

'speed'：250

7}）；

8}）；

别忘了添加第5行中'slide'后面的逗号。Speed选项控制以毫秒为单位来设置，因此，250意味着250毫秒或者1/4秒，这是很不错的设置值。更大的数字将会使得菜单显得迟缓而不灵活。

6.保存页面并且在浏览器中预览。

现在，你应该有了一个功能完整的导航栏了。这很简单。查看一下文件中的CSS，看看这些按钮是如何格式化的，并且尝试不同的CSS属性来定制该菜单的外观。教程complete_menu.html的第一个完整版本位于chapter08文件夹下。

插件警告用于增强页面导航的其他jQuery插件

jQuery Navigation插件简单而高效，但是，还有很多其他的jQuery插件可以用于创建更高级的导航。

·DD Mega Menu插件（http：//dynamicdrive.com/dynamicindex1/ddmegamenu.htm）不仅是允许我们创建下拉菜单，而且可以下拉进入视线的整个＜divs＞。想象一下，当鼠标光标放在一个按钮上的时候，一个导航栏将整个内容都滑入视线范围。通过这种方式，我们可以隐藏内容，但是当某个鼠标光标位于一个菜单按钮上的时候，就显示这些内容。

该插件也支持常规的下拉式菜单。

·jqDock插件（www.wizzud.com/jqDock/）允许我们模拟Mac的Dock，这是跨越屏幕底部的一个带状图像，它随着你的滚动而增大。这是一种有趣的视觉效果。

如果这些插件还不能引发你的兴趣，查看一下位于www.noupe.com/jquery/45-jquery-navigation-plugins-and-tutorials.html的45种jQuery导航插件的详细列表和教程。

第9章　扩展Web表单

从Web诞生开始，表单就使得Web站点能够收集来自访问者的信息。表单可以收集一个新闻组的E-mail地址，收集完成一次在线销售的配送信息，或者直接接收访问者反馈。表单还要求站点的访问者思考：阅读标签、录入信息、做出选择等。由于一些站点完全依赖于接收表单数据（如果人们不使用表单来订购图书，Amazon恐怕要不了多久就要关门了），Web设计者需要知道如何让表单尽可能容易地使用。幸运的是，JavaScript为表单注入交互功能的能力，可以帮助我们构建容易使用并且确保更精确的访问者反馈的表单。

9.1　理解表单

HTML提供了各种标签来构建如图9-1所示的一个表单。最重要的标签是＜form＞标签，它定义了表单的开始（开始＜form＞标签）和结束（结束＜/form＞标签）。它还指明了表单用来发送数据的方法是什么（post或get），并且指明了表单数据应该发送到Web表单上的何处。

可以使用＜input＞、＜textarea＞或＜select＞标签创建真正的表单控件，包括按钮、文本字段和菜单。大多数的表单元素都使用了＜input＞标签。例如，文本字段、密码字段、单选按钮、复选框和提交按钮都拥有＜input＞标签，并且我们通过type属性来指定要使用具体哪种表单元素。例如，要使用＜input＞标签创建一个文本字段，并将其type属性设置为text：

图　9-1　一个基本的表单可以包含很多不同类型的控件，包括文本字段、单选按钮、复选框、菜单列表、提交按钮等。HTML表单字段的一个完整列表以及如何使用它们的说明，可以在www.w3schools.com/html/html_forms.asp找到

＜input name="user"type="text"＞

如下的HTML创建了图9-1所示的表单。＜form＞标签和表单元素用粗体表示：

＜form action="process.php"method="post"name="signup"id="signup"＞

＜div＞

＜label for="username"class="label"＞Name＜/label＞

＜input name="username"type="text"id="username"size="36"＞

＜/div＞

＜div＞＜span class="label"＞Hobbies＜/span＞

＜input type="checkbox"name="hobby"id="heliskiing"value="heliskiing"＞

＜label for="heliskiing"＞Heli-skiing＜/label＞

＜input type="checkbox"name="hobby"id="pickle"value="pickle"＞

＜label for="pickle"＞Pickle eating＜/label＞

＜input type="checkbox"name="hobby"id="walnut"value="walnut"＞

＜label for="walnut"＞Making walnut butter＜/label＞

＜/div＞

＜div＞

＜label for="planet"class="label"＞Planet of Birth＜/label＞

＜select name="planet"id="planet"＞

＜option＞Earth＜/option＞

＜option＞Mars＜/option＞

＜option＞Alpha Centauri＜/option＞

＜option＞You've never heard of it＜/option＞

＜/select＞

＜/div＞

＜div class="labelBlock"＞Would you like to receive annoying e-mail from

us?＜/div＞

＜div class="indent"＞

＜input type="radio"name="spam"id="yes"value="yes"checked="checked"＞

＜label for="yes"＞Yes＜/label＞

＜input type="radio"name="spam"id="defnitely"value="defnitely"＞

＜label for="definitely"＞Definitely＜/label＞

＜input type="radio"name="spam"id="choice"value="choice"＞

＜label for="choice"＞Do I have a choice?＜/label＞

＜/div＞

＜div＞

＜input type="submit"name="submit"id="submit"value="Submit"＞

＜/div＞

＜/form＞

注意：这个示例中的＜label＞标签是表单常用的另一个标签。但是，它不会创建像按钮一样的一个表单控件。它允许我们在页面上添加一个可见的文本标签，它说明了表单控件的作用。

9.1.1　选择表单元素

我们已经在本书中多次见到，操作页面上的元素首先需要选择那些元素。例如，要确定存储在一个表单字段中的值，我们必须选择该字段。同样，如果想要隐藏或显示表单字段，必须使用JavaScript来标识这些元素。

正如我们已经介绍过的，jQuery可以使用几乎任何的CSS选择器来选择页面元素。选择单个元素的最容易的方法是给它赋一个ID，如下所示：

＜input name="user"type="text"id="user"＞

或者，可以使用jQuery的选择函数：

var userField=$（'#user'）；

一旦选择了一个字段，我们可以对它做一些事情。例如，假设想要确定一个字段中的值，检查访问者是否输入了一个字段。如果表单字段有一个ID为user，可以使用jQuery来访问该字段值，如下所示：

var fieldValue=$（'#user'）.val（）；

注意：jQuery val（）函数将在9.1.2节讨论。

但是，如果想要选择一种特定类型的所有表单元素，该怎么办呢？例如，可能想要给页面上的每个单选按钮添加一个click事件。

由于＜input＞标签用于单选按钮、文本字段、密码字段、复选框、提交按钮、重置按钮和隐藏字段，你不能只是选择＜input＞标签。相反，需要能够找到特定类型的一个＜input＞标签。

幸运的是，jQuery已经承担了选择特定类型的表单字段的负担（参见表9-1）。使用一个jQuery表单选择器，可以很容易地找到和使用一个特定类型的所有字段。例如，假设当访问者提交了一个表单，而我们想要检查该表单以确保所有文本字段都保存了一些值。你需要选择这些文本字段，然后挨个查看，看看每一个的字段是否包含一个值。可以像下面这样选择所有的文本字段：

$（'：text'）

然后，直接使用.each（）函数来遍历结果（参见4.10节），以确保每个字段中都有一个值（将会在9.4节学习页面上字段验证的更多知识）。

也可以把表单选择器和其他选择器组合使用。例如，假设在页面上有两个字段，并且我们想要只选择一个表单中的文本字段。假设带有需要的字段的表单有一个ID为signup，那么可以只选择该表单中的文本字段，如下所示：

$（'#signup：text'）

此外，jQuery提供了一些有用的过滤器，可以找到和一个特定状态匹配的表单字段：

·：checked选择选中标记或打开的所有字段，也就是说，复选框和单选按钮。例如，如果想要找到打开的所有复选框和单选按钮，可以使用如下代码：

$（'：checked'）

更好的是，可以使用这个过滤器来找到一组单选按钮中哪一个是选中的。例如，假设有一组单选按钮（“Pick a delivery method”），它们拥有不同的值（例如，UPS、USPS和FedEx），并且你想要找出访问者已经选择的单选按钮的值。一组相关的单选按钮都共享同样的HTML name属性，假设有一组单选按钮其name都为shipping。可以使用jQuery的属性选择器（参见4.6.2节的介绍）和：checked过滤器来找到选中的单选按钮的值：

var checkedValue=$（'input[name="shipping"]：checked'）.val（）；

选择器$（'input[name="shipping"]'）选择所有name为shipping的input元素，但是，添加了：checked后的$（'input[name="shipping"]：checked'），只选择选中的一项。val（）函数返回了该选择框中存储的值，例如，USPS。

·：selected选择一个列表或菜单中的所有选中的选项，它允许找到访问者从一个菜单或列表（＜select＞标签）做出的选择。例如，假设有一个ID为state的＜select＞标签，列出了美国的50个州。要找到访问者选择了哪个州，可以这样编写：

var selectedState=$（'#state.：selected'）.val（）；

注意，和：checked过滤器的例子不同，ID名和过滤器之间有一个空格（'#state：selected'）。这是因为这个过滤器选择＜option＞标签，而不是＜select＞标签。也就是说，这个jQuery选择意味着“找到ID为state的＜select＞标签中的所有选中的选项”。空格使得它像一个CSS子孙选择器一样工作：首先，它找到带有正确的ID的元素，然后在其中查找选中的任何元素。

注意：可以为一个＜select＞菜单激活多个选项。这意味着：selected过滤器可能会返回多个元素。

9.1.2　获取和设置表单元素的值

有时候，我们需要查看表单元素的值。例如，我们可能想要查看一个文本字段，以确保其中输入的是一个E-mail地址。或者，我们可能想要确定一个字段值来计算订单的总额。另外，可能想要设置表单元素的值。例如，假设有一个要求有账单信息和配送信息的订单表单。给访问者一个“Same as billing”的复选框，并且使得配送信息自动使用账单字段的信息来填充。

jQuery提供了一个简单的函数来完成这两项任务。val（）既可以设置表单字段的值，也可以读取表单字段的值。如果调用这个函数而不传递任何参数，它读取字段的值；如果给该函数传递一个值，它设置表单字段的值。例如，假设有一个字段收集用户的E-mail地址，其ID为email。可以像下面这样获取该字段的内容：

var fieldValue=$（'#email'）. val（）；

可以直接传递一个值给val（）函数，从而设置字段的值。例如，假设有一个表单用于订购产品，并且想要根据访问者指定的数量来计算一笔销售的总金额（如图9-2所示）。可以获取访问者提供的数量，将其和产品的价格相乘，然后用这个值设置总金额字段。

图9-2所示的表单获取数量并设置表单的总金额字段的代码如下：

1 var unitCost=9.95；

2 var amount=$（'#quantity'）.val（）；//get value

3 var total=amount*unitCost；

4 total=total.toFixed（2）；

5$（'#total'）.val（total）；//set value

第1行代码创建了一个变量，其中存储了产品的价格。第2行代码创建了另一个变量，获取了访问者在ID为quantity的字段中输入的数量。第3行代码通过把订购数量和单价相乘得到了总金额，第4行代码把结果格式化为包含两位小数的格式（参见14.3.4节对toFixed（）方法的介绍）。最后，第5行把ID为total的字段的值设置为总金额（将在本书9.1.4节学习如何触发这些代码）。

图　9-2　jQuery使得我们很容易获取表单字段的值和设置表单字段的值

9.1.3　确定按钮和选框是否选中

尽管val（）函数对于获取任何表单元素的值很有帮助，但对于某些字段，只有访问者已经选择了字段，其值才是重要的。例如，单选按钮和复选框要求访问者通过选择一个特定的值来做出选择。我们在9.1.1节见到了如何使用：checked过滤器来找到选中的单选按钮或复选框，但是，一旦找到它，需要一种方法来确定一个特定按钮或选框的状态。

在HTML中，checked属性确定一个特定的元素是否选中。例如，要在Web页面载入的时候打开一个选框，可以像下面这样为XHTML添加checked属性：

＜input type="checkbox"name="news"id="news"checked="checked"/＞

向下面这样为HTML 5添加checked属性：

＜input type="checkbox"name="news"id="news"checked＞

由于checked是一个HTML属性，因此可以很容易地使用jQuery来选中选框的状态，如下所示：

if（$（'#news'）.attr（'checked'））{

//the box is checked

}else{

//the box is not checked

}

如果选框选中，代码$（'#news'）.attr（'checked'）返回true；如果选框没有选中，它返回值undefined, JavaScript解释器把这个值看做等同于false。因此，如果选框选中，这个基本的条件语句允许我们执行一组任务，而如果选框关闭，执行另一组任务（如果需要复习有关条件语句的知识，请参见3.1.1节）。checked属性也适用于单选按钮。我们可以以同样的方式使用attr（）函数来检查单选按钮的checked属性是否设置。

9.1.4　表单事件

正如本书第5章所介绍，事件允许我们通过响应各种不同的访问者动作来为页面添加交互。表单和表单元素可以对很多不同的事件做出反应，因此，可以借助广泛的事件来使得表单智能地响应访问者的动作。

Submit

无论何时，当输入一个文本字段后访问者通过单击一个提交按钮或者按下回车键来提交表单的时候，submit事件就触发了。我们可以借助这个事件在表单提交的时候运行脚本。通过这种方式，JavaScript可以验证表单字段，以确保它们都正确填充。当提交表单的时候，一个JavaScript程序会检查字段，如果有问题，JavaScript会停止表单提交并且让访问者知道出现了什么错误；如果没有问题，表单会正常提交。

为了在表单提交事件触发的时候运行一个函数，首选选择表单，然后使用jQuery的submit（）函数添加脚本。例如，假设想要确保在图9-1所示的表单提交的时候，其上的name字段有内容，换句话说，访问者不能让这个字段为空。可以通过为表单添加一个submit事件并在表单提交之前检查该字段的值来做到这一点。如果该字段为空，我们想要让访问者知道并且停止提交过程；否则将允许表单继续提交。如果查看9.1节开始处给出的该表单的HTML，可以看到该表单有一个ID为signup，并且name字段有一个ID为username。因此，可以使用jQuery像下面这样来验证这个表单：

1$（document）.ready（function（）{

2$（'#signup'）.submit（function（）{

3 if（$（'#username'）.val（）==’'）{

4 alert（'Please supply a name in the Name field.'）；

5 return false；

6}

7}）；//end submit（）

8}）；//end ready（）

第1行代码设置了必需的$（document）.ready（）函数，以使得代码在页面的HTML已经载入之后才运行（参见本书5.4.1节的介绍）。第2行代码给表单的submit事件添加了一个函数（如果需要回顾如何使用事件的知识，参见5.1节）。第3行到第6行代码是验证例程。第6行代码检查字段的值是否为一个空字符串（''），即该字段是否为空。如果该字段中没有内容，就会显示一个警告框，让访问者知道出错了。第5行代码非常重要：它防止表单被提交。如果忽略了这个步骤，那么，表单无论如何都会提交，即便没有访问者的名字。第6行代码完成了条件语句，第7行代码结束了submit（）函数。

提示：也可通过使用事件对象的preventDefault（）函数来阻止表单提交，参见5.4.4节的介绍。

submit事件仅适用于表单，因此，必须选择一个表单并给其添加一个submit事件函数。可以使用HTML的＜form＞标签中所提供的一个ID名来选择表单，或者，如果页面上只有一个表单，可以使用一个简单的元素选择器，例如：

$（'form'）.submit（function（）{

//code to run when form is submitted

}）；

Focus

无论何时，当某人单击表单上的一个文本字段或者跳转到一个文本字段，该字段就会接收到所谓的焦点。focus是浏览器触发的一个事件，它表示访问者的光标在一个特定字段之上或之中，据此我们可以确定访问者的注意力集中在哪里。我们可能不会经常使用这个事件，但是一些设计者使用它来擦除一个字段中已经显示的任何文本。例如，假设在一个表单中有如下的HTML：

＜input name="username"type="text"id="username"?

value="Please type your user name"＞

这段代码在表单上创建了一个文本字段，其中带有文本“Please type your user name”。这一技术使得我们能够为访问者提供说明，告诉他应该在该字段填写什么内容。当访问者把焦点放到这个字段，可以擦除文本内容，而不是迫使访问者自己动手通过填充表单来擦除内容，代码如下所示：

1$（'#username'）.focus（function（）{

2 var field=$（this）；

3 if（field.val（）==field.attr（'defaultValue'））{

4 field.val（''）；

5}

6}）；

第1行选择了字段（ID为username）并且把一个函数分配给focus事件。第2行创建一个变量field，然后把对jQuery选择的一个引用存储到其中；正如4.10.2节所讨论的，$（this）引用了一个jQuery函数中当前选择的元素，在这个例子中，就是表单字段。

第4行真正擦除字段。它把字段的值设置为一个空字符串（以两个单引号表示），因此，从字段中删除了任何内容。但是，我们不希望每次该字段获得焦点的时候都擦除这个字段。例如，假设某人来到表单并单击表单字段，第一次擦除了“Please type your user name”文本。然而，如果访问者随后在该字段中输入了自己的名字，单击这个字段之外的内容，然后，又单击回到这个字段，我们不希望他的名字突然消失。这就是第3行的条件语句的作用。

文本字段有一个名为defaultValue的属性，表示当页面初次载入的时候字段中的文本。即便擦除了文本，Web浏览器仍然记得当页面载入的时候字段中的内容是什么。这个条件语句检查字段中当前的内容（field.val（））和字段中最初的内容（field.attr（'defaultValue'））是否相同。如果相同，那么JavaScript解释器就会擦除字段中的文本。

这里用一个例子来说明整个过程。当前面所述页面的HTML初次载入的时候，文本字段中的值为“Please type your user name.”。这是字段的defaultValue。因此，当访问者初次单击字段的时候，条件语句询问“字段中当前的内容和页面初次载入时候的字段中的内容是否相同”。换句话说，“Please type your user name”还是“Please type your user name”吗？答案是肯定的，因此，字段被擦除。

然而，假设输入helloKitty作为用户名，然后跳转到另外一个字段，然后，意识到用户名输入错误。当我们单击回到字段来修改这个错误的时候，focus事件再次触发，分配给该事件的函数再次运行。这次的问题是，“Is'helloKitty'等于'Please type your username.’吗？”答案是否，因此，字段没有擦除，并且我们可以修改自己的输入错误。

Blur

当我们跳转出一个字段或者单击当前字段之外的时候，浏览器触发一个blur事件。这个事件通常和文本字段或文本区域字段一起使用，在某人单击或跳转到一个字段之外的时候，运行一个验证脚本。例如，假设有一个带有很多问题的长长的表单，其中的很多问题需要特定类型的值（例如，E-mail地址、数值、邮政编码等）。假设访问者没有正确地填充这些字段中的任意一个，但是他单击了提交按钮，他将会面对一个长长的错误列表，指出他是怎样填错了这个表单。也可以在他填写表单的同时检查字段，而不是一次把所有的错误告诉他。通过这种方式，如果他在填写过程中犯了错误，会立即注意到并且马上修改错误。

假设有一个字段用来收集访问者想要的产品的数量。HTML可能如下所示：

＜input name="quantity"type="text"id="quantity"＞

我们希望确保该字段只包含数值（例如，1、2或9，而不是One、Two或Nine）。可以在访问者单击这个字段之外后检查，如下所示：

1$（'#quantity'）.blur（function（）{

2 var fieldValue=$（this）.val（）；

3 if（isNaN（fieldValue））{

4 alert（'Please supply a number'）；

5}

6}）；

第1行给blur事件指定了一个函数。第2行获取了字段中的值并且将其存储到一个名为fieldValue的变量中。第3行使用isNaN（）方法（参见本书14.3.2节）检查以确保这个值是一个数字。如果它不是数值，那么第4行运行并显示一个警告框。

Click

当单击任何表单元素的时候，触发click事件。这个事件对于单选按钮和复选框特别有用，因为我们可以根据访问者选择的按钮来添加改变表单的函数。例如，假设有一个订单表单，分别为账单信息和配送信息提供了字段。要使得配送信息和账单信息相同的访问者不必将自己的信息输入两次，我们可以提供一个“Same as billing information”的复选框，那么，当这个复选框选中的时候，隐藏配送信息字段并且使得表单更为简单而方便阅读（参见9.3.3节的实际例子）。

和其他事件一样，可以使用jQuery的click（）函数来为表单字段的click事件指定一个函数：

$（'：radio'）.click（function（）{

//function will apply to every radio button when clicked

}）；

注意：click事件也适用于文本字段，但是它不同于focus字段。无论何时，当你单击或跳到一个文本字段中的时候，focus就会触发，而只有在单击到字段中的时候，click事件才会触发。

Change

change事件适用于表单菜单（例如，图9-1所示的“Planet of Birth”菜单）。当我们从一个菜单做出选择的时候，change事件触发。可以使用这个事件来运行一个验证函数，例如，很多设计者通常在菜单中添加一条说明作为第一个选项，例如，“Please choose a country.”。为了确保访问者没有选择一个国家然后突然把菜单改变回第一个选项（“Please choose a country”），我们可以在每次某人从菜单做出新的选择的时候检查菜单的选择值。

或者，可以对表单编程，让它根据菜单选择来做出变化。例如，可以运行一个函数，无论何时，当从菜单选择一个选项时，另一个菜单的可用选项发生相应的变化。例如，图9-3展示了带有两个菜单的一个表单，从上面的菜单中选择一个选项，将会改变下面的菜单中可用颜色的列表。

要对一个菜单应用change事件，可以使用jQuery的change（）函数。例如，假设有一个菜单列出了国家的名字，而该菜单有一个ID为country，并且每次有一个新的选择的时候，我们想要确保新选择不是说明文本“Please choose a country.”，可以像下面这样做：

$（'#country'）.change（function（）{

if（$（this）.val（）=='Please choose a country'）{

alert（'Please select a country from this menu.'）；

}

}

图　9-3　当用户从菜单选择一个选项的时候，表单菜单的change事件允许我们做一些有趣的事情。在这个例子中，从上面的菜单选择一个选项会动态地改变第二个菜单中给出的选项。从上面的菜单中选择一种产品，并且第二个菜单显示该产品可用的颜色

9.2　为表单添加智能

Web表单对站点访问者提出很多要求：文本字段需要填充、要做出选择、选中复选框等。如果想要人们填充你的表单，应该让表单尽可能简单。幸运的是，JavaScript可以做很多事情，使得Web表单易于使用。例如，可以隐藏表单字段直到需要它们的时候才显示，把不适用的表单字段变为灰度，并且根据表单的选择计算总值。JavaScript提供了无数的方法来提高表单的易用性。

9.2.1　聚焦表单的第一个字段

通常，要开始填充一个表单，必须单击第一个文本字段并开始录入。在带有登录表单的一个页面上，何必让访问者在可以输入之前还要多此一举地把鼠标光标移动到位置并单击登录字段呢？为什么不恰好把光标放在字段中，准备好立即接收他们的登录信息呢？使用JavaScript，很容易做到这点。

秘密在于focus，它不仅仅是可以响应的一个JavaScript事件，而且是可以用来把光标放置到一个文本字段中的命令。我们直接选择文本字段，然后运行jQuery focus（）函数。例如，假设在图9-1所示的页面载入的时候，我们希望光标在name字段中。如果看一下9.1节开始处的表单的HTML，你将会看到该字段的ID是username。因此，JavaScript把焦点（也就是光标）放置到该字段上，如下所示：

$（document）.ready（function（）{

$（'#username'）.focus（）；

}）；

在这个例子中，文本字段的ID是username。然而，可以创建一个通用的脚本，它总是聚焦到表单的第一个文本字段上，而不一定必须给字段分配一个ID：

$（document）.ready（function（）{

$（'：text：frst'）.focus（）；

}）；

正如9.1.1节所介绍的，jQuery提供了一个方便的方法来选择所有的文本字段，即$（'：text'）。此外，通过为任何选取器添加：first，你可以选择该元素的第一个实例，以使得jQuery选择器$（'：text：first'）选择页面上的第一个文本字段。添加.focus（），会随后将光标放置在该文本字段，以便耐心地等待访问者填写该字段。

如果页面上有多个表单（例如，一个“search this site”表单、一个“signup for our newsletter”表单），我们需要修改选择器来指明应该聚焦到哪个表单的文本字段。例如，假设想要光标在一个登录表单的第一个文本字段中闪烁，以准备好供用户输入，而不是在查找表单的第一个文本字段中。要聚焦到登录表单的文本字段，只要给表单添加一个ID（例如，signup），然后就可以使用如下的代码：

$（document）.ready（function（）{

$（'#signup：text：frst'）.focus（）；

}）；

现在，选择器$（'#signup：text'）只是选择登录表单中的文本字段。

9.2.2　关闭或打开字段

表单字段通常是用来填充的，毕竟，如果不能在一个文本字段输入，那它有什么用处呢？然而，有的时候我们不希望访问者能够填充一个文本字段、选中一个复选框或者从菜单选择一个选项。假设有一个字段，只有在前一个复选框选中的时候才能够填充。例如，在用来确定美国收入税的1040表单上，有一个字段用来收集你的配偶的社会安全号码。只有在你已经结婚的情况下，才应该填充该字段。

要“关闭”一个不应该填充的表单字段，可以使用JavaScript来关闭它。关闭一个字段意味着它不能被选中（单选按钮或复选框），不能录入（文本字段），不能选择（菜单）或单击（提交按钮）。一些浏览器还会改变表单控件的颜色，使用灰色来使其淡化。

要关闭一个表单字段，直接把字段的disabled属性设置为true。例如，要关闭表单上所有的输入字段，可以使用如下的代码：

$（'：input'）. attr（'disabled'，true）；

通常会关闭一个字段来响应一个事件。以1040表单为例，当单击了“single”按钮的时候，可以关闭收集配偶的社会安全号码的字段。假设声明你是单身的单选按钮的ID为single，而收集配偶的社会安全号码的字段的ID为spouseSSN，那么JavaScript代码如下所示：

$（'#single'）.click（function（）{

$（'#spouseSSN'）.attr（'disabled'，true）；

}）；

当然，如果关闭一个字段，可能希望有一种方式能够再次打开它。要做到这点，直接把disabled属性设置为false。例如，要打开表单上的所有字段：

$（'：input'）.attr（'disabled'，false）；

注意：当关闭一个表单字段的时候，确保使用布尔值（参见2.3.3节）true或false，而不是字符串'true'或'false'。例如，下面的代码是错误的：

$（'：input'）.attr（'disabled'，'false'）；

正确的形式是：

$（'：input'）.attr（'disabled'，false）；

再回到收入税的例子：如果访问者选择了“married”选项，需要确保收集配偶的社会安全号码的字段是激活的。假设结婚选项按钮的ID为married，可以添加如下代码：

$（'#married'）.click（function（）{

$（'#spouseSSN'）.attr（'disabled'，false）；

}）；

我们将在9.3.1节的教程中看到使用这一技术的一个例子。

常见问题阻止多次提交

有时候，我们得到相同的表单信息的多次提交。如何防止这种情况发生呢？

Web服务器不会总是最快的生物，Internet也不是。通常，在访问者按下表单的提交按钮和一个新的“We got your info”页面出现之间，总是有一段延迟。有时候，这段延迟真的很长，并且不耐烦的Web冲浪者会再次（或者三次、四次地）单击提交按钮，他们会以为表单的第一次提交无效。

这种现象可能导致同样的信息提交两次。在网上商店的例子中，这也可能意味着一张信用卡付两次费。幸运的是，使用JavaScript，一旦表单提交过程开始了，有一种简单的方法来关闭一个提交按钮。使用提交按钮的disabled属性，我们可以将其关闭，以避免再次单击它。假设该表单有一个ID为formID，而提交按钮有一个ID为submit。首先，给表单添加一个submit（）函数，然后，在该函数中关闭提交按钮，如下所示：

$（'#formID'）.submit（function（）{

$（'#submit'）.attr（'disabled'，true）；

}）；

如果该页面只有一个表单，你甚至不需要为每个标签使用ID：

$（'form'）.submit（function（）{

$（'input[type=submit]'）.

attr（'disabled'，true）；

}）；

此外，可以通过修改按钮的值来改变提交按钮上的消息。例如，按钮首先显示“Submit”，但是当表单提交的时候，按钮就改变为显示“sending information”。可以像下面这样做：

$（'#formID'）.submit（function（）{

var subButton=$（this）.

find（'：submit'）；

subButton.attr（'disabled'，true）；

subButton.val（'……sending

information'）；

}）；

确保把这段代码放入到$（document）.ready（）函数中，关于该函数的介绍参见5.4.1节。

9.2.3　隐藏和显示表单选项

除了关闭一个字段外，还有另外一种方法来确保访问者不必浪费时间填写不必要的字段，只需要隐藏那些不需要的字段。例如，以上一节中的税收表单为例，我们可能希望当“single”选项选中的时候隐藏配偶的社会安全号码的字段，而当“married”选项打开的时候则显示该字段。可以像下面这样做：

$（'#single'）.click（function（）{

$（'#spouseSSN'）.hide（）；

}）；

$（'#married'）.click（function（）{

$（'#spouseSSN'）.show（）；

}）；

提示：关于jQuery的hide（）和show（）函数（以及显示和隐藏元素的其他函数）参见6.1.1节。

隐藏字段（相对于仅仅关闭它）的一个可用性方面的好处是，这使得表单的布局更加简单。毕竟，关闭的按钮仍然是可见的并且仍然能够吸引（或者更准确地说，转移）人们的注意力。

在很多情况下，我们希望隐藏或显示多个字段：可能还想要隐藏字段的标签以及与其相关的任何其他文本。一种方法是把想要隐藏的代码（字段、标签以及任何其他的HTML）包含到一个＜div＞标签中，给该＜div＞添加一个ID，然后隐藏这个＜div＞。我们将会在下面的教程中看到使用这一技术的一个例子。

9.3　教程：基本表单扩展

在这个教程中，我们将为一个基本订购表单进行3个可用性改进，该表单由收集账单信息和配送信息的字段所组成。首先，在页面载入的时候，我们将把文本光标放在表单的第一个字段中。其次，我们将根据访问者做出的选择来关闭或恢复表单字段。最后，当表单的某个部分不需要的时候，我们将其整个隐藏起来（参见图9-4）。

图　9-4　使用JavaScript，可以增加Web表单的可用性并且增加交互式功能，例如隐藏不需要的字段和关闭不应该填充的字段

注意：参见1.3节中的“注意”部分以获取如何下载教程文件的信息。

9.3.1　聚焦到一个字段

本教程的订购表单页面的第一个字段收集了下订单的人的名字（如图9-4所示）。

为了使得表单更容易填写，页面载入的时候，我们把光标放入到这个字段中。

1.在文本编辑器中打开chapter09文件夹下的文件form.html。

这个文件已经包含了jQuery文件的一个链接以及$（document）.ready（）函数（参见6.4.1节）。这里有一个包含了两个部分的表单，一个部分收集账单信息，另一个部分收集配送信息（在继续之前，检查Web浏览器中的页面）。

第一步是聚焦到字段（实际上，也是教程的这一部分的唯一的步骤）。

2.在$（document）.ready（）函数后面的空行上单击，然后输入$（'#name'）.focus（）；，代码如下所示：

$（document）.ready（function（）{

$（'：text：frst'）.focus（）；

}）；//end ready（）

第一个字段有一个ID为name，因此，对该字段应用focus（）函数使得浏览器在这里放置一个插入点，这是很简单的事情。

保存文件并在Web浏览器中预览。

当页面载入的时候，第一个字段应该有一个闪烁的光标，意味着这个字段拥有了焦点，并且我们可以立即开始填写它。

9.3.2　关闭表单字段

上一部分只是热身。在教程的这一部分中，我们将根据表单的选择来关闭或打开两个表单字段。如果在Web浏览器中预览该表单（或者仅仅是看图9-4），将会看到在表单的账单信息部分的末尾，有选择支付方式的3个单选按钮：PayPal、Visa和MasterCard。此外，下面还有两个字段用来收集卡号和有效日期。这两个选项只适用于信用卡，而不适用于PayPal支付，因此，当单击PayPal按钮的时候，我们要关闭这两个字段。

页面的这部分的HTML如下所示（表单字段用粗体显示）：

1＜div＞＜span class="label"＞Payment Method＜/span＞

2＜input type="radio"name="payment"id="paypal"value="paypal"＞

3＜label for="paypal"＞PayPal＜/label＞

4＜input type="radio"name="payment"id="visa"value="visa"＞

5＜label for="visa"＞Visa＜/label＞

6＜input type="radio"name="payment"id="mastercard"value="mastercard"＞

7＜label for="mastercard"＞MasterCard＜/label＞

8＜/div＞

9＜div id="creditCard"class="indent"＞

10＜div＞

11＜label for="cardNumber"class="label"＞Card Number＜/label＞

12＜input type="text"name="cardNumber"id="cardNumber"＞

13＜/div＞

14＜div＞

15＜label for="expiration"class="label"＞Expiration Date＜/label＞

16＜input type="text"name="expiration"id="expiration"＞

17＜/div＞

18＜/div＞

3.返回到文本编辑器和文件form.html。

添加在前面部分创建的代码。首先，给PayPal单选按钮的click事件分配一个函数。

4.找到页面顶部的脚本，添加如下粗体所示的代码：

$（document）.ready（function（）{

$（'：text：first'）.focus（）；

$（'#paypal'）.click（function（）{

}）；//end click

}）；//end ready（）

PayPal选项的单选按钮有一个ID为paypal（参见HTML代码的第2行），因此，选择该字段只需要输入$（'#paypal'）。代码剩下的部分把一个匿名函数分配给了click事件（如果对此不是很明白，请参考5.2节关于给事件分配函数的介绍）。换句话说，单击PayPal单选按钮不仅会选中它（这是常规的Web浏览器行为），而且会触发我们将要创建的函数。

接下来，关闭信用卡号码和有效日期字段，因为当选择PayPal选项的时候，它们都不适用。

5.在上一步所添加的匿名函数内添加新的一行代码（下面的第4行）：

1$（document）.ready（function（）{

2$（'：text：first'）.focus（）；

3$（'#paypal'）.click（function（）{

4$（'#creditCard input'）.attr（'disabled'，true）；

5}）；//end click

6}）；//end ready（）

尽管我们想要关闭两个表单字段，但是有一种简单的方式只需要一行代码就可以做到这点。两个表单字段都在一个ID为creditCard的＜div＞标签中（参见上面的HTML代码的第9行）。因此，jQuery选择器$（#creditCard input'）解释为“选择ID为creditCard的一个元素中的所有＜input＞标签”。这种灵活的方法确保我们选择了所有的输入字段，因此，如果添加另一个字段，例如CVV字段，它也会被选中（CVV是信用卡后面的3个数字，Web页面常常要求输入这3个数字以加强网上订购的安全性）。

要关闭这些字段，只需要把disabled属性设置为true（参见9.2.2节）。然而，这对文本标签（“Card Number”和“Expiration Date”）没有什么影响。即便字段本身关闭了，这些文本标签仍然是粗黑体，这会传递潜在的令人混淆的信息：访问者可以填写这些字段。为了让关闭的状态更清晰，可以把标签改变为灰度。既然如此，也可以给字段添加一个灰色的背景，使它们看上去是关闭的（大多数浏览器已经这么做了，但是在Internet Explorer中，关闭的文本字段和其他激活的字段外观上没什么不同）。

6.把粗体所示的代码添加到脚本中：

1$（document）.ready（function（）{2

$（'：text：first）.focus（）；3

$（'#paypal'）.click（function（）{

4$（'#creditCard input'）.attr（'disabled'，true）8

5.css（'backgroundColor'，’#CCC'）；

6$（'#creditCard label'）.css（'color'，’#BBB'）；

7}）；//end click

8}）；//end ready（）

注意：第4行代码末尾的8符号表示我们应该把下一行当做前一行的一部分输入。因为真正的一行JavaScript代码很长，无法在本书一页的宽度中显示的，所以将其分为两行书写。然而，正如5.4.2节所述，当遇到换行和空白的时候，JavaScript很灵活，因此，如果你像下面这样，将一条JavaScript语句划分为多行，实际上是完全可以接受的（并且，有时候还易于阅读）：

$（'#creditCard input'）.attr（'disabled'，true）

.css（'backgroundColor'，’#CCC'）；

注意，有些程序员在多行都缩进代码保持格式，在这种情况下，缩进.css（）函数以便它和.attr（）函数对齐。

首先，我们使用jQuery的css（）函数来改变文本字段的背景颜色（注意，这段代码是第4行的一部分，因此，我们应该将其和attr（）函数输入到同一行）。接下来，我们使用css（）函数来调整＜div＞标签中的任何＜label＞标签的字体颜色（css（）在4.8.2节中介绍）。

如果此时在Web浏览器中预览该页面，将会看到单击PayPal按钮确实关闭了信用卡号码和有效日期字段，并且使得标签文本变为灰度。然而，如果单击了Visa或MasterCard按钮，字段仍然是激活的。当这两个单选按钮中的任何一个选中的时候，我们需要重新激活字段。

7.在click（）函数的后面添加一个新的空行（将在步骤6中的第7行和第8行之间添加新代码），然后，添加如下代码：

$（'#visa，#mastercard'）.click（function（）{

$（'#creditCard input'）.attr（'disabled'，false）?

.css（'backgroundColor'，’'）；

$（'#creditCard label'）. css（'color'，’'）；}）；//end click选择器$（'#visa，#mastercard'）选择了另外的两个单选按钮（参见9.3.2节中的HTML的第4行和第6行）。注意，为了删除单击PayPal按钮所添加的背景颜色和文本颜色，我们直接传递一个空字符串作为颜色值：$（'#creditCard label'）.css（'color'，"）；。这会删除元素的颜色，但是为样式表中最初定义的颜色留下位置。

我们快要完成这个教程了。在最后一部分中，我们将完成根据表单选择隐藏页面的一部分的功能。

jQuery插件提示使得选择日期更容易

如果你加入一个社会网络站点、预订飞机票，或者查找活动日历，会经常遇到要求输入一个日期的表单。在大多数情况下，你将会看到一个基本的文本字段，要求在其中输入一个日期。遗憾的是，我们不会总是知道从现在开始的下下个星期五的日期是什么。此外，一个空的文本字段意味着访问者可以以任何想要的格式输入一个日期，包括10-20-2009、10.20.2009、10/20/2009，甚至是20/10/2009。使得选择日期更容易并且确保能够接收同样格式的日期的最好办法是使用一个日历挂件，这是一个弹出式日历，它允许访问者通过单击日历上的某天来选择一个日期。尽管日历挂件很有用，但通过编程来实现它们本身还是很困难的。幸运的是，我们可以下载一个jQuery插件，它使得给表单添加一个日历挂件变为小菜一碟。

jQuery UI Datepicker插件是一个高级的日期选取的弹出式日历，你可以以多种方式来定制它。要使用它，你需要从位于http：//jqueryui.com/的jQuery UI站点获取该插件。可以使用下载页面（http：//jqueryui.com/download）仅获取日期选择器，或者，如果你喜欢这个jQuery UI插件的其他元素，也可以选择它们。也可以使用jQuery UI Themeroller（http：//jqueryui.com/themeroller/）创建一个定制的样式表。

一旦下载了这些文件，将jQuery UI CSS文件添加到页面，添加jQuery库（参见本书4.3节），然后，连接到jQuery UI文件（参见本书1.2节连接到一个外部JavaScript文件的方法）。你将会在本书10.2节了解到关于jQuery UI的更多内容。

在设置好所有这些基本步骤之后，只需要对一个文本字段应用datepicker（）函数。例如，假设有一个表单和ID为dateOfBirth的一个文本字段。为了使得当某人在字段内部单击的时候弹出一个日历，给基本的$（document）.ready（）函数添加一个＜script＞标签（参见5.4.1节对此的介绍），并像下面这样调用Datepicker：

$（'#dateOfBirth'）.datepicker（）；

当然，$（'#dateOfBirth'）是选择文本字段的一种旧的jQuery方法，datepicker（）函数随后处理剩下的事情。Datepicker插件支持的选项包括选择日期范围、通过单击日历图标打开弹出日历等。要了解这个有用的插件，请访问http：//marcgrabanski.com/code/ui-datepicker/。

9.3.3　隐藏表单字段

和很多产品订购表单上一样，本教程的表单包含了单独的字段来收集账单信息和配送信息。在很多情况下，这些信息完全相同，因此，如果是这样的话，没必要让访问者把这两个字段都填上。我们经常会在这样的表单上看到一个“Same as billing”复选框，表示两组字段的信息是相同的。然而，如果在不需要配送信息字段的时候完全隐藏它们，岂不是更有用吗（更别说改变其颜色）？使用JavaScript，可以做到这一点。

1.在文本编辑器中打开文件form.html。

我们将扩展在本教程的前两个部分中编写的代码。首先，为带有标签“Same as billing.”的复选框的click事件添加一个函数。该复选框的HTML如下所示：

＜input type="checkbox"name="hideShip"id="hideShip"＞

2.把如下的代码添加到在9.3.2节的步骤4所添加的代码之后，但在脚本末尾（最后一行代码}）；//end ready（））之前。

$（'#hideShip'）.click（function（）{

}）；//end click

由于该复选框有一个ID为hideShip，上面的代码选择它并给click事件添加一个函数。在这个例子中，当复选框选中的时候，我们希望整组字段都消失，而不只是隐藏单个的字段。为了使得这更容易做到，组成配送信息字段的HTML包围在ID为shipping的一个＜div＞标签中：要隐藏这个字段，只需要隐藏该＜div＞标签。

然而，我们仍然只想在复选框选中的时候隐藏这些字段。如果某人再次单击复选框而未选中它，＜div＞标签及其表单字段都应该返回。因此，第一步是搞清楚复选框是否选中。

3.添加如下粗体所示代码：

$（'#hideShip'）.click（function（）{

if（$（this）.attr（'checked'））{

}

}）；//end click

一条简单的条件语句（参见3.1.1节）使得测试复选框的状态并且隐藏或显示表单字段变得很容易。$（this）指向被单击的对象，在这个例子中就是复选框。元素的checked属性使我们能够知道复选框是否选中了。如果选中了，那么这个属性返回true；否则，它返回false。要完成这段代码，只需要添加隐藏和显示表单字段的步骤。

4.把如下粗体所示的代码（第16～18行）添加到脚本中。完成后的脚本如下所示：

1＜script＞

2$（document）.ready（function（）{

3$（'：text：first'）.focus（）；

4$（'#paypal'）.click（function（）{

5$（'#creditCard input'）.attr（'disabled'，true）8

6.css（'backgroundColor'，’#CCC'）；

7$（'#creditCard label'）.css（'color'，’#BBB'）；

8}）；//end click

9$（'#visa，#mastercard'）.click（function（）{

10$（'#creditCard input'）.attr（'disabled'，false）8

11.css（'backgroundColor'，’'）；

12$（'#creditCard label'）.css（'color'，’'）；

13}）；//end click

14$（'#hideShip'）.click（function（）{

15 if（$（this）.attr（'checked'））{

16$（'#shipping'）.slideUp（'fast'）；

17}else{

18$（'#shipping'）.slideDown（'fast'）；

19}

20}）；//end click

21}）；//end ready（）

22＜/script＞

$（'#shipping'）引用了带有表单字段的＜div＞标签，而slideUp（）和slideDown（）函数（参见6.1.3节的介绍）通过将＜div＞标签向上滑出视线或者向下滑入视线来隐藏和显示它。你可以尝试一些其他的jQuery效果，例如fadeIn（）和fadeOut（），或者，甚至可以使用animate（）函数（参见本书6.3节）来创建自己的定制动画。

这个教程的完整版本complete_form.html位于chapter09目录下。如果你的版本不能够工作，将自己的代码和完成的教程比较一下，或者参考1.6节介绍的排错步骤。

9.4　表单验证

检查已经通过Web站点上的一个表单提交的反馈，却只发现访问者没有提供一个名字、E-mail地址或者一些其他的关键信息片段，这可能会令人沮丧。这是为什么呢?根据我们所创建的表单的类型，可能要求某些信息是必需的。

例如，如果潜在的读者没有输入一个E-mail地址来接受新闻组，那么用来提交一个E-mail新闻组的表单就不会有太大用处。同样，如果需要一个配送地址来发送一份目录或产品，就会希望确保访问者在表单上输入了他的地址。

此外，当从Web表单接收数据的时候，我们希望接收的数据格式正确，例如，表示订购数量的一个数字，或者是一个Web地址的格式正确的URL。确保访问者正确地输入信息，这叫做表单验证，使用JavaScript，可以在访问者提交不正确的信息之前识别出任何错误。

基本上，表单验证要求在表单提交之前检查表单字段，以确保提供了必需的信息，并且信息的格式是正确的。当访问者单击一个submit按钮或者当光标在文本字段中时按下回车键，表单的submit事件触发，而这通常是进行验证的时候。如果一切正确，表单信息会提交到Web服务器，就像通常的情况一样。然而，如果有问题，脚本停止提交过程并且在页面上显示错误，通常是在靠近有问题的表单字段的地方显示（如图9-5所示）。

图　9-5　当注册一个Yahoo账户的时候，如果没有正确地填充表单，会遇到很多红色的错误消息（带圈的部分）

检查以确保一个文本字段已经填充是很容易的。正如9.1.2节所介绍的，我们可以直接访问字段的value属性（例如，使用jQuery val（）函数），并且，如果值是一个空字符串，那么字段就是空的。但是，当检查其他类型的字段，例如复选框、单选按钮和菜单的时候，就需要一些技巧了。此外，当我们想要检查以确保访问者提交特定类型的信息的时候，例如E-mail地址、邮政编码、数值、日期等，需要编写一些复杂的JavaScript。幸运的是，不需要自己编写这些代码，Web上有丰富的表单验证脚本，并且其中最好的一个就是jQuery库的一个插件。

9.4.1　jQuery Validation插件

Validation插件（http：//bassistance.de/jquery-plug-ins/jquery-plug-in-validation/）是由Jorn Zaefferer开发的一个功能强大但易于使用的jQuery插件。它可以检查一个表单以确保所有必需的字段都已经填充了，并且检查确保访问者的输入符合特定的需求。例如，一个数量字段必须包含数值，一个E-mail字段必须包含E-mail地址。如果访问者没有正确地填充一个表单，插件将通过显示错误消息说明问题。

以下是使用Validation插件的基本过程：

1.下载jquery.js文件，并且将其添加到包含了要验证的表单的Web页面。

阅读4.2节中的“初学jQuer y”部分了解关于下载jQuer y库的详细信息。Validation插件使用了jQuery库，因此，首先需要把jquery.js文件添加到页面。

2.下载并添加Validation插件。

可以在http：//bassistance.de/jquery-plug-ins/jquery-plug-in-validation/找到这个插件。下载将会包含很多额外的内容，包括一个演示文件、测试等。实际上只需要jquery.validate.min.js文件（你也可以在教程目录的_js文件夹下看到这个插件，名为jquery.validate.min.js，参见本书9.3节的教程）。这个文件只是一个外部JavaScript文件，因此，按照1.2节的说明把这个文件连接到页面。

3.添加验证规则。

验证规则只是表示“让这个字段成为必需的，让这个字段接收一个E-mail地址”等指令。换句话说，这个步骤就是我们指定要验证哪个字段以及如何验证的地方。有几种方法可以添加验证规则：一种简单的方法是仅仅使用HTML（参见9.4.1节），另一种更为灵活但略微复杂的方法是使用JavaScript（参见9.4.2节）。

4.添加错误消息。

这个步骤是可选的。Validation插件带有一组预先定义好的错误消息，例如，“This field is required”、“Please enter a valid date”、“Please enter a valid number”等。这些基本的信息很好而且切中要害，但是，我们想要根据自己的表单来定制它们，以便错误消息为每个表单字段提供更明确的说明，例如，“Please type your name”或“Please tell us your date of birth”。

添加错误消息的方法有两种，简单的方法在9.4.2节讨论，更为灵活的方法在9.4.3节讨论。

注意：可以按照9.4.4节介绍的方法来控制错误消息的样式和位置。

5.对表单应用validate（）函数。

这个插件包含了一个函数，它使得所有的魔术发生：validate（）。要应用这个函数，首先使用jQuery来选择表单，然后给该选择添加函数。例如，假设有一个ID为signup的表单。HTML将会如下所示：

＜form action="process. php"method="post"name="signup"id="signup"＞应用验证的简单方式是：

$（'#signup'）. validate（）；

validate（）函数可以接收许多不同的信息片段，它们影响到插件如何工作。例如，尽管可以在表单的HTML中指定验证规则和错误消息（参见下面的内容），但是也可以在调用validate（）函数的时候指定规则和错误（将在9.4.3节学习这一方法）。

一个非常基础的表单验证（包含本节所介绍的两个步骤）的完整的JavaScript代码可能会像下面这样简单：

＜script src="js/jquery-1.6.3.min.js"＞＜/script＞

＜script src="js/jquery.validate.min.js"＞＜/script＞

＜script＞

$（document）.ready（function（）{

$（'#signup'）.validate（）；

}）；//end ready

＜/script＞

提示：别忘了总是把脚本包含在jQuery的document.ready（）函数中，从而确保脚本在页面的HTML载入后运行（参见5.4.1节）。

9.4.2　基本验证

使用Validation插件很简单，只需要附加插件的JavaScript文件、给想要验证的表单元素添加几个类和title属性，并且对表单应用validate（）函数。validate（）方法是验证表单的最简单的方法，并且可能对大多数表单来说，这个方法就足够了（然而，如果我们需要控制错误消息放置在页面的何处，或者对一个表单字段应用多个规则，或者为一个文本字段设置最小字符数或最大字符数，我们将需要使用9.4.3节所介绍的高级方法）。

要添加验证，可以根据前面部分列出的基本步骤（附加jQuery和Validation插件文件等），除此之外，可以在表单字段的HTML中嵌入规则和错误消息。

添加验证规则

使用Validation验证一个字段的最简单的方法就是给表单字段分配表9-2所列出的一个或多个类名。插件通过智能编程，可以扫描每个表单字段的类名来确定它是否是所表示的验证字段，并且如果是的话，就对该字段应用特定的验证规则。

例如，假设有一个文本字段用来收集人名。基本的HTML可能如下所示：

＜input name="name"type="text"＞

要告诉插件，该字段是必需的，换句话说，除非访问者在这个字段输入了某些内容，否则表单不能提交，要给该标签添加一个required类。例如，要让这个字段成为必需的，像下面这样为该标签添加一个class属性：

＜input name="name"type="text"class="required"＞

用这种方法添加一个类实际上和CSS无关，即使通常我们给标签分配一个类来提供一种方式通过创建CSS样式表格式化该标签。在这个例子中，我们将使用一个类名给插件提供它所需要的信息，以确定对该字段应用何种验证。

注意：JavaScript验证是为那些偶然漏掉一个字段或提供错误类型的信息的访问者提供友好反馈的一种很好的方法，但是，它不是预防恶意输入的好办法。JavaScript验证很容易失效，因此，如果你想要确保不会接收到来自访问者的错误数据，你还需要实现一个服务器端验证解决方案。

要求访问者填充一个字段可能是最常见的验证任务，但是，通常我们也想确保提供的数据符合特定的格式。例如，如果询问某人需要多少个小器件，我们期待一个数值。要让字段既成为必需的并且还包含一种特定类型的值，除了添加required类，还要加上表9-2所列出的其他类中的一个。

例如，假设有一个字段询问某人的出生日期。这个信息不仅是必需的，而且还应该是日期格式。该字段的HTML可能如下所示：

＜input name="dob"type="text"class="required date"＞

注意，类名required和date之间用一个空格隔开。

如果没有包含required类，而只是使用了另外一种验证类型，例如class="date"，那么，这个字段是可选的，但如果某人在这个字段输入内容，它必须是正确的格式（一个日期）。

提示：当我们为字段信息要求一个特定格式的时候，确保在表单中包含了特别的说明，以便访问者知道应该如何添加他们的信息。例如，如果要求一个字段是日期，可以在字段附近添加一条类似“Please enter a date in the format 01/25/2009”的消息。

添加错误消息

Validation提供了通用的错误消息，以匹配它所检查到的验证问题。例如，如果一个必需的字段为空白，这个插件会显示消息“This field is required”。如果字段需要一个日期，那么会显示消息“Please enter a valid date”。然而，也可以覆盖这些基本的消息，提供自己的消息。

最简单的方法是给表单字段添加一个title属性，并且把错误消息作为title的值提供。例如，假设使用了required类来使得一个字段是必需的，如下所示：

＜input name="name"type="text"class="required"＞

要提供自己的消息，只需要添加一个title属性：

＜input name="name"type="text"class="required"

title="Please give us your name."＞

通常，Web设计师使用title属性来增强一个表单字段的可访问性，通过它提供一个说明，以便当某人的鼠标光标移动到字段上的时候显示出来，或者供屏幕阅读软件读出声。但是，对于Validation插件，我们使用title属性来提供想要显示的错误消息。这个插件扫描所有的验证字段并检查是否有一个title属性。如果有，该插件使用这个属性的值作为错误消息文本。

如果使用多种验证方法，我们应该提供一个对每种情况有意义的title。例如，如果有一个字段是必需的并且也必须是一个日期，如果访问者输入了错误的日期格式，“This field is required”这条消息就没有太多意义。下面是对于访问者使得该字段为空白或者以错误方式输入日期都有意义的一条错误消息的例子：

＜input name="dob"type="text"class="required date"8

title="Please enter a date in the format 01/28/2009."＞

通过给字段添加类名和title来添加验证规则和错误消息很容易，并且也很有效。但是，有时候，我们可能有更为复杂的验证需求，Validation提供了另一种更为高级的验证方法来为表单添加验证。例如，我们可能想要根据错误的类型提供不同的错误消息，例如，当一个字段留下空白的时候是一条消息，而当访问者输入了错误类型的信息的时候又是另一条消息。我们无法使用本节所介绍的基本验证方法来做到这些。幸运的是，Validation插件提供了另一种更为高级的方法，允许实现更为广泛的验证规则。

例如，如果想要确保输入到一个字段的最小字符数，我们必须使用这种高级方法。例如，在设置密码的时候，我们可能想要确保密码最少有6个字符的长度。

9.4.3　高级验证

Validation插件提供了另一种方法来为一个表单添加验证，而不需要改变字段的HTML。此外，这个插件提供了广泛的附加选项来控制它如何工作。我们通过给validate（）函数传递一个对象直接量（参见4.8.3节）来设置这些选项，其中包含了每个选项的单独对象。例如，要指定一条验证规则，我们包含了该规则的代码的一个对象。首先，紧跟着validation函数的第一个圆括号的后面，我们包含一个开始花括号，并且在函数的结束圆括号的前面，放置一个结束花括号。

$（'idOfForm'）.validate（{

//options go in here

}）；//end validate（）；

这对花括号表示一个对象直接量，它将包含选项设置。以这种方式使用Validation可能有点容易令人混淆，要理解插件的作者想要它如何工作，最好方法是看一个简单的示例，如图9-6所示。

图　9-6　即便使用这样一个简单的表单，也可以使用Validation插件的高级选项实现更好的控制

提示：我们可以把9.4.2节所介绍的基本验证方法和这里所介绍的高级验证方法组合到同一个表单上。对于只有一条验证规则和错误消息的字段，可以使用基本验证方法，因为它很快捷，而只是对于更为复杂的验证使用高级验证方法。例如，9.5节的教程使用这两种方法来验证一个单个的表单。

图9-6中的表单的HTML如下所示：

＜form action="process.php"method="post"id="signup"＞

＜div＞

＜label for="name"＞Name＜/label＞

＜input name="name"type="text"＞

＜/div＞

＜div＞

＜label for="email"＞E-mail Address＜/label＞

＜input name="email"type="text"＞

＜/div＞

＜div＞

＜input type="submit"name="submit"value="Submit"＞

＜/div＞

＜/form＞

这个表单包含两个文本字段，以粗体显示，一个用于人的名字，一个用于E-mail地址。本小节处理这两个字段的验证，使用高级规则来确保名字字段和E-mail字段都填充了并且格式正确。

注意：可以在http：//docs.jquery.com/Plug-ins/Validation/validate#options找到Validation插件的一个完整的选项列表。

高级规则

指定验证规则的高级方法包括，传递一个对象，其中包含了表单字段名字和验证规则或想要应用到该字段的规则（参见4.8.3节）。该对象的基本结构如下所示：

rules：{

fieldname：'validationType'

}

对象名为rules，其中，可以指定字段以及想要应用到该字段的验证类型。然后，整个对象传递给validate（）函数。例如，在图9-6所示的表单中，为了让名字字段成为必需的，可以对前面所述的字段应用validate（）函数，然后把rules对象传递给该函数，如下所示：

$（'#signup'）.validate（{

rules：{

name：'required'

}

}）；//end validate（）

在这个例子中，字段的名字为name，并且规则指定了这个字段是必需的。要对一个表单字段应用多个验证规则，必须为该字段创建另一个对象。例如，要针对图9-6中的表单扩展验证规则，可以添加一条规则，不仅使得email字段是必需的，而且也指定了E-mail地址必须是有效的格式：

1$（'#signup'）.validate（{

2 rules：{

3 name：'required'，

4 email：{

5 required：true，

6 email：true

7}

8}

9}）；//end validate（）

注意：根据JavaScript对象直接量的规则，我们必须用一个逗号来结束每个名/值对，但最后一个名/值对除外。例如，在上面代码的第3行中，name：'required'的后面必须有一个逗号，因为其他规则（用于email字段）跟在后面。参见4.8.3节回顾一下对象直接量是如何工作的。

粗体显示的第4行到第7行，指定了用于email字段的规则。这个字段的名字是email，如HTML中所指定的（参见9.4.3节），required：true使得该字段是必需的，而email：true确保字段包含一个E-mail地址。

可以使用表9-2所列出的任何的验证类型。例如，假设给这个例子使用的表单添加一个名为birthday的字段。要确保在这个字段中输入的是日期，可以像下面这样扩展规则列表：

$（'#signup'）.validate（{

rules：{

name：'required'，

email：{

required：true，

email：true

}，

birthday：'date'

}

}）；//end validate（）

如果还想让birthday字段是必需的，可以把代码做如下调整：

$（'#signup'）.validate（{

rules：{

name：'required'，

email：{

required：true，

email：true

}，

birthday：{

date：true，

required：true

}

}

}）；//end validate（）

正如前面提到的，可以对高级验证规则所做的最强大和有用的事情是，要求访问者的输入具有某个最小或最大长度。例如，在一个投诉报告表单上，我们可能想要限制评论长度，例如，200个字符，这样，客户会切中要点而不是长篇大论。也有规则可以确保输入的数字在某个范围内，因此，如果表单不是要足够容易以供古人使用的话，我们不接受1900年以前的出生日期。

·minlength。这个字段必须包含至少指定的数目的字符。例如，确保一个字段至少输入了6个字符的规则是：

minlength：6

·maxlength。这个字段必须包含不多于指定数目的字符。例如，确保输入到字段中的字符不超过100个的规则是：

maxlength：100

·rangelength。minlength和maxlength的一个组合。指定了一个字段中所允许的最小字符数和最大字符数。例如，确保一个字段包含至少6个字符而不超过100个字符的规则是：

rangelength：[6，100]

·min。要求字段包含一个数值，并且等于或大于指定的数值。例如，如下的规则要求字段包含一个数值，并且该数值大于或等于10。

min：10

在这个例子中，如果访问者输入8，字段无法通过验证，因为8小于10。同理，如果访问者输入一个单词eight，那么字段也无法通过验证，并且会产生一条错误消息。

·max。和min一样，但是指定了字段可以包含的最大的值。例如，要确保一个字段包含的值小于1000，可以使用如下规则：

max：1000

·range。组合了min和max来指定字段必须包含的最小值和最大值。例如，要确保一个字段包含至少10但不超过1000的数值，可以使用：

range：[10，1000]

·equalTo。要求一个字段内容匹配其他字段。例如，在一个登录表单上，通常要求访问者输入一个密码然后通过再次输入密码来验证它。通过这种方式，访问者可以确保第一次没有输错密码。要使用这个方法，必须指定一个字符串，其中包含有效的选择器。例如，假设第一个密码字段有一个ID为password。如果想要确保“验证密码”字段和第一个密码字段匹配，可以使用这段代码：

equalTo：’#password'

可以组合使用这些高级验证规则。只要一次处理一个字段就行了。这里有它们如何一起工作的一个例子。假设有一个表单包含了两个字段，一个用于创建密码，另一个用于确认密码。这两个字段的HTML如下所示：

＜input name="password"type="password"id="password"＞

＜input name="confirm_password"type="password"id="confirm_password"＞

这两个字段都是必需的，并且密码必须至少有8个字符，但是不超过16个字符。最后，我们要确保“确认密码”字段匹配其他的密码字段。假设表单的ID为signup，可以使用如下代码验证这两个字段：

$（'#signup'）.validate（{

rules：{

password：{

required：true，

rangelength：[8，16]

}，

confirm_password：{

equalTo：’#password'

}

}

}）；//end validate（）

高级用户提示验证服务器

尽管JavaScript验证能够很快地检查用户输入，有时候，还是需要检查服务器来看看一个字段是否有效。例如，假设有一个登录表单，它允许访问者创建自己的用户名以便在站点论坛上使用。不能有两个人具有相同的用户名，因此，如果某人想要使用的用户名在表单提交之前已经被使用了，能够通知填写表单的人才会有用。在这个例子中，必须查询服务器以确定该用户名是否可用。

Validation插件提供了一个名为remote的高级验证方法，它允许我们检查服务器。这个方法允许我们把字段名以及访问者输入到字段中的值传递给一个服务器端页面（例如一个PHP、JSP、ASP或Cold Fusion页面）。然后服务器端页面接收这些信息并对其做一些事情，例如查看用户名是否有效，然后用一个true（通过验证）或false（验证失败）响应表单。

假设有一个名为“username”的字段，它是必需的而且不能是站点上当前已经使用的一个名字。要为该字段创建一条规则（使用9.4.3节介绍的高级验证方法），可以添加如下的规则对象：

username：{

required：true，

remote：'check_username.php'

}

remote方法接收一个字符串，其中包含了从当前页面到服务器上的一个页面的路径。在这个例子中，服务器上页面的名字是check_username.php。当Validation插件试图验证这个字段，它把字段的名字（username）以及访问者的输入发送给check_username.php，然后该页面判断用户名是否可用。如果这个名字可用，PHP页面返回单词'true'；如果用户名已经使用了，这个页面返回单词'false'并且显示字段没有通过验证。

这里所有的魔术都是通过Ajax的功能实现的，我们将在本书第四部分学习Ajax。要查看这个验证方法的工作示例，请访问http：//jquery.bassistance.de/validate/demo/captcha/。

高级错误消息

正如9.4.2节所介绍的，可以通过为错误消息文本添加一个title，从而很容易地为字段添加一条错误消息。然而，这种方法不允许我们为每种验证错误都添加一条单独的错误消息。例如，假设一个字段是必需的并且其中必须有一个数值。我们可能希望对于每个错误有两种不同的消息，分别是“This field is required”和“Please enter a number.”。我们无法使用title属性来做到这点。相反，必须给validate（）函数传递一个JavaScript对象。

这个过程类似于创建高级验证规则，参见上一节的介绍。messages对象的基本结构如下：

messages：{

feldname：{

methodType：'Error message'

}}

在上面的例子中，用要验证的字段替换fieldname，用分配的验证方法替换methodType。例如，要对一个password字段使用组合验证方法，并且为每种错误提供消息，可以添加如下粗体所示的代码：

$（'#signup'）.validate（{

rules：{

password：{

required：true，

rangelength：[8，16]

}，

confirm_password：{

equalTo：’#password'

}

}，//end of rules

messages：{

password：{

required："Please type the password you'd like to use."，

rangelength："Your password must be between 8 and 16 characters long."

}，

confrm_password：{

equalTo："The two passwords don't match."

}

}//end of messages

}）；//end validate（）

提示：正如你所见到的，使用高级方法可能需要很多的对象直接量，并且，所需的{和}字符的数量，往往使得代码难以理解。在使用Validation插件的高级方法的时候，一种较好的办法是慢点来，并且经常测试。不要试图在一次热闹的输入会话中尝试所有的规则和消息，而是添加一条规则，然后测试页面。如果验证无效，你可能在某处录入错误了，因此，在继续进行并添加另一条规则之前，先修改它。一旦规则完成了并且有效，为任何的错误消息添加对象直接量。再来一次一次添加一条消息，然后，经常测试。

9.4.4　样式化错误消息

当Validation插件检查一个表单并找到一个无效表单字段，它做两件事情：第一，给表单字段添加一个类；第二，它添加包含了错误消息的一个＜label＞标签。例如，假设页面对于一个email字段有如下的HTML：

＜input name="email"type="text"class="required"＞

如果给带有这个表单的页面添加Validation插件，并且访问者试图没有填写email字段就提交表单，该插件将会阻止提交过程并且修改字段的HTML，添加一个额外的标签。新的HTML如下所示：

＜input name="email"type="text"class="required error"＞

＜label for="email"generated="true"class="error"＞

This feld is required.＜/label＞

换句话说，插件给表单字段添加了一个类名error。它还插入了带有类名error的一个＜label＞标签，其中包含了错误消息文本。

要改变错误消息的外观，只需要给样式表添加一个样式来为错误定义外观。例如，要让错误文本以粗体和红色显示，可以给样式表添加如下所示的样式：

label.error{

color：#F00；

font-weight：bold；

}

由于Validation插件也给无效的表单字段添加一个error类，也可以创建CSS样式来格式化这些字段。例如，要在无效字段的周围放置一个红色边框，可以创建如下所示的一个样式：

input.error, select.error, textarea.error{

border：1px red solid；

}

9.5　验证教程

在本教程中，我们将使用一个表单并为其添加基本验证和高级验证选项（如图9-7所示）。

注意：参见本书1.3节中的“注意”部分以获取有关如何下载教程文件的信息。

9.5.1　基本验证

在本教程中，我们将从介绍Validation插件开始，首先应用9.4.2节所介绍的基本验证方法。然后，我们将学习更为复杂的验证过程，这将使用9.4.3节介绍的高级方法。你将会看到，在同一表单上混合和匹配这两种方法完全没有问题。

图　9-7　不要让访问者不正确地提交表单。在jQuery Validation插件的协助下，我们可以确保得到想要的信息

1.在文本编辑器中打开chapter09文件夹下的文件validation.html。

这个文件包含了带有各种表单字段的一个表单，这些字段包括文本字段、复选框、单选按钮和菜单。我们将为这个表单添加验证，但是，首先需要把验证插件添加到页面中。

2.紧接在添加jquery.js文件的＜script＞标签之后的空行中输入：

＜script src="../_js/jquery.validate.min.js"＞＜/script＞

Validation插件包含在chapter09目录下一个名为validation的文件夹中。

这个页面已经有另外一个＜script＞插件了，还有jQuery ready（）函数。只需要给页面的表单添加validate（）函数。

3.在$（document）.ready（function（）下面的空行中输入：

$（'#signup'）.validate（）；

这个表单有一个ID为signup：

＜form action="process.php"method="post"name="signup"id="signup"＞

因此$（'#signup'）使用jQuery来选择表单，并且validate（）给表单应用了验证插件。然而，在指定某些验证规则之前，表单是不会得到验证的。因此，首先，我们将使得name字段成为必需的并且提供一个定制的错误消息。

4.找到name字段的HTML，即＜input name="name"type="text"id="name"＞，添加class和title属性，使得标记如下所示（修改的部分用粗体显示）：

＜input name="name"type="text"id="name"

class="required"title="Please type your name."＞

代码的class="required"部分使得Validation插件知道这个字段是必需的，而title属性指定了如果访问者没有填写这个字段将会看到的错误消息。

5.保存页面，在Web浏览器中预览，并且单击Submit按钮。

由于name字段没有填写，因此一条错误消息将会出现在字段的右边（图9-8中的圆圈所示）。

图　9-8　现在不必担心错误消息的外观，我们将在9.5.4节学习如何格式化错误消息

祝贺你，你已经使用9.4.2节所介绍的基本方法给表单添加了验证。接下来，我们将为“date of birth”字段添加另一个验证规则。

提示：如果没有看到一条错误消息，而是得到带有“Form Processed”标题的一个页面，说明验证没有工作并且表单无论如何还是提交了。再次检查步骤1～4，确保没有任何输入错误。

6.找到出生日期字段的HTML，即＜input name="dob"type="text"id="dob"＞，并且添加class和title属性，以使该标签如下所示（修改的地方用粗体显示）：

＜input name="dob"type="text"id="dob"class="date"8

title="Please type your date of birth using this format：01/19/2000"＞

由于没有添加required类，因此填充这个字段是可选的。然而，如果访问者确实在字段中输入任何内容，class="date"告诉插件输入必须是日期的格式。我们再次使用title属性来保存字段无效时候的错误消息。保存这个页面并在Web浏览器中预览，可以在出生日期字段输入类似kjsdf的内容，并尝试提交表单。

注意：如果确实想要求访问者填充出生日期字段，并且要求他们输入一个有效的日期，只要给class属性添加required就行了。只要确保date和required用一个空格隔开：

class="date required"

可以使用同样的技术来验证一个菜单（＜select＞tag）。

7.找到打开选择标签的HTML，即＜select name="planet"id="planet"＞，并且添加class和title属性，以便标签看上去如下所示（修改的地方用粗体表示）：

＜select name="planet"id="planet"class="required"?

title="Please choose a planet."＞

可以通过添加一个validation类和title属性，像验证文本字段那样验证菜单。

现在是时候尝试高级验证方法了。

9.5.2　高级验证

正如9.4.2节介绍的，有很多事情不能够通过基本验证方法来实现，例如为不同的验证问题指定不同的错误消息，或者要求输入特定数目的字符。在这些情况下，我们需要使用Validation插件的高级方法来创建验证规则和错误消息。

要开始使用高级验证，需要为表单的email字段添加两个验证规则和两个不同的错误消息。

1.在靠近文件顶部的JavaScript代码中找到$（'#signup'）.validate（）；这一行，并像下面这样编辑它：

$（'#signup'）.validate（{

}）；//end validate（）

换句话说，在validate（）的圆括号之间添加开始花括号和结束花括号，在两个花括号之间添加两个空行，并且在末尾添加一条JavaScript注释。这条注释表示validate（）函数的结束。我们将很快看到很多带有花括号和圆括号的脚本，因此，要记清楚哪个花括号是干什么的很难。这条注释可以帮助我们避免搞混淆了，但是，像代码中的所有注释一样，它是可选的。

接下来，我们将创建一个基本的结构来添加验证规则。

2.在（花括号之间的）空行中添加最后一步，输入：

rules：{

}//end rules

要使得代码容易阅读，你可能还想在规则和}的前面放置两个空格。缩进这些行，可以从视觉上更清楚地表示这些行是validate（）函数的一部分。

这段代码创建了一个空白对象，我们用特定的字段名和验证方法来填充它。此外，一条JavaScript注释表示rules对象的结束。接下来，我们将为email字段添加规则。

3.编辑validate（）函数，以使它看上去如下所示（改变的部分用粗体显示）：

$（'#signup'）.validate（{

rules：{

email：{

required：true，

email：true

}

}//end rules

}）；//end validate（）

这里，我们将添加另外一个对象文本。第一部分email是想要验证的字段名，并且和HTML中的字段名匹配。接下来，指定了两个验证方法，字段是必需的（意味着如果访问者想要提交表单则必须填写它），并且输入必须匹配一个E-mail地址的形式。尽早测试，并且经常测试，这对任何程序员来说都是金玉良言。在继续进行之前，先进行测试以确保这段脚本有效。

4.保存文件，在Web浏览器中预览，并且尝试提交表单。

你将会看到该插件针对遗漏信息的默认错误消息：“This field is required.”。单击该字段并且输入几个字母。错误消息变为“Please enter a valid email address”（当访问者在一个email字段输入的不是一个E-mail地址的时候，这是该插件所显示的标准消息）。如果你不想看到任何错误消息，那么，查看下你的代码并且将其与上面的步骤3进行比较。

现在，我们将为这个字段添加定制的错误消息。

5.返回文本编辑器。在rules对象的结束花括号的后面（但是在//end rules注释的前面）添加一个逗号，然后输入：

messages：{

}//end messages

这段代码表示另一个JavaScript对象，名为messages。这个对象将包含想要添加给表单字段的任何错误消息。另外，末尾的注释//endmessages是可选的。现在，我们将为email字段添加实际的错误消息。

6.编辑validate（）函数以便它看起来如下所示（添加的部分用粗体显示）：

1$（'#signup'）.validate（{

2 rules：{

3 email：{

4 required：true，

5 email：true

6}

7}，//end rules

8 messages：{

9 email：{

10 required："Please supply your e-mail address."，

11 email："This is not a valid e-mail address."

12}

13}//end messages

14}）；//end validate（），

保存页面并且在Web浏览器中预览。尝试不填写email地址字段而提交表单。将会得到一条“Please supply your e-mail address.”错误消息。现在，在email字段中输入类似“hello”的内容，并尝试提交表单。这次将会得到“This is not a valid e-mail address”错误消息。

如果没有得到任何错误消息，并且相反，得到了“Form Processed！”页面，那么在代码的某处有一个JavaScript错误。最可能的问题是漏掉了rules对象后面的逗号（参见第7行），或者在email message对象中漏掉了逗号（参见第10行）。

现在，应该为两个密码字段添加验证规则了。

7.编辑rules对象以便它看上去如下所示（修改的地方用粗体显示）：

1 rules：{

2 email：{

3 required：true，

4 email：true

5}，

6 password：{

7 required：true，

8 rangelength：[8，16]

9}，

10 confrm_password：{

11 equalTo：’#password'

12}

13}，//end rules

不要漏掉了第5行的逗号，它是必需的，用来分隔email规则和password规则。

第一组规则应用于第一个密码字段。它使得字段成为必需的并且要求字段至少有8个字符长度但不多于16个字符长度。第二条规则应用于email确认字段，并且要求其内容和第一个密码字段中的值相匹配（关于这些规则如何工作的细节可以参考9.4.3节）。

提示：保存文件并在教程中的每步之后测试它，这是个好办法。通过这种方法，如果验证停止工作，就可以知道在哪个步骤中出错。

这些规则也需要相应的错误消息。

8.编辑messages对象，使其看上去如下所示（修改的部分用粗体显示）：

1 messages：{

2 email：{

3 required："Please supply an e-mail address."，

4 email："This is not a valid email address."

5}，

6 password：{

7 required：'Please type a password'，

8 rangelength：'Password must be between 8 and 16 characters long.’

9}，

10 confrm_password：{

11 equalTo：'The two passwords do not match.’

12}

13}//end messages

不要忘记了第5行的逗号。

现在，应该能够熟练地添加规则和错误消息了。接下来，我们将为复选框和单选按钮添加验证。

9.5.3　验证复选框和单选按钮

复选框和单选按钮通常成组出现，并且为一个组中的几个复选框或单选按钮添加验证是一个很难的过程，因为要找到一组中的所有复选框或按钮。幸运的是，Validation插件负责最难的部分，并且使得我们很容易快速验证这些表单字段的类型。

1.找到第一个复选框的HTML，即＜input name="hobby"type="checkbox"id="heliskiing"value="heliskiing"＞，并且添加class和title属性，以便该标签如下所示（修改的地方用粗体表示）：

＜input name="hobby"type="checkbox"id="heliskiing"

value="heliskiing"class="required"

title="Please check at least 1 hobby."＞

这里，我们使用9.4.2节所介绍的基本验证技术，也可以使用高级验证技术，并且把规则和错误消息作为validate（）函数的一部分包含，但是，如果只需要一个验证规则和错误消息，那么基本技术更简单并且不容易出错。

在这个例子中，所有3个复选框具有相同的名字，因此Validation插件将它们视为一组。换句话说，这个验证规则应用到了所有3个复选框，即便只能为一个复选框添加class和title属性。实际上，我们已经要求访问者至少选中一个复选框，然后才能够提交表单。

我们将对页面底部的单选按钮做同样的事情。

2.找到第一个单选按钮的HTML，即＜input type="radio"name="spam"id="yes"value="yes"＞，并且为标签添加class和title属性，如下所示（修改的部分用粗体表示）：

＜input type="radio"name="spam"id="yes"value="yes"

class="required"title="Please select an option"＞

相关的一组单选按钮总是具有同样的名字（在这个例子中是spam），因此，即便我们只是对一个按钮添加了规则和错误消息，它也会应用到所有的3个按钮。由于字段是必需的，因此访问者必须选择3个单选按钮中的一个才能提交表单。

3.保存文件，在Web浏览器中预览它，并且单击提交按钮。

你可能注意到一些事情看起来有些奇怪：当复选框和单选按钮的错误消息出现的时候，它们直接出现在第一个复选框和单选按钮的后面（如图9-9所示的圆圈所示）。在最糟的情况下，消息甚至出现在表单字段及其标签之间（例如，在复选框和标签“Heli-skiing”之间）。

Validation插件把错误消息直接放置在应用了验证规则的表单字段的后面。通常这是没问题的，当消息直接出现在一个文本字段或按钮的后面的时候，这看上去很好（就像本教程前面的例子中那样）。但是在这个例子中，消息应该放到其他的地方，最好是在所有的复选框或单选按钮的后面。

幸运的是，Validation插件有一种方法来控制错误消息的位置。可以通过给validate（）函数传递另一个JavaScript对象来为错误消息放置创建自己的规则。

4.找到在前面添加的验证脚本，并且在messages对象的结束花括号的后面（但是在//end messages注释的前面）输入一个逗号。在messages对象的后面插入一个空行，然后输入：

errorPlacement：function（error, element）{

if（element.is（"：radio"）||element.is（"：checkbox"））{

error.appendTo（element.parent（））；

}else{

error.insertAfter（element）；

}

}//end errorPlacement

图　9-9　Validation插件在无效的表单字段之后放置错误消息。在复选框和单选按钮的例子中，这看上去很难看。为了把错误消息放置在别处，需要为插件的validate（）函数提供一些指令

Validation插件可以通过编程来接收一个可选的errorPlacement对象，它只是一个JavaScript函数，用来确定错误消息放置在何处。这个函数接收错误消息以及应用错误消息的表单元素，因此，可以使用一个条件语句（参见3.1.1节）来检查表单字段是一个单选按钮还是一个复选框。如果是，错误消息添加到包含了按钮或复选框的元素的末尾。在这个页面的HTML中，一个＜div＞标签包围了一组复选框，而另一个＜div＞标签包围了单选按钮。因此，使用jQuery的appendTo（）函数（参见本书4.7节的介绍）把错误消息放置在结束的＜/div＞标签的前面。

我们已经完成了这个表单的所有JavaScript编程。下面是包含了$（document）.ready（）函数的完整脚本。

1$（document）.ready（function（）{

2$（'#signup'）.validate（{

3 rules：{

4 email：{

5 required：true，

6 email：true

7}，

8 password：{

9 required：true，

10 rangelength：[8，16]

11}，

12 confirm_password：{equalTo：’#password'}，

13}，//end rules

14 messages：{

15 email：{

16 required："Please supply an e-mail address."，

17 email："This is not a valid email address."

18}，

19 password：{

20 required：'Please type a password'，

21 rangelength：'Password must be between 8 and 16 characters long.’

22}，

23 confirm_password：{

24 equalTo：'The two passwords do not match.’

25}

26}，//end messages

27 errorPlacement：function（error, element）{

28 if（element.is（"：radio"）||element.is（"：checkbox"））{

29 error.appendTo（element.parent（））；

30}else{

31 error.insertAfter（element）；

32}

33}//end errorPlacement

34}）；//end validate

35}）；//end ready（）

9.5.4　格式化错误消息

现在，页面有了有效的表单验证，但是，错误消息看上去还不是很好。它们不仅分散到了页面的四处，而且它们不能按照应该的方式突出显示。如果看上去都是粗体、红色并且出现在应用它们的表单字段下面，那么，错误消息看上去会更好一些。可以使用一些简单的CSS来做到格式化所有这些消息。

1.在validation.html文件的顶部，单击开始的＜style＞和结束的＜/style＞标签之间的空行。

这个页面有一个空白的样式表，我们将在其中添加样式。在实际情况中，你可能使用一个外部样式表，要么是其他站点所使用的主样式表，要么是专门为该表单指定的样式表（例如，forms.css）。但是，为了让这个教程保持简单，我们将只是为这个页面添加新样式。

2.在＜style＞标签中添加如下所示的CSS规则：

#signup label.error{

font-size：0.8em；

color：#F00；

font-weight：bold；

display：block；

margin-left：215px；

}

CSS选择器#signup label.error找到出现在ID为signup的一个元素中的所有类为error的＜label＞标签。在这个例子中，＜form＞标签有一个signup标签，并且Validation插件把错误消息放入一个＜label＞标签中并添加类error（参见9.4.4节）。换句话说，这个CSS规则只应用于这个表单中的错误消息。

CSS属性本身非常简单：首先，字体减小到.8 em；接下来，颜色改变为红色，而文本加粗。Display：block指令告诉浏览器把＜label＞当做一个块级别元素。也就是说，浏览器把错误本身当做一个段落，在其上下都加上一个分行，而不是将错误消息靠近表单字段放置。最后，要让错误消息和表单字段对齐（从主内容区域的左边界缩进215像素），我们需要添加一个左边距。要使得具有验证问题的字段更为清楚，可以添加CSS规则来改变无效表单字段的外观。

3.为form.css文件添加一条最后的规则：

#signup input.error，#signup select.error{

background：#FFA9B8；

border：1px solid red；

}

这条规则通过在无效表单字段的周围添加一个红色边框并给字段添加一个背景颜色来突出显示它。

这就是所有要做的事情。保存CSS文件并在Web浏览器中预览validation.html页面，将会看到CSS如何影响错误消息（可能需要单击浏览器的刷新按钮来看到对CSS文件所做的修改）。

最终的表单如图9-7所示。可以在chapter09目录下找到该教程的一个完整版本（complete_validation.html）。

第10章　扩展界面

W eb页面给人的感觉就像是长长的一页的目录。如果看上去有太长的文本和图片要滚动，访问者会应接不暇，并且他们无法快速找到自己真正需要的信息。为访问者提供工具来找到他们想要的东西，这完全取决于我们。使用JavaScript和jQuery，我们可以把Web页面流线化，并且使得访问者更容易处理，可以隐藏内容直到需要的时候才显示，并且提供对信息更为容易的访问方式。

在本章中，我们将学习使得页面容易阅读和使用的基本技术。折叠面板和标签页面板把很多信息放入一小块空间，并且允许访问者单击一个标签页来访问小块空间中的内容。鼠标光标移动到链接、表单字段以及其他HTML元素上的时候，带有附加信息的弹出工具提示窗口将提供补充信息。控制页面内容的一种逐渐流行的方法是幻灯片，这是页面上的一种窗口，其内容会滑入或滑出视线。幻灯片允许你显示内容，并且通常用在主页上。

你将学会构建自己的交互式小配件的一些有用的技术：例如，如何确定浏览器窗口大小、一个页面元素，以及元素在页面上的位置。

10.1　把信息组织到标签页面板

在页面上放置太多的内容可能会让访问者应接不暇，并且使得页面看起来很拥挤。JavaScript允许你以多种方式来在一个很小的空间里展示很多的信息。一种技术就是标签页面板效果。标签页面板由顶部的标签和每次只能看见一个的面板组成。当访问者单击标签，当前可见的面板消失，隐藏的面板显示，如图10-1所示。

图10-1：标签页面板在电子商务站点上的应用很普遍，其中的信息常常要显示在各个不同的面板中。这个示例（只是一个完整的We b页面的一部分）在不同的面板中展示了产品概览、产品说明和配送信息，因此，访问者可以单击一个标签来查看他们感兴趣的信息标签页面板就像所有的用户交互式小配件一样，是由HTML、CSS和JavaScript程序组成的。有很多种方法来处理这些组件中的每一个，但后面将介绍一种直接且简单的方式。

10.1.1　HTML

带有标签页的面板有两个主要组成部分：第一部分是标签页，这是在面板的顶部或底部并排出现的按钮；第二部分是面板，这是包含了要显示的内容的＜divs＞。此外，一些其他的标签有助于组织内容和使得编程变得容易：

·包装器元素。这不是严格必需的，但是，如果你在一个页面中包含了多个标签页面板，如果有一个＜div＞标签能够包围起标签页和面板，将有助于标注标签页面板的开始处和结尾处，并且可以使编程变得更为容易。做到这点的HTML很简单：

＜div class="tabbedPanels"＞

＜/div＞

给div添加一个class，将有助于识别这个div，同时，也为样式化标签页面板中的元素提供了一个钩子，并且可以供jQuery用来识别和选择标签页和面板。如果在一个页面上只有一个单个的标签页面板，一个ID就足够了。

·标签通常是以无序的列表项的结构来组织的，它们之间带有链接：

＜ul class="tabs"＞

＜li＞＜a href="#panel1"＞Overview＜/a＞＜/li＞

＜li＞＜a href="#panel2"＞Specifications＜/a＞＜/li＞

＜li＞＜a href="#panel3"＞Shipping＜/a＞＜/li＞

＜/ul＞

列表项中的链接是指向分配给面板的ID的页内链接（后面将会介绍）。指向面板的链接使得那些不支持JavaScript的访问者也可能直接跳转到相应的内容，就像是一个常规的页内链接一样，只不过它直接向上滚动页面到达相应的位置。

注意：如果你不熟悉页内链接，可以从www.yourhtmlsource.com/tex t/internallinks.html找到一个简单的介绍。

·面板包装器。用一个＜div＞标签包围起所有的面板，将有助于样式化，并且提供了一种方法使用jQuery来聚焦于标签页：

＜div class="panelContainer"＞

＜/div＞

·面板是内容所在的地方。每个面板用一个＜div＞标签来表示，并且可以容纳你想要放置的内容，诸如标题、段落、图像和其他的div。每个＜div＞应该有一个独特的ID，这和标签页链接（参见前面的内容）的HREF中使用的ID相匹配：

＜div class="panel"id="panel1"＞

＜！--put content here--＞

＜/div＞

＜div class="panel"id="panel2"＞

＜！--put content here--＞

＜/div＞

＜div class="panel"id="panel3"＞

＜！--put content here--＞

＜/div＞

给每个面板div添加一个class是个好主意，例如，class="panel"，因为它提供了另一种方法来样式化这些元素，并且可以使用jQuery选择它们。

所有这些面板div位于面板包装器div和主包装器元素之中。用于3个标签页面板集合的基本结构的HTML如下所示：

＜div class="tabbedPanels"＞

＜ul class="tabs"＞

＜li＞＜a href="#panel1"＞Overview＜/a＞＜/li＞

＜li＞＜a href="#panel2"＞Specifications＜/a＞＜/li＞

＜li＞＜a href="#panel3"＞Shipping＜/a＞＜/li＞

＜/ul＞

＜div class="panelContainer"＞

＜div class="panel"id="panel1"＞

＜！--put content here--＞

＜/div＞

＜div class="panel"id="panel2"＞

＜！--put content here--＞

＜/div＞

＜div class="panel"id="panel3"＞

＜！--put content here--＞

＜/div＞

＜/div＞

＜/div＞

10.1.2　CSS

标签页面板的CSS允许你创建标签效果（并排的按钮），并且可以样式化面板，以使得它们显得和标签页成为一个一致的、协调的整体。

·容器。你不需要为包装了所有其他带标签页面板代码的＜div＞标签设置一个样式（实际上，你根本不需要这个div）。然而，如果你想要将标签页面板的宽度限定在某一个尺寸，可能是为了让标签页面板挨着另一个页面元素放置，或者是为了并排放置两组标签页面板，那么，创建一个样式是很方便的。在这种情况下，你可以像下面这样创建一个样式并设置其width属性：

.tabbedPanels{

width：50%；

}

·项目列表和列表项。由于项目列表通常是缩进的，因此，如果想要让标签页和左边界对齐，你需要删除它们左边和右边的任何补白。此外，要让标签页并排放置，而不是一个在另一个之上，必须使列表项浮动。最后，由于列表项前面通常有项目符号，应该删除它们。如下这些基本的样式完成了这些工作：

.tabs{

margin：0；

padding：0；

}

.tabs li{

float：left；

list-style：none；

}

注意：这里列出的CSS代码应用于上面列出的HTML。换句话说，.tabs引用项目符号列表，也就是HTML中的＜ul class="tabs"＞，而.tabs li引用＜ul＞标签中的列表项。

·标签页。本身用列表项中的＜a＞标签表示。还有一些你肯定想要设置的属性。首先，你想要删除通常出现在链接下面的下划线，接下来，你想要把链接的display属性设置为block，以使得padding和margin属性应用于它们。下面是一些基本样式：

.tabs a{

display：block；

text-decoration：none；

padding：3px 5px；

}

当然，你可能想要添加更多的属性，以便真正地让标签页看上去很不错：background-color给标签页带来活力，fonts、font colors和sizes用来使得标签页更突出等。

·激活标签页。突出显示与当前显示的面板相关的标签页面，这是一个好主意。这是一种“你现在在这里”的标记，可以帮助访问者识别面板中的信息。常见的方法是创建class样式，当访问者单击该标签页后，jQuery动态地应用这个样式。这个样式没有强制的属性，但是，它给激活的标签页和面板同样的背景颜色（并且使得常规的标签页的样式和面板不同），这使得选中的标签页与其面板具有一致的视觉效果：

.tabs a.active{

background-color：white；

}

提示：常用的方法是，给面板周围和标签页周围添加一个边框。当你单击了一个标签页，标签页底部的边框消失，并且标签页显得和面板融合为一体了（如图10-1所示）。为了达到这种效果，首先给.tabs a的样式添加一个边框，并且将底部的边距设置为-1像素。负值会将标签页向下移动1像素，实际上会使面板重叠。然后，给tabs a.active样式一个底部边框颜色，使得这个颜色和面板的背景颜色相匹配，边框仍然在那里，但是由于它和面板的背景颜色匹配并且和面板的顶部边框重合，就显得好像标签页和面板是一体的（你还需添加相对于该样式的position：，以便它可以适用于IE 8和之前的版本）。最后，给面板容器添加一个边框，使得它与.tabs a style的边框具有相同的样式、宽度和颜色。本书10.1.4节的教程展示了这种效果的应用。

·面板容器。包含了所有面板需求的div标签的样式：因为所有的标签页都向左浮动（以便它们对齐），你必须“清除”面板容器。否则，它将尝试折返到标签页的右侧，而不是出现在标签页的下面：

.panelContainer{

clear：left；

你也可以使用这一样式来格式化面板的外观。由于面板容器是包围面板的一个盒子，你可以为这个样式添加一个背景颜色、边框、补白等。

·面板。正如前面所提到的，可以使用一个面板容器样式来为面板创建基本的格式，包括边框、背景颜色等。然而，如果愿意的话，你可以通过创建一个.panel样式，将格式化应用于单个的面板div。

·面板内容。要样式化面板中的内容，可以使用子孙选择器来选定每个面板div中的标签。例如，如果想要让一个橙色面板中的一个＜h2＞标签显示Arial字体，可以创建如下的样式：

.panel h2{

color：orange；

font-family：Arial, Helvetica, sans-serif；

}

要格式化面板中的一个段落，创建一个名为.panel p的样式。

10.1.3　JavaScript

准备好了HTML和CSS，已经可以看到顶部的标签页了，它们下面还有3个div（面板）彼此重叠，如图10-2所示。基本的外观放置好了。你只需要添加程序使得面板打开和关闭，并且修改标签页上的class，来让一个标签页成为“激活的”而其他标签页成为“常规的”。基本的步骤如下所示：

1.给标签页内部的链接添加一个click事件。

标签页面板只不过是访问者和标签页的交互：单击一个标签页来显示一个面板，单击另一个标签页来显示另外一个面板。

2.给click事件添加一个匿名函数，以便：

·隐藏当前可见的面板。

·从之前选择的标签页删除active类。

·给刚刚单击的标签页添加active类，以使其激活。

·显示和标签页相关联的面板。

3.触发第一个标签页上的click事件。

这个步骤是必需的，因为当初次载入页面时，所有的面板都是可见的，并且没有标签页选中。你可能只是编写代码来突出显示第一个标签页，并且隐藏第一个标签页之外的所有其他面板，但是，你不需要这么做，click事件的匿名函数（步骤2中）为你做了这些，因此，你只需要通过编程“单击”该标签，并且触发该函数。

因此，简而言之，事情就是这么完成的。现在，我们将在下面的教程中按部就班地进行编程。

图　10-2　标签页面板的HTML结构实际上很简单：一个div标签、一个无序的列表、一些链接，还有一些div标签

10.1.4　标签页面板教程

既然已经理解了创建一个导航菜单的基础知识，下面来介绍如何实现它。在这个教程中，我们将添加CSS和JavaScript，将10.1.1节介绍的基本的HTML菜单列表转换成一个导航栏。

注意：参见1.3节中的“注意”部分以获取如何下载教程文件的相关信息。

1.在文本编辑器中打开chapter10文件夹下的tabs.html文件。

该文件包含了10.1.1节所述的HTML文件：一个容器div、一个用于标签页的链接的无序列表、一个包含面板的div，以及针对每个面板的一个div。基本的CSS格式也设置好了。如果在Web浏览器中查看页面，将会看到3个标签页以及3个面板的内容（每个面板都叠放在另一个之上）。

注意：为了让用于标签页面板的CSS尽可能清晰，我们将其直接放在一个内部样式表中。如果想要将这个CSS重用于你自己的标签页面板，最好将其放到你的站点所使用的主外部样式表中。

jQuery文件已经链接了，并且$（document）.ready（）函数也准备好了，因此，第一步是隐藏该面板。

2.单击$（document）.ready（）函数中的空行，并且添加如下粗体所示的代码：

$（document）.ready（function（）{

$（'.tabs a'）.click（function（）{

}）；//end click

}）；//end ready

$（'. t abs a'）选择了无序列表（它有一个类为tabs）中的所有＜a＞标签（5.2节介绍了click（）函数）。此时，你有了一个空的匿名函数，因此，将开始用程序填充它。首先，你将添加语句让代码更加高效一点。

3.在匿名函数中添加如下粗体所示的代码：

$（'.tabs a'）.click（function（）{

var$this=$（this）；

}）；//end click

正如4.10.2节所提到的，当在一个事件处理程序的匿名函数内部时，$（this）引用为其添加事件的元素。在这个例子中，$（this）引用访问者单击的标签页。每次使用$（）选择一个元素的时候，会真正地调用jQuery函数，迫使Web浏览器执行很多行的JavaScript代码。如果你需要在一个函数内多次使用相同的选择器，将其保存到一个变量中，这是一个好办法。在这个例子中，$this只不过是程序员（也就是你）创建的一个变量。

将$（this）的值保存到变量中，意味着任何时候，当你想要访问该链接的时候，只需要使用变量$this就可以了，不需要让jQuery再次来选择找出链接。换句话说，如果在函数中两次用到$（this），那么，浏览器必须运行jQuery函数两次，以选择完全相同的元素。如果你先将$（this）存储到一个变量$this中，你可以在自己的代码中多次使用该变量，而不需要迫使浏览器做额外的工作（13.1.2节更加深入地介绍了将jQuery选择器存储到变量中的好处）。

现在，可以隐藏面板，并且激活单击的标签页。4.添加如下第3行和第4行所列出的代码。

1$（'.tabs a'）.click（function（）{

2 var$this=$（this）；

3$（'.panel'）.hide（）；

4$（'.tabs a.active'）.removeClass（'active'）；

5}）；//end click

第3行代码隐藏了面板。由于每个面板都是带有一个panel的class的＜div＞标签，$（'.panel'）会选择所有的面板，并且jQuery's.hide（）函数（参见6.1.1节）隐藏它们。你需要这么做，因为当你打开一个面板的时候，之前的面板将仍然是可见的。

第4行从标签页内部的任何链接上删除了active类。在10.1.2节中，我们探讨了如何创建一个active类样式，以允许你为用户单击的标签页提供一种不同的外观（一种“你正位于此处”的外观）。当然，当访问者单击激活一个标签页时，你还需要从之前选定的标签页上删除该类，这就是第4行代码使用jQuery的remove-Class（）函数所做的事情（参见4.8.1节）。接下来将突出显示访问者刚才单击过的标签页。

5.添加如下第5行所示的代码。

1$（'.tabs a'）.click（function（）{

2 var$this=$（this）；

3$（'.panel'）.hide（）；

4$（'.tabs a.active'）.removeClass（'active'）；

5$this.addClass（'active'）.blur（）；

6}）；//end click

记住，$this是你在第2行创建的一个变量，并且它保存了到访问者所单击的链接的一个引用。因此，$this.addClass（'active'）给链接添加了active类：Web浏览器将使用CSS来样式化这个标签。最后的.blur（）使用了jQuery的链化功能（参见4.6.4节的介绍）。这只是在.addClass（）之后运行的另一个函数。.blur（）函数删除了一个链接的焦点（或者form字段）：在这个例子中，对于访问者单击过或标记过的链接，它删除了Web浏览器围绕链接显示的模糊线条。没有了这个，激活的标签页看上去就更好看了。

这个函数基本上完成了，你只需要显示面板。6.添加如下第6行和第7行所示的代码。

1$（'.tabs a'）.click（function（）{

2 var$this=$（this）；

3$（'.panel'）.hide（）；

4$（'.tabs a.active'）.removeClass（'active'）；

5$this.addClass（'active'）.blur（）；

6 var panel=$this.attr（'href'）；

7$（panel）.fadeIn（250）；

8}）；//end click

每个标签页实际上是指向其相关的面板的一个链接。记住，一个面板的HTML看上去如下所示：＜div id="panel1"class="panel"＞，并且每个标签页的HTML看上去如下所示：＜a href="#panel1"＞。注意，href值看上去就像是一个CSS ID选择器，由于jQuery使用CSS选择器来选择一个页面元素，因此你可以获取链接的HREF并且使用它来定位你想要显示的面板。第6行创建了一个新的变量panel，来保存该链接的HREF属性（使用4.9节介绍的jQuery的.attr（）函数）。

第7行使用该HREF来选择面板，然后，使用jQuery的fadeIn（）函数将其淡入视线（参见6.1.2节）。你可以使用jQuery的其他效果函数，诸如show（）、slideDown（）或animate（）来替代fadeIn（）。

现在程序已经编写好了，如果页面载入的时候你触发一个单击操作，那么，你可以运行这个函数、隐藏面板、选择一个标签页并且显示其相关联的面板。幸运的是，jQuery使得很容易模拟任何的事件以及触发一个事件处理程序。

7.在click（）函数的后面再添加一行额外的代码来触发第一个标签页上的一个“click”。

1$（'.tabs a'）.click（function（）{

2 var$this=$（this）；

3$（'.panel'）.hide（）；

4$（'.tabs a.active'）.removeClass（'active'）；

5$this.addClass（'active'）.blur（）；

6 var panel=$this.attr（'href'）；

7$（panel）.fadeIn（250）；

8}）；//end click

9$（'.tabs li：frst a'）.click（）；

这段代码使用了一个相当复杂的选择器：.tabs li：first a，来选择第一个标签页。基本上，你可以从右到左地读这个选择器（就像所有的子孙选择器一样）。在最右端，a表示我们的目标是选择一个＜a＞标签。在中间，li：first是第一个孩子伪元素，它匹配一个＜li＞标签，而这个＜li＞标签是另一个标签的第一个孩子。由于标签是由一个项目符号列表构成的，li：first表示第一个列表项，或者在这个例子中，是第一个标签页。最后，.tabs确保我们选择的链接所在的列表项是标签页面板的一部分。这防止我们意外地选择了位于页面上任何其他地方的另一个符号列表中的一个链接（例如，一个导航栏）。

最后，你可以使用.click（）函数，不只是设置一个函数来响应click事件，而是真正地触发该事件。因此，第9行实际上是在说：“Web浏览器，单击第一个标签页吧。”这会触发整个一系列的动作：隐藏面板、突出显示面板并且将合适的面板淡入视线中。哦，你几乎就在那个位置。如果你在Web浏览器中预览页面，可能会注意到一个小问题。由于面板实际上是页内链接，如果你在一个较小的显示器中查看页面，可能会看到浏览器跳到了面板之下，而不是显示面板。你需要告诉浏览器，不要指向这个链接。

8.在click处理程序的末尾添加return false；（参见第9行）。完整的代码如下所示：

1$（document.ready（function（）{

2$（'.tabs a'）.click（function（）{

3 var$this=$（this）；

4$（'.panel'）.hide（）；

5$（'.tabs a.active'）.removeClass（'active'）；

6$this.addClass（'active'）.blur（）；

7 var panel=$this.attr（'href'）；

8$（panel）.fadeIn（250）；

9 return false；

10}）；//end click

11$（'.tabs li：first a'）.click（）；

12}）；//end ready

9.保存页面并在Web浏览器中预览。

完整的教程如图10.3所示。只需要向无序列表（该列表包含了指向用于新的面板的新的div标签的链接）插入额外的列表项，就可以添加更多的标签页和更多的面板。

注意：你可以在chapter10教程文件夹中看到本教程的一个完整版本（complete_tabs.html）。此外，标签页面板代码的一个更加高级的版本位于complete_complex_tabs.html文件中，它考虑到一个单个页面上的多个带标签页的面板，它用到了13.3节介绍的jQuery的一些高级DOM Traversal函数。

图　10-3　标签页面板提供了一种优雅的方式，用来访问众多的信息并且节省了We b页面资源

快速熟悉jQuery UI项目

作为jQuery UI的一部分，你可以找到更高级的标签页面板版本。jQuery UI是jQuery团队的一个官方项目，其目标是提供解决基本的用户交互问题的插件、折叠、标签页、对话框、日历挂件和可拖拽页面元素。这个项目有自己的网站（http：//jqueryui.com/），可以在那里找到最新的代码、演示，以及到jQuery主Web站点上的文档的链接。

jQuery UI为Web开发者提供了众多的工具，甚至支持CSS主题，它允许你为所有的jQuery UI元素创建一种一致的外观和感觉。jQuery UI相当复杂，并且包含了众多不同元素。你可以定制化jQuery UI文件以满足自己的需要，不要考虑那些不需要的组件，从而节省文件大小和下载速度，并且，你甚至可以创建自己的CSS主题来和站点的颜色、字体和贴图相匹配。

http：//jqueryui.com/download中的一款在线下载工具可以帮助你完成这个过程。

本书的第3版在很多章节中都用到了j Q u e r y UI，但是，从那时起，jQuery UI团队决定重新编写该项目的所有代码，并且添加了大量新的、令人兴奋的用户界面挂件和工具。遗憾的是，在编写本书的时候，jQuery UI的新版本（版本1.9）还没有推出，因此，你不用浪费时间学习大量的、注定要在不久的将来被替代的用户界面挂件，本书不会介绍如今的jQuery UI。

然而，jQuery UI有着很好的前景并且值得关注。请访问jQuery UI的站点看看版本1.9是否推出了。如果是，继续学习它。

10.2　为站点添加一个内容幻灯片

Web设计师用于解决信息过多的另一款工具是内容幻灯片，它是一个简单的类似幻灯片的界面，每次从一堆内容中显示一幅图片。很多信息丰富的站点，例如Microsoft.com，使用一小块的幻灯片来显示图像、文本以及链接，这些幻灯片横跨屏幕（如图10-4所示）。一个幻灯片就像是一个标签页面板，只不过幻灯片面板通常具有相同的大小，并且在一个幻灯移动中动态地出现在屏幕上，而且常常运行一定的时间，每隔数秒钟面板就会幻灯式地滑过屏幕。幻灯片通常用在主页上，以保持页面简单而富有吸引力，而同时又突出显示很多的内容。在站点的其他页面上，它们经常充当招揽顾客的传单、广告内容或者产品。单击幻灯片中的一个面板，通常会导向另一个Web页面。

内容幻灯片需要掌握JavaScript和jQuery编程的一些要素，包括动画、定时器以及操作CSS和HTML。尽管你可以编写自己的幻灯片，但是，还是有很多jQuery插件提供各种广泛的、有用的功能。最为全面的jQuery幻灯片插件叫做AnythingSlider（可以从https：//github.com/ProLoser/AnythingSlider/下载）。

图　10-4　为了将屏幕上的杂乱程度降到最低，像Microsoft.com这样的站点使用内容幻灯片（带有黑色边框的部分），每次显示一副单个的图像或一块内容。在这个例子中，带有边框的图像可以向左滑动出视线，显示另一幅带有更多信息的图像

10.2.1　使用AnythingSlider

AnythingSlider需要几个文件才能工作：jQuery（这当然是需要的）、带有幻灯片程序的一个JavaScript文件、样式化基本幻灯片效果的一个CSS文件，以及用于幻灯片控制的一个图像文件（next按钮和previous按钮）。你可以从https：//github.com/ProLoser/AnythingSlider/下载AnythingSlider。（我们已经在本章的教程文件中包含了必要文件的一个副本）。使用该插件的基本过程很简单，步骤如下所示。

1.将anythingslider.css文件附加到Web页面。

这个外部的CSS提供了用于幻灯片导航按钮的格式化指令，以及用于放置单个幻灯片的背景样式。通过更改这个文件中的样式（参见下面的介绍），你就可以修改幻灯片的基本元素。

2.链接到jQuery文件。

jQuery提供了AnythingSlider所需要的基本工具集。和使用任何jQuery插件一样，你必须先载入jQuery JavaScript文件。如果在插件文件载入之后再载入jQuery，它将无法工作。

3.连接到AnythingSlider文件。

这个文件包含了基本的程序，用来将HTML转换为一个幻灯片。4.添加HTML。

AnythingSlider不需要任何复杂的HTML。你只需要添加一个容器div，它带有一个ID为slider，即＜div id="slider"＞，并且，在该div中，为每个面板插入一个div或“幻灯片”。这和为标签页面板设置面板非常类似（参见10.1.1节的介绍）。

5.添加＜script＞标签和$（document）.ready（）函数，然后调用AnythingSlider函数：

使用jQuery插件的乐趣之一是，它们往往需要很少的代码就能工作。在这个例子中，在到AnythingSlider文件的链接之后（参见步骤3），所需要添加到该页面的简单代码如下所示：

＜script＞

$（document）.ready（function）{

$（'#slider"）.anythingSlider（）；

}）；//end ready

＜/script＞

正如你在本章稍后将要见到的，有很多种方法来定制化AnythingSlider效果。但是，你首先要玩转插件。

10.2.2　AnythingSlider教程

创建一个基本的幻灯片真的很容易。本教程将带领你经历这个过程。你可以使用任何喜欢的HTML编辑器。

注意：参见本书1.3节的介绍以了解如何下载教程文件。

1.在文本编辑器中打开chapter10文件夹下的文件slider.html。

第一步是添加AnythingSlider的CSS文件。

2.在＜link href="../_css/site.css"rel="stylesheet"＞后面的空行单击，并且输入：

＜link rel="stylesheet"href="anythingSlider/anythingslider.css"＞

这行代码将载入anythingslider.css文件，它包含了用于格式化幻灯片的特定样式。稍后，当你查看如何更新幻灯片的外观的时候，将会重新访问这个文件。接下来需要链接到必要的JavaScript文件。

3.在刚才添加的代码行的下面录入如下代码：

＜script src="../_js/jquery-1.6.3.min.js"＞＜/script＞

＜script src="anythingSlider/jquery.anythingslider.min.js"＞＜/script＞

第1行载入jQuery文件，而第2行载入插件文件。现在，该要添加HTML了。

提示：对于本教程来说，用于AnythingSlider插件的JavaScript、CSS和图像文件都存储在chapter10教程文件夹中的一个名为anythingSlider的文件夹中。将插件所需要的所有文件都一起放在一个特定文件夹中，这是一种好办法，可以确保你不会搞错其中某个文件的位置。这也使得在其他站点上重用插件会更容易。如果你喜欢这种效果，可以直接将anythingSlider文件夹复制到你的站点（将其放在主根目录中或者放在专用于JavaScript文件的一个文件夹中）。

4.在代码中找到一级标题标签，即＜h1＞Anything Slider＜/h1＞，并且，在其下面的空白行中输入：

＜div id="slider"＞

＜/div＞

这是一个＜div＞标签（用来在Web页面上标记出一个区域或部分的一个HTML标签）。这个特定的div表示幻灯片自身。在其中，你可以添加更多的＜div＞标签，每个＜div＞用于你想要显示的一个幻灯片。

5.在幻灯片div中（在开始＜div＞和结束＜/div＞标签之间）输入：

＜div＞

＜a href="page1.html"＞＜img src="images/pumpkin.jpg"width="700"height="390"

alt="Pumpkin"＞＜/a＞

＜/div＞

这是第二个div，其中包含了一个链接和一幅图像。单击图像会将你带到另一个页面：这是目录式幻灯片常用的方法，它往往充当站点的一种动画的banner广告。每个幻灯片充当其他内容的预告，因此，访问者通过单击一个幻灯片跳转到该站点的一篇文章或其他部分。

有了AnythingSlider，你可以添加任何想要的HTML。不仅限于一幅较大的图像。你可以有文本、图像或者其他的div标签，包括你想要放置在幻灯片中的任何内容。

6.在幻灯片div中再添加两个＜div＞标签：

＜div＞

＜a href="page2.html"＞＜img src="images/grapes.jpg"width="700"height="390"

alt="Grapes"＞＜/a＞

＜/div＞

＜div＞

＜a href="page3.html"＞＜img src="images/various.jpg"width="700"height="390"

alt="Various"＞＜/a＞

＜/div＞

这两个div表示另外两个幻灯片。你可以添加想要的任意多个幻灯片。现在，应该来添加一些程序了。

7.在靠近文件顶部的位置，在第二个＜script＞标签之后，但是，在结束的＜/

head＞标签的前面，添加一个空行，然后输入：

＜script＞

$（document）.ready（function（）{

$（'#slider'）.anythingSlider（）；

}）；

＜/script＞

不管你是否相信，你所需要做的只是选择包含了幻灯片的div，即$（'#slider'），并且应用anythingSlider（）函数。插件负责处理剩下的事情。

8.保存该文件，并且在Web浏览器中预览。

这个页面如图10-5所示（如果不是这样，仔细检查一下你的代码。你也可以将自己的作品和complete_slider.html文件相比较，后者是本教程的一个完整版本）。尝试不同的控件：单击向右箭头的按钮，向右移动到下一个幻灯片；单击左箭头按钮，回到上一个幻灯片，单击带有数字的按钮跳转到指定的幻灯片，并且，单击开始按钮开始自动播放幻灯片。

图　10-5　使用AnythingSlider jQuery插件，你可以快速创建一个交互式的幻灯片，来突出显示整个站点的页面和产品

10.2.3　定制幻灯片外观

正如你所看到的，使用AnythingSlider相当容易。当然，这个幻灯片的“新潮的”外观可能和你的站点的设计并不匹配，并且你可能不想或者不需要其所有的功能（例如，幻灯片自动播放，或者向前和向后按钮）。AnythingSlider的外观可以以多种方式更改：编辑一个图形文件、编辑样式表以及为插件设置选项（将在下一节介绍）。

使用一种叫做CSS精灵的技术，可以使单个的图像文件充当多种角色，既表示向后和向前箭头的常规和“悬停”状态，又体现了数字按钮以及“开始”按钮的阴影背景（要了解CSS精灵，请访问http：//css-tricks.com/158-css-sprites/）。你可以编辑这些图像文件（每个箭头是45像素×140像素，带下拉式阴影），并且添加自己的箭头图像。

你也可以编辑样式表来定制幻灯片的外观。下面是你可能需要编辑的一些最常见的格式化修改和样式的列表：

·幻灯片的高度和宽度。anythingslider.css文件中的第一个样式#slider控制了幻灯片播放的整体宽度和高度。你可以调整宽度值，以容纳一个更宽或更窄的幻灯片，并且如果幻灯片比默认的390像素高或者低，可以调整高度值。

·导航按钮的颜色。在幻灯片底部编号的按钮通常都是绿色的。如果你不喜欢这种颜色，编辑如下这一系列的样式：div.anythingSlider.activeSlider、.anythingControls ul a.cur、div.anythingSlider.active Slider.anythingControls ul a.，将背景颜色从#7C9127修改为和你的站点相匹配的一种颜色。如果你想要更改字体颜色，也可以用你想要的颜色来添加颜色属性。例如：

color：#F44439；

·导航按钮的翻滚颜色。你可以给导航按钮定义一种新的背景颜色，通过编辑div.anythingSlider.anythingControls ul a：hover样式，来更改字体或者你想要更改的属性。当前，直接从按钮上删除背景图像（下拉阴影）。

·当前选择的导航按钮。可以通过给样式表添加一个名为div.anythingSlider.activeSlider.anythingControls ul a.cur的样式，并且设置一种不同的背景颜色、字体等，来突出显示与当前显示的幻灯片相关的按钮。确保要么将这个样式放置在上面的“导航按钮的颜色”部分所列出的样式之后，要么通过从其中删除div.anythingSlider.activeSlider.anythingControls ul a.cur来编辑样式。由于上面列出的样式已经有与其相关的背景颜色，它将覆盖你的新样式，除非你将新的样式放在样式表中较后面的位置。

开·始和结束按钮的颜色。用来开始和停止幻灯片动画播放的按钮，通过两种样式来控制。要更改开始按钮的背景颜色和字体颜色，可以编辑div.anythingSlider.start-stop。要更改停止按钮的红色，可以编辑div.anythingSlider.start-stop.playing样式。

·删除下拉式阴影并且对导航按钮做其他的更改。如果你不喜欢出现在导航按钮上的下拉式阴影和开始/停止按钮，那么，编辑div.anythingSlider.anythingControls ul a和div.anythingSlider.start-stop样式：删除background-image属性。你也可以在这些样式上编辑border-radius、-moz-border-radius和-webkit-border-radius属性，以删除或增加这个按钮的圆角。这些样式控制了按钮的基本外观，因此，值得尝试一下它们并看看所创建的新外观。

·幻灯片显示之上和之下的绿色边框。幻灯片之上和之下有一个绿色的、3像素的边框。编辑div.anythingSlider.anythingWindow样式来更改它。直接删除border-top和border-bottom属性以完全删除边框，或者只是更改其设置，以修改边框的颜色和宽度。

箭头的放置。·你可以通过编辑div.anythingSlider.back（用于左箭头）和div.anythingSlider.forward（用于右箭头）样式来控制将向后箭头和向前箭头放置在何处。此外，div.anythingSlider.arrow样式设置了两个箭头的某些基本样式，包括它们在幻灯片中间的位置。例如，如果你想要箭头靠近幻灯片的顶部，直接编辑div.anythingSlider.arrow样式并将top：50%更改为top：20%，或者直接使用诸如top：45px；的一个像素值。

10.2.4　定制幻灯片行为

你可以通过直接编辑CSS文件对幻灯片做出很多修改。但是，要对幻灯片如何工作做出根本修改，你需要为插件设置一些选项。要做到这点，给插件传递一个对象直接量（参见2.8.4节）：

{

buildArrows：false，

startText："Start slideshow"，

stopText："Stop slideshow"

}

在这个示例中，buildArrows是一个AnythingSlider选项，并且设置为false。这一设置防止AnythingSlider添加左箭头和右箭头。在每个选项/值对的末尾添加一个逗号，除了最后一对（注意，在stopText：“Stop Slideshow”的后面没有逗号）。

然后，将这个对象直接量放在插件的函数调用中。例如：

$（'#slider'）.anythingslider（{

buildArrows：false，

startText："Start slideshow"，

stopText："Stop slideshow"

}）；

下面列出了一些最有用的选项。

·隐藏导航箭头。要隐藏向左或向右箭头，可以将buildArrows选项设置为false，如下所示：

buildArrows：false

·更改幻灯片标签。要更改出现在开始按钮和结束按钮上的文本，像下面这样设置startText和stopText选项：

startText："Start slideshow"，

stopText："Stop slideshow"

·关闭autoplay选项。你可能不想要开始按钮和停止按钮出现，而是更愿意让访问者手动地选择他们想要浏览的幻灯片。在这种情况下，把buildStartStop设置为false：

buildStartStop：false

·垂直幻灯片播放。要让幻灯片垂直地从上到下移动，而不是从左到右移动，将vertical选项设置为true：

vertical：true

·Autoplay。当页面开始载入的时候，要让幻灯片播放功能自动化，将autoPlay选项设置为true：

autoPlay：true

在包含幻灯片的Web站点上，当页面载入的时候，开始自动播放幻灯片，这是特别常见的功能，因为这向访问者展示更多内容，而不需要访问者单击一个“播放幻灯片”按钮。

·各种大小的幻灯片。如果每个幻灯片包含的内容量不同，你可以让幻灯片窗口改变大小，以增加或缩减每个幻灯片所显示的内容。例如，假设第一个幻灯片是其中带有单独一段的一个div，而第二个幻灯片是带有一个标题栏、两幅较大的图像和3个段落的一个div。如果你将resizeContents属性设置为true，那么AnythingSlider将会针对每张幻灯片更改幻灯片的大小，在上面的例子中，幻灯片一开始将较小，显示单独的一个段落。当单击下一个按钮，移动到具有较多内容的下一个幻灯片的时候，幻灯片的高度变大了。可以这样设置这个属性：

resizeContents：true

要查看这些选项的应用，打开教程文件中的complete_slider2.html文件。

正如你所看到的，可以很容易将AnythingSlider添加到一个站点，并且也很容易定制。我们已经初步了解了这个插件能够干什么。你可以嵌入视频、特殊效果，并且增加定制编程来让它们按照你想要的那样工作。查看AnythingSlider Wiki以了解更多信息：//github.com/ProLoser/AnythingSlider/wiki。

10.3　确定页面元素的大小和位置

当你使用JavaScript和jQuery来添加或更改一个Web页面的时候，知道页面上的元素的大小和位置，往往是很方便的。例如，你可能想要将非常常见的覆盖放到页面的顶部（这种效果使得页面显得暗淡，如7.5节的FancyBox教程所示）。要做到这些，我们放置一个绝对定位的＜div＞标签，以便它覆盖住浏览器窗口。要完全覆盖窗口，你需要确保div和窗口具有相同的大小，因此，你必须首先确定浏览器窗口的宽度和高度。

同样，如果想要创建一个工具提示效果（也就是当访问者鼠标光标移动到页面上的某个位置的时候，弹出一个带有信息的小对话框），我们需要确定屏幕上鼠标光标的位置，以便可以放置该工具提示。

10.3.1　确定元素的高度和宽度

jQuery提供了.height（）和.width（）函数来得到页面元素的高度和宽度。通过提供合适的选择器，你可以确定页面上任何标签的高度和宽度，甚至确定浏览器窗口的大小，以及文档自身的大小。

·浏览器窗口的高度和宽度。如果你想要获取浏览器窗口的高度和宽度（通常称之为视口），使用$（window）选择器和height（）和width（）函数，如下所示：

var winH=$（window）.height（）；

var winW=$（window）.width（）；

上面的代码获取了宽度和高度，并且将其存储到两个变量中。当你想要确保没有将一个元素放置在浏览器窗口的可见区域之外的时候，获取浏览器窗口的宽度和高度是很有用的。

·文档的高度和宽度。文档和浏览器窗口并不是同样的东西，并且，大多数时候，二者的高度和宽度是不同的。文档表示你的Web页面，如果页面只有一点内容，例如，一个段落，那么，文档的高度就是这个段落（加上顶端和底部的边距）。在较大的显示器上，文档的高度可能小于浏览器窗口的高度。相反，如果一个Web页面有很多内容，以致访问者必须滚动页面来读取所有内容，那么，文档比浏览器窗口还要高。同样，如果你已经为一个body标签或者包含了内容页面的一个＜div＞标签设置了一个样式，并且，假设将该样式的宽度设置为1500像素，文档可能比浏览器窗口要宽。要获取文档的宽度和高度，使用$（document）选择器和height（）和width（）函数：

var docH=$（document）.height（）；

var docW=$（document）.width（）；

也可以在段落、div和图像这样的常规HTML元素上使用height（）和width（）函数，然而，这并不总是能给出你想要的信息。height（）和width（）函数返回CSS的height和width属性的值，这并不一定与页面上的元素的高度和宽度相同。在CSS中，width和height属性定义了一个标签中留给内容的空间，例如，一个段落标签中的文本。然而，如果你给该元素添加了补白、边框或者边距，那么，该元素占据页面的空间要大于该元素的CSS的高度和宽度。

要理解这是如何工作的，我们来看用于一个＜div＞标签的一些简单的CSS：

div{

width：300px；

height：300px；

padding：20px；

border：10px solid black；

}

图10-6显示了该div。该div在实际屏幕上的宽度和高度是360像素，因为它在页面上的空间是高度（或宽度）、补白和边框的总和。因此，实际的宽度是左边框+左补白+CSS宽度+右补白+右边框，并且，实际的高度是上边框+上补白+CSS高度+下补白+下边框。

由于存在这些不同的大小，jQuery提供了3组函数来确定页面元素的不同宽度和高度：

·width（）和height（）函数返回该元素的CSS宽度和高度。例如，假设你的页面有上面列出的CSS样式，并且其上面有一个＜div＞标签：

var divW=$（'div'）.width（）；//300

var divH=$（'div'）.height（）；//300

在上面的代码中，变量divW和divH保存了值300，即CSS中设置的高度和宽度。

·innerWidth（）返回了宽度+左补白和右补白，innerHeight（）返回CSS高度+上补白和下补白：

var divW=$（'div'）.innerWidth（）；//340

var divH=$（'div'）.innerHeight（）；//340

在上面的代码中，变量divW和divH保存了值340，即CSS中设置的高度和宽度加上两边的补白值。

·outerWidth（）返回了宽度+左补白和右补白+左边框和右边框；outerHeight（）返回了CSS高度+上补白和下补白+上边框和下边框：

var divW=$（'div'）.outerWidth（）；//360

var divH=$（'div'）.outerHeight（）；//360

在上面的代码中，变量divW和divH保存了值360，即CSS中设置的高度和宽度+两边的补白和边框值。

outerWidth（）和outerHeight（）属性也接收一个可选的参数：true，它还会将元素周围的边距考虑进去。例如，假设div标签的CSS如下所示：

div{

width：300px；

height：300px；

padding：20px；

border：10px solid black；

margin：20px；

}

注意20像素的边距设置。如果你还想要将边距包含到整体的宽度和高度计算中，可以编写如下所示的jQuery代码：

var divW=$（'div'）. outerWidth（true）；//400

var divH=$（'div'）. outerHeight（true）；//400

根据想要完成的事情不同可以选择不同的函数。例如，可能你想要用一个黑色的框覆盖页面上的一些文本（例如，测试题的答案），随后才显示这些文本。一种可能的方法是，使用填充了黑色背景色的一个div来覆盖该文本。在这个例子中，你将使用width（）和height（）函数来确定只是文本的宽度和高度（不包括补白或边框），以便在div覆盖文本之前正确地确定其大小。

或者，假设你想要创建一个类似Pong的游戏，一个小球在屏幕上来回弹跳，并且想要这个球保持在游戏区域的边框之内（一个div可能四周带有一条边框线）。那么，你需要知道边框中可用的区域，以便确保动画的球不会跳到框外或者压着边框。在这个例子中，可以使用innerHeight（）和innerWidth（），因为球可能会移动到框中的任何位置，甚至超过应用于框的任何补白。

注意：不要使用窗口对象（$（'window'））或文档对象（$（document））的innerHeight（）、innerWidth（）、outerHeight（）或outerWidth（）。只有height（）和width（）才对这些对象有效。

图　10-6　当提到CSS的时候，宽度和高度并不总是很直接的。CSS的width和height属性只是指定了用于标签中的内容的空间。页面上的一个元素的整体高度和宽度，是通过将width和height和元素上的任何补白和边框组合起来计算的

10.3.2　确定页面上的元素的位置

确定页面上的一个元素的位置，往往是很有用的，可能当访问者的鼠标光标放置在图像上的时候，你想要在图像上显示一个工具提示。这个工具提示相对于该页面上的图像来放置，因此，你需要确定图像在页面上的什么位置，然后将工具提示放置在该位置。jQuery提供了几个函数来帮助做到这点：

·offset（）。offset（）函数返回了一个对象，其中包含一个元素距离文档的左上角偏左和偏上的位置。例如，当访问者的鼠标光标移动到一幅图像上的时候，你想要在图像顶部放置一个横跨的标题。你将需要知道图像的位置。假设图像有一个ID为captionImage。你可能像下面这样收集其顶部、左边的位置：

var imagePosition=$（'#captionImage'）.offset（）；

在这个例子中，变量imagePosition保存了图像的坐标。这些坐标存储在一个JavaScript对象中，并且可以使用本书2.10节介绍的点语法来访问。左边的位置存储在一个名为left的属性中，顶部的位置存储在一个名为top的属性中：

imagePosition.top//number of pixels from the top of the document

imagePosition.left//number of pixels from the left edge of the document

我们假设你想要使用这些信息来放置id为caption的一个div，你可以使用jQuery的.css（）函数（参见本书4.8.2节）来设置该标题的top、left和position属性：

$（'#caption'）.css（{

'position'：'absolute'，

'left'：imagePosition.left，

'top'：imagePosition.top

}）；

注意：offset（）和position（）函数总是返回数字，表示来自左边位置和顶部位置的像素数。也就是说，即便你使用ems或百分比来放置页面上的元素，这两个函数也只是获取元素的像素位置。

·position（）。position（）函数返回一个对象，其中包含了一个元素距离其最近的祖先元素的左上角偏左和偏上的位置。这需要小心一些，因此，让我们通过图10-7来看看它是如何工作的，该图显示了两个div。这两个div都是绝对定位的，outerBox相对于文档来定位，而innerBox的HTML代码位于outerBox div中，是相对于外围的框来定位的。外围的框是相对于文档来定位的（因为它没有嵌入到任何使用绝对、相对或固定定位的元素之中）。对于外围框，position（）就像offset一样起作用，因此，如下代码：

$（'#outerBox'）.position（）//{left：100，top：300}

返回了一个对象，其left属性为100，而top属性为300，因为这些都是在CSS中设置的值。

然而，在内部框的例子中，这些都是相对于外围框来定位的，对于offset（）和position（），你将得到两个不同的结果：

$（'#innerBox'）.offset（）//{left：300，top：550}

$（'#innerBox'）.position（）//{left：200，top：250}

在这个例子中，offset（）返回的位置是相对于文档的，也就是说，内部的框距离文档的左边界300像素，距离文档顶部550像素。另外，position（）函数返回了该div的CSS left和CSS top属性。

offset（）函数通常是最有用的两个函数之一，因为它允许你知道元素相对于整个页面的位置，并且提供了你将其他元素相对于该元素放置到页面上所需要的信息。

图　10-7　jQuery提供了两个函数，允许你确定一个元素在页面上的top和left位置。当你拥有一个绝对定位的元素（#innerBox），而它又位于另一个定位的元素（#outer-Box）之中的时候，这两个函数将返回不同的结果

提示：你也可以使用offset（）函数来设置页面上的一个元素的位置，只要将具有left和top值的一个对象传递给该函数即可，如下所示：

$（'#element'）.offset（{

left：100，

top：200

}）；

只能对left和top使用像素值，em值（20em）或者百分比（20%）将无效。

图　10-8：通常，一个We b页面不会完全位于访问者的浏览器窗口之中。那么，访问者必须滚动来查看页面的其他部分。即便你只是看到了文档的一部分，整个文档还是存在的

10.3.3　确定页面的滚动位置

Web页面常常比显示该页面的浏览器窗口还要大，因为Web文档具有很多内容，往往会导致页面比浏览器还要高，有时候甚至还要宽，这迫使访问者通过滚动来查看整个页面（如图10-8所示）。当访问者滚动页面的时候，一部分文档会从视线中消失。例如，在图10-8中，Web页面不能完全放入浏览器窗口中，文档会向左或向上滚动，因此，页面的顶部和左边都会消失在视野之内。这意味着浏览器窗口的左上角和文档的左上角并不相同。如果试图放置一个新元素，例如，屏幕顶部的一个动态Banner；而如果只是试图将元素的left和top位置设置为0，将会遇到麻烦，你实际上只是将其放到了文档的顶部，但是却位于浏览器窗口之外。

幸运的是，jQuery提供了两个函数，允许你确定从顶部和左边滚动页面的多少部分（换句话说，文档的多少像素存在于浏览器窗口的上边和左边）。要确定文档的多少部分在浏览器窗口之上，使用如下所示这行代码：

$（document）.scrollTop（）

要确定文档的多少部分位于屏幕的左边，使用如下所示这行代码：

$（document）.scrollLeft（）

这两个函数都返回一个像素值，你可以用它来定位页面上的另一个元素。例如，如果你想要将一个弹出窗口定位于页面的中心，即便在某人向下滚动之后也是如此，那么，你需要确定访问者滚动了多远，并且移动弹出窗口，以使得很多额外元素位于页面的下方。在弹出工具的提示的例子中，当访问者已经向下滚动一个页面，要定位工具提示时，你需要小心：很容易意外地将工具提示放到页面空间中，但是，却在浏览器的视口中可见区域之外。实际上，在10.4.4节的教程的第12步，你将会看到如何使用scrollTop（）来避免将一个工具提示放在浏览器窗口顶部的可视区域之上。

10.4　添加工具提示

工具提示是提供补充信息的一种常用方式。当访问者把鼠标光标放在一个链接、单词、照片或任何页面元素上的时候，它们都可以触发较小的弹出窗口。它们常常用来显示一个单词的定义、照片的标题，或者甚至更详细的信息，诸如一个事件的时间、成本和位置。

工具提示背后的思想相当简单：鼠标光标移动到一个元素上，显示靠近鼠标光标悬停元素的另一个元素（通常是一个＜div＞标签），鼠标光标移动到元素之外，工具提示消失。你已经学习了完成这一点所需的所有编程知识，因此，我们将通过一个按部就班的教程来实现这点。

你将学习组合CSS、HTML和JavaScript来产生如图10-9所示的效果。我们使用HTML来标识一个工具提示触发器（鼠标光标悬停其上的对象），并且构建工具提示。使用CSS来为工具提示窗口提供基本的外观，并且最终使用JavaScript来在页面载入的时候隐藏工具提示。我们还要为页面上所有的工具提示触发器添加一个hover事件处理程序。

10.4.1　HTML

工具提示有两个组成部分：第一个组成部分是工具提示本身，这是当访问者的鼠标光标悬停在触发器上的时候所出现的元素；第二个组成部分是触发器，就是诸如图像、链接、标题、段落或＜span＞标签这样的元素。

工具提示是一个＜div＞标签，它带有一个tooltip的class以及一个唯一的ID。你可以将额外的HTML放入诸如标题、段落和图像的div标签中。然而，不要插入链接，因为它们无法正常工作：鼠标光标悬停在要单击的链接的工具提示上的时候，会导致鼠标光标离开触发器，这将导致工具提示消失。

图　10-9　工具提示是带有补充信息的一个框。当访问者的鼠标光标悬停在另一个元素（一个触发器，页面上圆圈圈出的部分）之上的时候，它们会弹出到页面之上，当访问者的鼠标光标移动到元素之外的时候，它们会消失

如下是基本的工具提示的HTML：

＜div class="tooltip"id="aardvarkTooltip"＞

＜h2＞The Aardvark＜/h2＞

＜p＞A medium-sized, burrowing, nocturnal mammal native to Africa＜/p＞

＜/div＞

尽管我们可以将工具提示div放在页面的HTML中的任何地方（大多数时候，它们处于隐藏状态），但是放置这些工具提示的很好的一个地方是，直接放在结束＜/body＞标签之前。这个位置之所以很好，是因为它避免了任何可能的奇怪的显示问题，而如果将它们放置于任何已经绝对或相对定位的另一个标签内部，则有可能引发奇怪的显示问题。

触发器可以是任何元素，一个＜h1＞标签、一个＜div＞标签或一个＜img＞标签。如果你仅仅想要把一个单词或几个单词作为触发器，你需要将这些单词包含在一个＜span＞标签中。触发器需要两段信息才能工作：

·一个class名称。所有的触发器需要共享相同的class名称，例如trigger。Class名称是必需的，因为我们的JavaScript代码能够识别出页面上的触发器，并且应用正确的事件处理程序来打开和关闭相关的工具提示。

·标识工具提示的数据。每个触发器都有相关联的工具提示。工具提示是通常不可见的一个＜div＞标签，但是，当访问者的鼠标光标悬停到触发器上的时候，它会显示出来。每个工具提示都需要一个唯一的ID，并且，我们需要一种方式来将触发器与正确的工具提示div关联起来，以便当访问者的鼠标光标悬停到一个触发器上的时候，知道要显示哪一个div。一种简单的方法是，将工具提示的ID嵌入到触发器的一个属性中（在名称前添加一个#，使用jQuery来选择正确的工具提示将真的变得很容易）。HTML5允许你使用data前缀将数据嵌入到HTML标签中。

例如，假设一个工具提示的触发器是单词aardvark。鼠标光标悬停在其上，将会打开工具提示（实际上，是页面上带有aardvarkTooltip的ID的一个＜div＞标签）。你可以将该单词包含到一个＜span＞中，如下所示：

＜span class="trigger"data-tooltip="#aardvarkTooltip"＞aardvark＜/span＞

HTML5的定制data属性功能真的很不错。它允许Web设计者将各种信息嵌入到标签中，而随后可以用JavaScript来获取这些信息。要了解HTML 5 data属性的详细说明，请访问http：//html5doctor.com/html5-custom-data-attributes/。

如果你一直使用XHTML 1或HTML 4.01 doctypes，并且担心验证，你无法使用data属性。相反，你可以为标签搭载一个有效的HTML 4属性（例如title）：

＜span class="trigger"title="#aardvarkTooltip"＞aardvark＜/span＞

在这种方法中，使用一个title属性并不是该属性本意的用法，并且，一些设计师可能不会赞同这种方法。可能更好的方法是采用HTML 5的方法，并且使用定制的data属性。

你可以在页面中放置任意多个触发器和工具提示。

10.4.2　CSS

每个div都有一个tooltip类，因此，给页面的样式表添加一个.tooltip样式，将会格式化工具提示的整体外观（例如，背景颜色、边框、宽度等）。如下是教程文件中准备好的一个基本样式：

.tooltip{

width：25%；

padding：5px；

background-color：white；

border：3px solid rgb（195，151，51）；

border-radius：5px；

}

此外，没有某些样式化，访问者可能不会知道触发器可以做一些特殊的事情。如果你给段落中的一个单词添加工具提示的话，尤其如此。你可以使用CSS来突出触发器，添加一个边框、一个背景颜色等。例如，这里有一个基本的样式，它给带有trigger类的任何元素添加一个下边框：

.trigger{

border-bottom：1px dashed white；

cursor：help；

}

CSS cursor属性特别方便，当鼠标光标悬停在元素之上的时候，它控制光标的样子。在常规文本之上的时候，光标看上去像是一个选择条，但是，你可以更改光标的外观：help值将光标变成一个问号（这对于提供术语定义的工具提示来说很适用），而pointer值使得光标的行为就像鼠标悬停在一个链接上一样。要了解cursor的更多选项，请查看www.w3schools.com/cssref/pr_class_cursor.asp。

也可以给触发器添加一个：hover状态，以便当鼠标光标悬停其上的时候，它会改变外观：

.trigger：hover{

color：rgb（255，0，0）；}

10.4.3　JavaScript

从最基本的要求来看，工具提示只是显示，然后，当访问者的鼠标光标悬停在一个触发器上的时候消失掉。在本书第6章，我们已经了解了如何让元素出现和消失。然而，还有更多的内容要继续了解。任何工具提示的一个关键部分，是将其放置到触发器附近。这涉及使用jQuery函数来确定一个元素的高度、宽度和位置。这是需要技巧的部分。为了使这个过程更清晰，本教程将把工具提示编程分解为3个部分：

1.隐藏工具提示。

当页面载入的时候，所有的工具提示（位于页面底部的div标签）都应该隐藏。当然，你应该在页面载入之前，在CSS中完成这些，但是，使用不支持JavaScript的浏览器的任何访问者都不能够访问工具提示内容。如果工具提示内容不是必需的信息，并且如果某些访问者（包括搜索引擎）不能看到工具提示也没关系，那么，继续并且在CSS中隐藏工具提示：

.tooltip{

display：none；

}

2.给触发器添加一个mouseover事件处理程序。

这一部分是工具提示功能的核心。当访问者的鼠标光标悬停在触发器之上的时候，需要发生一些事情：

·需要显示相关工具提示的div。

·工具提示需要放置在触发器附近。为了做到这点，你需要确定触发器元素的当前位置。此外，你想要确保工具提示不会覆盖住触发器，并且，工具提示不会扩展到屏幕之外。

3.给触发器添加一个mouseout事件。

这一部分很简单，只需要在访问者的鼠标光标离开触发器的时候隐藏div即可。为了了解具体编程是如何进行的，我们来看看教程并且创建自己的工具提示。

10.4.4　工具提示教程

创建一个基本的工具提示真的很容易。本教程将带领你经历这个过程。你可以使用任意自己喜欢的HTML编辑器。

注意：参见本书1.3节了解有关如何下载教程文件的信息。

1.在文本编辑器中打开chapter10目录下的tooltip.html文件。

这个页面已经有一个内部的样式表，它带有一些用来格式化工具提示和触发器的样式，它们和10.4.2节所介绍的样式相同。然而，该页面没有任何工具提示，因此，我们添加一些代码来完成接下来的任务。

2.在靠近文件底部的地方找到结束的＜/body＞标签，并且为一个工具提示添加如下的HTML：

＜div class="tooltip"id="tip1"＞

＜h2＞A Tooltip＜/h2＞

＜p＞This is the tooltip text.It's inside a div tag

so you can put anything inside one of these.＜/p＞

＜/div＞

最重要的部分是外围的＜div＞标签。它包含了class tooltip，这对于CSS以及我们稍后将要加入的程序来说，都是必需的。此外，一个唯一的ID标识出这个特殊的工具提示，并且允许我们将随后将要添加的触发器和这个工具提示连接起来。div中的内容可能是任何的HTML，在这个例子中，是一个标题栏和一个段落。

3.在文件中间的位置，＜h1＞Tooltips＜/h1＞标题的下面，找到＜p＞标签。将该段落中的几个单词放入一个＜span＞标签中，如下所示：

＜span class="trigger"data-tooltip="#tip1"＞accusamus et iusto＜/span＞

这个类表示这个特殊的span是一个工具提示的触发器，该页面中的一些CSS以一种特殊的方式格式化了带有trigger类的任何标签。此外，data-tooltip属性标识出了这个触发器所属的工具提示HTML。现在，我们将添加另一个工具提示。

4.在步骤2中添加的＜div＞的后面（但是，在结束＜/body＞标签的前面）添加：

＜div class="tooltip"id="tip2"＞

＜h2＞Another Tooltip＜/h2＞

＜p＞＜img src="../_images/small/yellow_h.jpg"

alt="yellow"width="70"height="70"class="imgRight"＞

This is another tooltip.Look there's even a photo here.＜/p＞

＜/div＞

刚刚添加了另一个工具提示。注意，它和前面的工具提示共享同样的class，但是，它拥有自己唯一的ID：tip2。这个工具提示也包含了一幅图像。现在，我们将为这个工具提示添加触发器。

5.在文件某处的一个段落中，找到另外几个单词，并且将其包含在一个span标签中，如下所示：

＜span class="trigger"data-tooltip="#tip2"＞At vero eos＜/span＞

确保为新的工具提示使用#tip2这个ID。你可以继续添加更多的工具提示和触发器，只要确保为每个工具提示使用相同的ID，并且在为触发器设置data-tooltip属性的时候使用相同的ID。

现在要进行一些编程了。页面已经添加了jQuery文件，并且$（document）.ready（）函数也已经准备好了。第一个步骤是在页面载入的时候隐藏所有的工具提示。

6.单击$（document）.ready（）中的空白行，并且输入：

$（'.tooltip'）.hide（）；

这一行代码相当简单。jQuery的hide（）函数（参见6.1.1节）直接隐藏了所有的工具提示，以使得页面载入的时候访问者不会看到它们。当然，当访问者的鼠标光标移动到一个触发器上的时候，你想要单个的工具提示出现，因此，下一个步骤是选择所有的触发器并且添加一个mouseover事件处理程序。

7.在第6行中添加的代码之后输入：

$（'.trigger'）.mouseover（function（）{

}）；//end mouseover

这是一个基本的事件处理程序，就像5.2节所介绍的那些。在这个例子中，选中了所有带trigger类的元素，并且给它们分配了一个mouseover事件。这个函数是工具提示编程的核心，因为它控制了屏幕上的工具提示的显示和放置。识别具体在屏幕上的何处放置一个工具提示，这需要一些技巧，并且，你需要很多不同的信息片才能搞清楚。因此，这个函数的第一部分是创建一系列的变量。

8.在刚才添加的匿名函数中输入如下粗体所示代码：

1$（'.trigger'）.mouseover（function（）{

2 var ttLeft，

3 ttTop，

4}）；//end mouseover

首先创建两个变量，ttLeft保存了工具提示的左边位置，ttTop保存顶部位置。这两个变量此时都是空的，因此，还不需要知道它们应该是什么值。

创建变量的这种方式看上去有点奇怪，你可能习惯使用两个var关键字来创建两个变量，如下所示：

var ttLeft；

var ttTop；

这绝对是可以接受的，但是，当你一次创建一批变量的时候，常用的方法是使用一个var关键字，后面跟着一批变量，都用逗号隔开。这减少了录入，不必重复地输入var。第3行末尾的逗号不是一个输入错误，还有更多的变量需要创建。

9.向代码中添加另一个变量（下面的第4行）：

1$（'.trigger'）.mouseover（function（）{

2 var ttLeft，

3 ttTop，

4$this=$（this），

5}）；//end mouseover

$（this）引用触发器元素，并且$this=$（this）用来把对触发器元素的一个引用存储到一个变量中。我们在10.1.4节的标签页面板教程的步骤3中做了同样的事情。在这个函数中，将会多次引用该触发器元素，并且如果重复使用$（this），将会迫使浏览器的JavaScript解释器每次都运行jQuery函数，这很浪费计算机的资源和时间。相反，通过将$（this）存储到一个变量中，jQuery函数只需要运行一次就可以获取触发器元素，这使得程序更加高效（参见本书13.1.2节了解将jQuery选择存储到一个变量中的好处）。

接下来选择与这个触发器相关的工具提示。

提示：当把一个jQuery选择存储到一个变量中的时候，常见的做法是，在变量名的前面添加一个$，如下所示：

var$banner=$（'#banner'）；

包含一个美元符号，这不是必须的，var banner=$（'#banner'）也能工作。然而，美元符号提醒你，变量保存的是一个jQuery选择，而不是像数字或字符串一样的任何旧的值。

10.添加另一个变量（参见第5行）：

1$（'.trigger'）.mouseover（function（）{

2 var ttLeft，

3 ttTop，

4$this=$（this），

5$tip=$（$this.attr（'data-tooltip'）），

6}）；//end mouseover

变量$tip保存了工具提示的一个jQuery选择。代码$（$this.att r（'data-tooltip'））做了很多事情，因此，这里看看如何分解它。$（）中的部分，即$this.attr（'data-tooltip'），使用jQuery的.attr（）函数来获取触发器的'data-tooltip'属性（记住，$this引用该触发器）。换句话说，这段代码接受了当前的触发器元素，查找一个data-tooltip属性，并且获取其值。例如，对于步骤3中添加的触发器，这段代码返回’#tip1’；对于步骤5中的触发器，这段代码将返回’#tip2’。

一旦获取了data-tooltip值，它会传递给jQuery函数，即上面代码中第5行的outer$（）。换句话说，这段代码最终是$（'#tip1'）或$（'#tip2'）这样的内容。看上去很熟悉吧，这是jQuery选择。一旦上面的第5行运行了，最终将得到名为$tip的一个变量，它包含了针对正确的工具提示的jQuery选择。然后，你可以使用它在屏幕上显示、动画和定位工具提示。

接下来，你需要收集很多的信息，以便在屏幕上放置工具提示。

11.将下面的第6～12行添加到mouseover函数中。

1$（'.trigger'）.mouseover（function（）{

2 var ttLeft，

3 ttTop，

4$this=$（this），

5$tip=$（$this.attr（'data-tooltip'）），

6 triggerPos=$this.offset（），

7 triggerH=$this.outerHeight（），

8 triggerW=$this.outerWidth（），

9 tipW=$tip.outerWidth（），

10 tipH=$tip.outerHeight（），

11 screenW=$（window）.width（），

12 scrollTop=$（document）.scrollTop（）；

13}）；//end mouseover

在这里，你将收集到关于不同的元素的位置和大小的很多信息。图10-10可以帮助你认识这些值：它显示了一个比浏览器窗口大的（带边框的部分）Web页面（灰框）。这个页面向上滚动，因此，其一部分会超出视线而位于浏览器窗口上，并且，由于页面比浏览器窗口要长和宽，页面的部分将会扩展到右边缘和下边缘之外。

上面代码的第6行获取了触发器的顶部和左边位置（图10-10中的#1），由于我们将相对于触发器来定位工具提示，因此需要知道这些。

第7行和第8行使用outerHeight（）函数（参见10.3.1节）和outerWidth（）函数（参见10.3.1节）来获取触发器的height（#2）和width（#3）（包括补白和边框）；而第9行和第10行获取工具提示的width（#4）和height（#5）。由于你不想让工具提示出现在窗口之外，因此还需要知道屏幕的宽度（上面代码的第11行，图10-10中的#6），以及访问者是否将页面向下滚动，如果是的话，滚动了多远（第12行，#7）。不要忘了第12行末尾的；，因为这是我们从第2行开始的var语句的结束。

图　10-10　这张图显示了比浏览器窗口（黑色边框）更高和更宽的一个W e b页面（灰框）。访问者查看的页面已经向下滚动过，因此，页面的顶部部分不可见，页面的右边和底部部分也是不可见的。工具提示放置在文档的末尾，因为这是页面载入的时候放置它的位置。直到步骤15运行代码的时候，程序才确定了工具提示需要出现在哪里，随后将其放置在那里

你可能会问为什么需要这些信息。确定触发器出现在页面上的什么位置，然后将工具提示放在其上面，这不是很容易吗？在大多数情况下，你可以这么做，但是，有几种情况下这种方法实现不了。例如，在图10-11中，触发器位于浏览器窗口的右上角；文档的一部分向上扩展，并且到了窗口的左边。如果工具提示直接放置在触发器上，那么，大多数时候，访问者是看不到它的。换句话说，代码需要更加智能一些：它必须搞清楚，将工具提示放置在触发器上是否会导致它出现在浏览器窗口之外。如果是这种情况，那么，程序必须重新放置工具提示。

首先，我们来看看工具提示直接放在触发器上是否会使其位于浏览器窗口顶端的内部。

12.将如下所示第13～17行代码添加到mouseover函数中：

1$（'.trigger'）.mouseover（function（）{

2 var ttLeft，

3 ttTop，

4$this=$（this），

5$tip=$（$this.attr（'data-tooltip'）），

6 triggerPos=$this.offset（），

7 triggerH=$this.outerHeight（），

8 triggerW=$this.outerWidth（），

9 tipW=$tip.outerWidth（），

10 tipH=$tip.outerHeight（），

11 screenW=$（window）.width（），

12 scrollTop=$（document）.scrollTop（）；

13 if（triggerPos.top-tipH-scrollTop＞0）{

14 ttTop=triggerPos.top-tipH-10；

15}else{

16 ttTop=triggerPos.top+triggerH+10；

17}

18}）；//end mouseover

这里有很多事情要做，但是，首先看一下是否想要相对于触发器来放置工具提示的顶端，这样会有帮助。通常，我们将工具提示放置在触发器上10像素的位置，以便它不会覆盖触发器。要确定顶部位置，你首先获取触发器的顶部位置，然后，减去工具提示的高度，然后再减去10像素。例如，假设触发器距离文档顶部150像素，并且工具提示高100像素。要放置工具提示以使其不会覆盖触发器，用触发器150的顶部位置，减去100得到50像素，然后，减去10像素（这给触发器留下了很小的一点空间）。结果，工具提示放置在距离文档顶部40像素的位置。

如果触发器距离文档顶部10像素，而工具提示高100像素，会发生什么情况？如果你直接按照公式来计算，最终得到10-100，即-90像素的一个顶部位置，超过了文档顶部并且无法看到。

这就是在第13行使用的条件的原因：我们将获取触发器的顶端位置，减去工具提示的高度，然后减去访问者已经向下滚动页面的量。然后，进行测试以确保这个值比0大（如果它小于0，工具提示将放置在浏览器窗口顶端之外）。你还需要考虑到滚动，因为工具提示可能刚好放在触发器之上而没有放置在文档顶端之外，但是，如果页面滚动，它仍然会放置到浏览器窗口的视口之上（这是图10-11所描绘的情况）。

如果该条件为真，那么，工具提示的顶端位置ttTop设置为触发器之上（第14行）。然而，如果该条件为假，运行第16行，它将顶端位置设置为触发器底部10像素（我们通过得到触发器的顶端位置triggerPos.right，然后，加上其高度triggerH，就可以计算得出触发器的底端位置）。

接下来计算工具提示的左边位置。

注意：本节中的示例将工具提示放在触发器之上，但是，你不一定必须这么做。你可以自由地修改程序，看看是否可以让工具提示出现在触发器的下边、左边或右边。

图10-11：当动态地定位页面上的一个元素的时候，需要小心。你不知道访问者浏览器窗口可能有多大，也不知道他是否已经滚动了页面。如果你没有考虑这些事情，很容易意外地将一个元素部分地或全部地放到屏幕之外（图中的阴影区域）

13.将如下所示第18行的代码添加到mouseover函数中：

1$（'.trigger'）.mouseover（function（）{

2 var ttLeft，

3 ttTop，

4$this=$（this），

5$tip=$（$this.attr（'data-tooltip'）），

6 triggerPos=$this.offset（），

7 triggerH=$this.outerHeight（），

8 triggerW=$this.outerWidth（），

9 tipW=$tip.outerWidth（），

10 tipH=$tip.outerHeight（），

11 screenW=$（window）.width（），

12 scrollTop=$（document）.scrollTop（）；

13 if（triggerPos.top-tipH-scrollTop＞0）{

14 ttTop=triggerPos.top-tipH-10；

15}else{

16 ttTop=triggerPos.top+triggerH+10；

17}

18 var overFlowRight=（triggerPos.left+tipW）-screenW；

19}）；//end mouseover

计算左边位置比计算右边稍微困难一点。你不仅需要知道工具提示的一部分是否已经放置到了浏览器窗口右边界之外，还要知道在其外多少。例如，假设一个触发器的左边位置是850像素（在图10-11中用A标记），工具提示是250像素宽（B），并且，浏览器窗口宽度是1000像素（C）。如果你定位工具提示的左边界是850像素，它将从850像素标记的地方横跨到1100像素标记位置（A+B）。这意味着，工具提示的右边100像素是看不见的。为了修正这一问题，你需要知道工具提示扩展到浏览器的右边界之外多少，并且，通过将工具提示向左移动同样的量来调整其左边位置。

上面的第18行计算了工具提示扩展到浏览器窗口右边之外的总量（如果有的话）。为了做到这点，首先计算如果工具提示放置在触发器的左边界时候的最左的边界，即triggerPos.left+tipW（在图10-11中就是A+B）。然后，减去屏幕宽度（C）。如果结果是正值，工具提示的某些部分将位于浏览器窗口之外；如果结果是负值，还有足够的屏幕空间，并且能够很好地放置工具提示。

14.在上一步添加的那行代码之下（上面的第18行），添加如下的代码：

if（overFlowRight＞0）{

ttLeft=triggerPos.left-overFlowRight-10；

}else{

ttLeft=triggerPos.left；

}

实际上，这段代码是说，如果overFlowRight变量保存的数字大于0（这意味着工具提示放不下），那么将工具提示的左边界ttLeft设置为触发器的左边位置减去工具提示可能要超出窗口的量。额外减去10个像素，以防止工具提示碰到了窗口的边界。如果overFlowRight的值小于0，那么工具提示能很好地放置，将其左边位置设置为与触发器的左边位置一致：

ttLeft=triggerPos. left；

这需要很多数学知识！幸运的是，你能搞定。工具提示的顶端位置和左端位置已经计算完毕，因此，现在我们可以让它出现在屏幕上了。终于完成了！

15.添加第24～28行代码以完成mouseover函数。

1$（'.trigger'）.mouseover（function（）{

2 var ttLeft，

3 ttTop，

4$this=$（this），

5$tip=$（$this.attr（'data-tooltip'）），

6 triggerPos=$this.offset（），

7 triggerH=$this.outerHeight（），

8 triggerW=$this.outerWidth（），

9 tipW=$tip.outerWidth（），

10 tipH=$tip.outerHeight（），

11 screenW=$（window）.width（），

12 scrollTop=$（document）.scrollTop（）；

13 if（triggerPos.top-tipH-scrollTop＞0）{

14 ttTop=triggerPos.top-tipH-10；

15}else{

16 ttTop=triggerPos.top+triggerH+10；

17}

18 var overFlowRight=（triggerPos.left+tipW）-screenW；

19 if（overFlowRight＞0）{

20 ttLeft=triggerPos.left-overFlowRight-10；

21}else{

22 ttLeft=triggerPos.left；

23}

24$tip.css（{

25 left：ttLeft，

26 top：ttTop，

27 position：'absolute'

28}）.fadeIn（200）；

29}）；//end mouseover

现在到了激动人心的时刻。使用jQuery的函数串联功能（参见4.6.4节），首先是对工具提示应用.css（）函数（参见4.8.2节），设置其左边位置和右边位置。由于要定位工具提示，其position属性也设置为absolute，然后fadeIn（）函数（参见6.1.1节）很快将工具提示淡入视线。幸运的是，当鼠标光标离开触发器的时候，让工具提示消失，这要简单很多。

16.通过添加如下的30～32行代码完成编程，完成后的代码如下所示：

1$（'.trigger'）.mouseover（function（）{

2 var ttLeft，

3 ttTop，

4$this=$（this），

5$tip=$（$this.attr（'data-tooltip'）），

6 triggerPos=$this.offset（），

7 triggerH=$this.outerHeight（），

8 triggerW=$this.outerWidth（），

9 tipW=$tip.outerWidth（），

10 tipH=$tip.outerHeight（），

11 screenW=$（window）.width（），

12 scrollTop=$（document）.scrollTop（）；

13 if（triggerPos.top-tipH-scrollTop＞0）{

14 ttTop=triggerPos.top-tipH-10；

15}else{

16 ttTop=triggerPos.top+triggerH+10；

17}

18 var overFlowRight=（triggerPos.left+tipW）-screenW；

19 if（overFlowRight＞0）{

20 ttLeft=triggerPos.left-overFlowRight-10；

21}else{

22 ttLeft=triggerPos.left；

23}

24$tip.css（{

25 left：ttLeft，

26 top：ttTop，

27 position：'absolute'

28}）.fadeIn（200）；

29}）；//end mouseover

30$（'.trigger'）.mouseout（function（）{

31$（'.tooltip'）.fadeOut（200）；

32}）；//end mouseout

mouseout的内容很简单：只要当鼠标光标从触发器上移开的时候，淡出任何可见的工具提示。就是这样。保存该文件并且在Web浏览器中测试。该文件的一个完整版本complete_tooltip.html位于chapter10教程文件夹下。

插件提示工具提示，更容易的方法

学习如何编写自己的工具提示，这是掌握用jQuery函数来控制元素的大小和位置的很好的方式。但是，如果你试图找到一些额外的功能，例如，外观漂亮的工具提示框，对话气泡效果，使用Ajax下载工具提示内容的功能，或者精确定位工具提示，那么，这里有大量的jQuery插件，它们提供了比我们在本章中创建的简单脚本更多的功能。

·qTip2（http：//craigsworks.com/projects/qtip2/）是一款强大的工具提示插件。它不仅可以创建类似本教程的简单的工具提示，还可以创建对话气泡，它可以跟着鼠标光标在屏幕上移动，从服务器下载内容并在工具提示中显示，还有很多其他功能。甚至可以使用它创建一个对话框、下拉式菜单等。它就像是工具提示的“瑞士军刀”。

·jQuery Tools Tooltip（http：//flowplayer.org/tools/tooltip/index.html）是另一款不错的工具提示插件。它创建的工具提

示外观漂亮，具有高度的可定制性。当你查看该插件的时候，也查看一下整个jQuery Tools集合（http：//flowplayer.org/t ool s/）。它将自己标榜为“T h e Missing UI Library for the Web”（Web少有的UI库），尽管这是有些夸大的宣传，但它确实基本上满足了很多Web设计师的需求。jQuery工具包含了用于标签页、覆盖、表单、幻灯片（类似于10.3.1节介绍的AnythingSlider）以及其他效果的插件。

·jQuery UI Tooltip（http：//wiki.jqueryui.com/w/page/12138112/Tooltip）。我们在10.2节已经介绍了jQuery UI，它是Web设计师可用的用户界面小挂件和工具的一个较大的集合。尽管（在编写本书时）jQuery UI团队还没有正式发布一个工具提示插件，但是，这方面的很多实际工作已经完成，并且计划在jQuery UI库的1.9版本中推出来。来自jQuery UI的所有插件都是一流的。

第四部分：Ajax与Web服务器通信

第11章　初识Ajax

J avaScript很不错，但是它不能做所有的事情。如果想要显示来自一个数据库的信息，使用来自一个表单的结果完成一封E-mail，或者只是下载其他的HTML，我们都需要和Web服务器通信。对于这些任务，通常需要载入一个新的Web页面。例如，当从一个数据库查找信息的时候，我们通常离开查找页面去往另一个结果页面。

当然，等待一个新页面载入需要时间。并且，如果我们仔细考虑，一个页面消失然后再重新出现的概念通常来讲确实很奇怪。想象一下我们要使用Microsoft Word，并且每次打开一个新的文件，程序的菜单、面板和窗口都突然消失，然后当新文件打开的时候再次重新出现。像Facebook、Twitter、Google Maps和Gmail这样的站点，混淆了Web站点和桌面计算机程序之间的界限。如果说二者之间还有区别的话，就是人们希望Web站点给人的感觉更快、更具有响应性，像桌面程序那样。使得这种新一代的Web应用程序成为可能的技术，是一种叫做Ajax的编程技术。

Ajax允许一个Web页面向Web服务器提出请求并接收来自Web服务器的响应，然后更新自己而不必像以前那样载入一个新的Web页面。结果是Web站点给人的感觉是更具有响应性。例如，当我们访问Google Maps的时候（如图11-1所示），可以放大地图，向北、向南、向东或向西移动，甚至可以抓起地图来回移动。所有这些响应都不用载入一个新的Web页面就发生了。

图　11-1　Google Maps（http：//maps.google.com）是使用Ajax刷新页面内容而不必载入一个新的W e b页面的第一个大规模站点。这个站点的响应性基于只有地图数据改变这样一个事实，在请求新的地图信息时，页面的其他部分，例如logo、搜索框、搜索结果栏和地图空间都保持不变

11.1　Ajax

术语Ajax最初产生于2005年，用来描述Google的Google Maps（http：//maps.google.com）、Gmail（www.gmail.com）和Google Suggest（www.google.com/webhp?complete=1＆hl=en）等新一代Web站点所具备的基本特征。Ajax表示Asynchronous JavaScript and XML（异步JavaScript和XML），但是，它不是像HTML、JavaScript或CSS这样的一种“正式的”技术。它是表示一些技术的混合交互（JavaScript、Web浏览器和Web服务器）的一个术语，它使得我们可以获取和显示新的内容而不必载入一个新的Web页面。

注意：如果想要阅读关于Ajax在哪里第一次提出的博客帖子，请访问www.adaptivepath.com/ideas/essays/archives/000385.php。

简而言之，当前的Web浏览器允许我们使用JavaScript来向Web服务器发送一个请求，而Web服务器反过来把数据送回到Web浏览器。JavaScript程序接收数据，然后对它做些事情。例如，如果单击Google Maps页面上的“north”箭头按钮，页面的JavaScript从Google服务器请求一个新的地图数据。新的信息随后用于显示一个新的地图。

尽管我们可能不会再创建下一个Google Maps，但还是有很多简单的事情可以用Ajax技术做到：

·显示新的HTML内容而不用载入页面。例如，在列出新闻标题的一个页面上，当访问者单击一个标题的时候显示文章，你可以避免枯燥地等待新页面载入。相反，新闻报道可能出现在同一Web页面的右边，而banner、边栏和页脚或者其他的页面内容都不需要重新载入。我们将在10.3.2节学习如何做到这点。

·提交一个表单并且立即显示结果。例如，假设有一个“注册新闻组”表单，当某人填写并提交了表单，表单消失并且立即出现一条“你已经注册了我们的新闻组”的消息。我们将在11.3.3节学习如何使用Ajax制作这样的表单。

·登录而不用离开页面。这里有另外一个表单也和使用JavaScript相关，该页面带有一个小小的“登录”表单。填写了该表单，单击“登录”按钮，并且用户不仅登录了，而且页面会转而展示用户特定的登录状态、用户名以及可能的其他信息。

·星级评定挂件。在列出图书、电影和其他产品的站点上，我们经常看到星级评定，通常使用1～5来表示访问者如何评价项目的质量。这些评定系统通常使我们能够通过单击星级的数目来表达自己的观点。使用Ajax，我们可以允许访问者投票而不用离开Web页面，他们所必须做的只是单击星级。这里有一个可以做到这点的漂亮的jQuery插件：http：//plugins.learningjquery.com/half-star-rating/。

·遍历数据库信息。Amazon是可以浏览的在线数据库的一个典型例子。当在Amazon上查找一本书的时候，例如，找一本JavaScript图书，将会得到Amazon销售的JavaScript图书的一个列表。通常，这里会有很多的图书，以至于一个页面放不下，因此，我们需要从一个页面跳转到另一个页面来查看“下10个项目”。使用Ajax，我们可以在数据库记录之间移动而不必跳转到其他页面。Twitter是这样使用Ajax的：当你浏览Twitter页面的时候，会看到你所follow的人的一系列的tweet。如果滚动到该页面的底部，Twiter将载入更多的tweet。再次滚动，还会出现更多的tweet。这就好像是一个永远不会结束的Web页面！

除了“不要载入一个新页面”部分，上面列出的任务都没有什么创新的地方，我们可以使用常规的HTML和一些服务器端编程（例如，来收集数据或访问数据库信息）来实现同样的基本的结果。然而，Ajax使得Web页面更具响应性，并且提升了站点的用户体验。实际上，Ajax允许我们创建Web站点并且使其感觉更像是桌面程序而不像Web页面。

11.2　Ajax基础知识

综合来考虑，Ajax背后的技术确实很复杂。它们包括JavaScript、服务器端编程以及Web浏览器的协同工作。然而，基本的概念很容易理解，只要你理解了所涉及的所有步骤。图11-2展示了传统的HTML Web页面和使用Ajax与Web服务器通信的Web页面之间的不同。

图　11-2　传统方式下，Web浏览器和服务器的通信（上图所示）包括从服务器请求一个文件，并且接收一个返回的Web页面。这是Web页面的一个常规的载入和重新载入的过程。使用Ajax, Web浏览器只是请求信息。服务器返回请求的数据，并且Web页面的内容和显示更新（当然，在JavaScript的帮助下）

11.2.1　拼图块

Ajax不是一种单一的技术，它混合了几种不同的技术一起工作，来产生一种更有效率的用户体验。实际上，Ajax把3种组件结合到了一起：

·Web浏览器。显然，我们需要通过b浏览器来浏览W e b页面并运行JavaScript，但是，还有一个秘密的组成部分构建到了大多数Web浏览器中，它使得Ajax成为可能，这就是XMLHttpRequest对象。这个听起来有些奇怪的术语就是使得JavaScript能够和Web服务器通信并接收响应信息的东西。

XMLHttpRequest对象实际上是多年前在Internet Explorer 5中引入的，目前，它已经逐渐进入所有主流Web浏览器中。我们将在11.2.2节学习其更多的内容。

·JavaScript承担了Ajax中的大部分体力活。它向Web服务器发送请求，等待响应，处理响应，并且（通常）通过添加新的内容或者以某种方式改变页面的显示来更新页面。根据我们希望自己的程序做什么，可能让JavaScript从表单发送信息、请求其他的数据库记录，或者直接发送单独一段数据（例如，访问者刚刚给一本书的评级）。在数据发送给服务器之后，JavaScript程序将等待来自服务器的响应，例如，其他的数据库记录或者只是像“你的投票已经统计”一样的一条简单的文本消息。

有了这些信息，JavaScript将更新Web页面，例如，显示新的数据库记录，或者通知访问者已经成功登录。更新一个Web页面涉及操作页面的DOM（文档对象模型，参见4.5节的讨论）来添加、修改或删除HTML标签和内容。实际上，这就是我们在本书大部分内容中所做的事情：使用JavaScript修改页面的内容和外观。

·Web服务器接收来自Web浏览器的信息，并且把信息发送给Web浏览器。服务器可能只是返回一些HTML或纯文本，或者它可能返回一个XML文档（参见11.3.7节的“高级用户提示：接收来自服务器的XML”部分）或者JSON数据（参见11.4节的介绍）。例如，如果Web服务器接收来自表单的信息，那么，它可能会向数据库添加信息或者把“添加记录”这样的一条确认消息发送回来。或者，JavaScript程序可能向数据库发送查找下10条记录的请求，并且Web服务器将送回下10条记录的信息。

这个过程的Web服务器部分有点难度。它通常涉及几种不同类型的技术，包括一个Web服务器、一个应用程序服务器和一个数据库服务器。Web服务器真正是一个壮观的文件柜：它存储文档，并且当Web浏览器请求一个文件的时候，Web服务器会发送它。要完成更为复杂的任务，例如，把来自一个表单的数据放入一个数据库中，我们还需要一个应用程序服务器和一个数据库服务器。应用程序服务器理解一种服务器端编程语言，例如PHP、Java、C#、Ruby或Code Fusion标记语言，并且允许我们执行仅用HTML页面不可能完成的任务，例如发送邮件、在Amazon查询图书价格或者在数据库中存储信息。数据库服务器允许我们保存客户的名字和地址、销售的产品的细节或收藏的购物单的存档等信息。常见的数据库服务器包括MySQL、PostgreSQL和SQL Server。

注意：这里的术语服务器可以指硬件或者软件。在本书中，术语应用程序、Web服务器和数据库服务器都是指可能（并且经常）在同一机器上运行的软件。

Web服务器、应用程序服务器和数据库服务器有很多种不同的组合。例如，可以使用Microsoft的IIS Web服务器，以及ASP.NET（应用程序服务器）和SQL Server（数据库服务器）。或者，可以使用Apache（Web服务器）、PHP（应用程序服务器）和MySQL（数据库服务器）。

注意：Apache、PHP和MySQL的组合（通常简写为AMP）是免费的而且非常普及。我们会看到很多Web托管公司提供这些服务器。本书的示例也是使用AMP（参见后面的介绍）。

快速熟悉安装一个Web服务器

Ajax和Web服务器一起工作，毕竟，它的主要目的是让JavaScript向服务器发送信息并从服务器获取信息。尽管本章以及下一章中的教程都将在没有Web服务器的本地计算机上运行，但如果想要进一步探索Ajax的世界，则可能需要访问一个Web服务器。如果已经知道了Internet上的一个Web站点，一个选择是通过把文件移动到Web服务器上来测试Ajax程序。遗憾的是，这种技术很麻烦，我们必须在自己的计算机上创建页面，然后使用一个FTP程序将其移动到Web服务器上来看它能否工作。

更好的方法是设置一个开发服务器，这包括在桌面计算机上安装一个Web服务器，以便可以直接在自己的机器上编程并测试Ajax页面。这个任务听上去可能有些可怕，但是，有众多的免费程序使得安装必备的组件变得只是双击一个文件那么简单。

在Windows上，我们可以使用WAMP（www.wampserver.com/en/）来安装Apache、PHP和M ySQL。WAMP是一个免费的安装程序，它安装了模拟寄存在I nte r ne t上的一个真实的

Web站点所需的所有元素。我们可以在http：//uptospeedguides.com/wamp/找到完整的视频安装说明。

对于Mac用户，MAMP（www.mamp.info/en）提供了一个容易使用的程序，其中包含了Apache、PHP和MySQL。它也是免费的。可以在http：//uptospeedguides.com/mamp找到MAMP的视频安装说明。

本书11.3.7节的教程需要AMP。因此，如果想要跟着学习教程，需要使用上述的两种程序中的一个在自己的计算机上安装AMP。如果已经有了使用不同的Web服务器（例如Microsoft的IIS）的一个Web站点，并且计划创建一个想要在自己的真实Web站点上使用的Ajax应用程序，我们可能要在自己的计算机上安装该Web服务器。有很多资料介绍如何安装IIS。如果想要在Vista上安装IIS，访问http：//learn.iis.net/page.aspx/85/installing-iis7/。XP用户可以访问www.webwiz.co.uk/kb/asp-tutorials/installing-iis-winXP-pro.asp。

11.2.2　和Web服务器通信

Ajax程序的核心是XMLHttpRequest对象。XMLHttpRequest对象有时候简称为XHR，它是构建到当前Web浏览器中的一种功能，允许JavaScript向Web服务器发送信息并反过来从Web服务器接收信息。包括5个基本步骤，都可以使用JavaScript来完成。

1.创建XMLHttpRequest对象的一个实例。

第一个步骤只是告诉We b服务器：“嗨，我想要向We b服务器发送一些消息，所以请准备好”。按照最为基本的形式，用J a v a S c r i p t创建一个XMLHttpRequest对象的方法如下所示：

var newXHR=new XMLHttpRequest（）；

遗憾的是，使用Ajax有很多跨浏览器的问题，因此，使用JavaScript库（例如jQuery）来发送Ajax请求更好。我们将在11.3节学习使用jQuery的方式。

2.使用XHR的open（）方法指定要发送何种数据以及数据去往何处。

可以用两种方式发送数据，使用GET方法或POST方法，这和HTML表单所使用的选项相同。GET方法向Web服务器发送作为URL一部分的任何信息，例如，shop.php?productID=34。在这个例子中，数据是跟在?：后面的信息，即productID=34，它表示一个名/值对，而productID是名字，34是值。把这个名字看做一个表单上字段的名字，而值就是访问者在改字段输入的内容。

注意：我们为open（）方法指定的URL必须和发出请求的页面在同一个Web站点上。为了安全起见，Web浏览器不允许对其他的域发出Ajax请求。

POST方法发送独立于URL的数据。通常，我们使用GET方法来从服务器取回数据，并且使用POST方法来更新服务器上的信息（例如，添加、更新或删除一条数据库记录）。我们将在11.3.3节学习如何使用这两种方法。

我们还使用了open（）方法来指定数据发送到服务器上的哪个页面。这通常是Web服务器上的一个页面，它使用一种服务器端脚本语言（例如PHP）来从数据库获取数据或执行一些其他的编程任务，并且我们通过其URL指向它，例如，如下的代码告诉XHR对象使用哪个方法（GET）以及请求服务器上的哪个页面：

newXHR.open（'GET'，'shop.php?productID=34'）；

3.创建函数操作结果。

当Web服务器返回一个结果，例如新的数据库信息、表单在处理中的一个确认，或者只是一条简单的文本消息时，我们通常想要对结果做一些事情。这可能是简单地显示“表单提交成功”之类的消息，或者使用带有记录的一个新表格来替代整个数据库记录的表格。在任何情况下，都需要编写一个JavaScript函数来处理这些结果，这个函数（叫做回调函数）通常是程序的主要部分。

通常，这个函数将通过删除元素（例如，使用Ajax删除刚刚提交的一个表单）、添加元素（一条“表单提交成功”的消息，或者是数据库记录的一个新HTML表格），或者改变元素（例如，突出显示访问者单击来评价一个产品的星级数目），来操作页面的内容（也就是说，改变页面的DOM）。

这里还包括一些其他的步骤，但是，我们将使用jQuery来处理这些细节，因此，关于回调函数，我们只需要理解其JavaScript代码是用来处理服务器响应的。

4.发送请求。

要真正向Web服务器发送请求，我们可以使用XHR对象的send（）方法。此前的所有步骤还都只是准备，而这个步骤则告诉Web浏览器：“我们准备好了，发送请求吧！”如果要使用GET方法，这个步骤就像下面这样简单：

newXHR.send（null）；

null部分表示我们不打算发送任何附加数据（别忘了，当使用GET方法时，数据像search.php?q=javascript这样放在URL中发送，而q=javascript才是数据）。另外，使用POST方法，数据则要和send（）方法一起提供：

newXHR.send（'q=javascript'）；

再次，不要关心这里的细节，从下一节开始，我们将看到jQuery如何极大地简化这一过程。

一旦发送了请求，JavaScript程序不需要停止。Ajax中的“A”表示异步，这意味着一旦发送了请求，JavaScript程序可以继续做其他的事情。Web浏览器不会只是坐等服务器的响应。

5.接收响应。

在服务器处理了请求之后，它会向Web浏览器送回一个响应。实际上，我们在步骤3中创建的回调函数就是处理这个响应的，但是，当Web服务器响应的时候，XHR对象同时接收到多段信息，包括请求的状态、文本响应以及可能的一个XML响应。

状态响应是一个数字，表示服务器如何响应请求：我们可能已经熟悉了状态号404，它表示没有找到文件。如果一切按照计划进行，我们将得到一个200或者可能是304的状态。如果在处理页面的时候有一个错误，将会得到一个500“Internal Server Error”的状态报告，并且，如果请求的文件受到密码保护，我们将会得到一个403“Access Forbidden”错误。

此外，大多数时候，我们会接收到一条文本响应，它存储在X H R对象的responseText属性中。这个响应可能是一段HTML、一条简单的文本消息或者是一组复杂的JSON数据（参见11.4节）。最后，如果服务器用一个XML文件来响应，它会存储到XHR对象的responseXML属性中。尽管XML仍然在使用，但是，编程服务器页面返回文本、HTML或JSON数据的情况更为常见，因此，我们可能不需要处理一个XML响应。

不管服务器返回何种数据，它都可以供回调函数用来更新Web页面。一旦回调函数完成，整个Ajax周期也就结束了（然而，我们可能还会同时发送多个Ajax请求）。

11.3　Ajax的jQuery方式

浏览器之间有很多的不同，以致我们必须为Ajax程序编写额外的代码，以使其工作于Internet Explorer、Firefox、Safari和Opera中。尽管基本的XMLHttpRequest过程并不是很复杂，由于每次我们必须采取很多步骤来发送一个XHR请求，如果采用一个JavaScript库的话，Ajax编程会进行得更快。

jQuery库提供了几个函数来很好地简化整个过程。毕竟，如果看一下Ajax请求的5个步骤（参见11.2.2节），将会看到有趣的内容，实际对服务器的响应做一些事情的程序都仅仅在一个步骤中发生（步骤3）。jQurey简化了所有这些步骤，以便我们可以把精力集中在真正有趣的编程上。

11.3.1　使用load（）函数

jQuery提供的最简单的Ajax函数是load（）。这个函数把一个HTML文件载入到页面上的一个特定元素中。例如，假设Web页面有一个区域用于新闻标题的一个简短列表。当页面载入的时候，显示5条最新的新闻主题。我们可能想要添加一些链接，让访问者选择在页面的这一区域中显示何种类型的新闻报道：例如，昨天的新闻、国内新闻、体育新闻等。我们可以通过链接到单独的Web页面来做到这点，而每个页面包含了相应的新闻项目，但是，那会迫使访问者移动到另外一个Web页面（这根本不是使用Ajax）。

另一种方法是直接把选择的新闻报道载入到页面中的新闻标题框中。换句话说，每次访问者单击一个不同的新闻类型，Web浏览器都从服务器请求一个新的HTML文件，然后把该HTML放置到标题框中，而不用离开当前的页面（如图11-3所示）。

快速熟悉了解服务器端的方式

除非使用jQuery的基本load（）函数（参见11.3.1节）来把服务器上的一个页面中的HTML插入到Web浏览器的页面中，否则我们将需要让服务器端程序使用Ajax。Ajax的主要特点在于允许JavaScript和服务器通信并从服务器获取信息。大多数时候，这意味着Web服务器上有另一个脚本运行着，完成了JavaScript所不能完成的任务，例如从服务器读取信息、发送E-mail或用户登录。

本书并不介绍服务器端编程，因此我们不需要学习如何使用PHP、.NET、JSP、ASP或Cold Fusion（或者你需要能够进行服务器端编程的某个人来替你考虑这些事）这样的一种服务器端技术来编程。如果还没有选择一种服务器端语言，PHP是一个很好的起点，它是最流行的Web服务器编程语言之一，它是免费的，

而且几乎每个Web托管公司都在自己的服务器上提供PHP。它是功能强大的Web构建语言，并且相对容易学习。如果你想要通过PHP开始学习服务器端编程，可以阅读《Learning PHP, MySQL, and JavaScript》（O'Reilly）、《Head First PHP, MySQL》（O'Reilly）或《PHP Solutions：Dynamic Web Design Made Easy》（Friends of Ed）等书。这些图书中的任何一本都是很好的初级读物。

还有很多学习PHP的免费网上资源。来自Zend（支持PHP开发的主要公司中的一家）的PHP入门（http：//devzone.zend.com/6/phP-101-php-for-the-absolute-beginner/）拥有很多基本的（和高级的）信息。W3Schools Web站点（网址为www.w3schools.com/PHP）也有很多面向PHP初级程序员的信息。

要使用load（）函数，首先使用一个jQurey选择器来表示页面上请求的HTML应该放置到的元素，然后，调用load（）函数并传递想要访问的页面的URL。例如，假设有一个ID为headlines的＜div＞标签，并且我们想要把文件todays_news.html中的HTML载入到该＜div＞中。可以像下面这样做：

$（'#headlines'）.load（'todays_news.html'）；

这行代码运行的时候，Web浏览器从Web服务器请求todays_news.html文件。当该文件下载后，浏览器使用新文件的内容来替换ID为headlines的＜div＞中当前的内容。这个HTML文件可能是一个完整的页面（包括＜html＞、＜head＞和＜body＞标签），或者只是一段HTML，例如，请求的文件可能只是一个单个的＜h1＞标签和一段文本。如果文件不是一个完整的Web页面，也没有问题，因为load（）函数只是向当前（完整的）页面中插入HTML片段。

注意：我们只能从同一站点载入HTML文件。例如，不能从自己的站点使用load（）函数把Google的主页载入到页面中的一个＜div＞里（可以使用一个iframe来显示来自另一个站点的页面，8.4节介绍的FancyBox插件使用了这一技术）。

图　11-3　上图表示访问附加的HTML链接的常规方法。单击页面上的一个链接（左边）并且它载入一个全新的页面（右边）。然而，使用Ajax和jQuery的load（）函数，我们可以访问同样的HTML而不必离开当前Web页面（如下图所示）。单击一个链接会在＜div＞标签（如圆圈所示）中载入HTML内容

在使用load（）函数的时候，我们必须对文件路径非常小心。首先，传递给load（）函数的URL和当前页面有关系。换句话说，我们使用和从当前页面链接到想要载入的HTML文件相同的路径。此外，当HTML载入到文档中的时候，HTML中的任何文件路径不会重写，因此，如果我们在载入的HTML文件中有一个链接或包含图像，这些URL需要和使用load（）的页面相关才能起作用。换句话说，如果使用了文档相对路径（参见1.2.2节中的“快速熟悉：URL类型”部分）并且载入的HTML文件位于Web站点的另一个目录下，当HTML载入到当前页面的时候，图像和链接可能无法工作。有一个简单的解决方法：只是用根相对目录的链接，或者确保载入的文件和使用load（）函数的页面位于同一目录下。

load（）函数甚至允许我们指定想要把下载HTML文件的哪一部分添加到页面中。例如，假设请求的页面是站点的一个常规Web页面；它包含了所有常规的Web页面元素，例如banner、导航栏和页脚。我们可能只是对于页面的一个单个区域中的内容感兴趣，例如，只是一个特定的＜div＞及其内容。要指定想要载入页面的哪一部分，可以在URL的后面插入一个空格，跟着是一个jQuery选择器。例如，假设在上面的例子中，我们只是想要把ID为news的一个＜div＞中的内容插入到todays_news.html文件中。可以通过如下代码做到这点：

$（'#headlines'）.load（'todays_news.html#news'）；

在这个例子中，Web浏览器下载页面todays_news.html，但是，它只是提取了ID为news的＜div＞标签（及其中的所有内容），而不是把文件的整个内容都插入到标题＜div＞中。我们将在后面的教程中看到这一技术。

11.3.2　教程：load（）函数

在这个教程中，我们将使用jQuery，用一种更具响应性的方法来替代传统的单击并载入的访问HTML的方法（如图11-3上图所示），这种方法使用新的HTML直接替换当前页面上的内容（如图11-3下图所示）。

概览

要了解将在这个教程中做些什么，首先需要理解准备“Ajax化”的页面的HTML。看一下图11-4，页面上有链接的一个符号列表，每个链接都指向包含不同新闻标题的一个页面。＜ul＞标签用来创建ID为newslinks的列表。此外，在右边的边栏中还有一个空的＜div＞标签（在标题“News Headlines”的下方）。这个＜div＞拥有一个ID headlines，并且，此时是一个空的占位符。最后，只要你使用了jQuery的load（）函数，单击其中一个链接将在＜div＞中载入新闻报道。

现在，单击一个链接只是打开带有一系列新闻项目的一个Web页面。换句话说，这个页面就像常规的HTML一样工作，它拥有指向其他页面的链接。实际上，没有了我们打算添加的花哨的JavaScript，这个页面也完全能很好地工作，它将帮助任何访问者找到他们想要的新闻。这是件好事情，因为并不是每个人的浏览器都支持JavaScript。此外，如果只能够通过JavaScript的方式才能获取这些新闻项目，搜索引擎可能会忽略有价值的内容。

图　11-4　当使用JavaScript来给页面添加内容时，通常插入带有ID的一个空＜div＞。然后，可以在需要的时候选择该＜div＞并插入内容。例如，这个页面的右边栏上有一个空的＜div＞（＜div id="headlines"＞）。借助一点Ajax的功能，很容易就能使用页面中间列出的3个链接中的任何一个的内容来填充这个＜div＞

注意：可以从硬盘上直接使用load（）而不使用Web服务器，因此，对于下面的教程，我们不需要在计算机上安装一个服务器（参见11.2.2节中“快速熟悉：安装一个Web服务器”部分的介绍）。

这个教程提供了渐进增强（progressive enhancement）的一个例子，在没有JavaScript的时候，其功能也很好，但是，有了JavaScript，工作得会更好。换句话说，每个人都可以访问内容，没有谁会被遗漏。要实现渐进增强，我们将添加JavaScript来“劫持”常规的链接功能，然后，获取链接的URL，然后把链接下载到页面并将其内容放入空的＜div＞中。就是这么简单。

注意：参见1.3节中的“注意”部分以获取如何下载教程文件的信息。

编程

1.在文本编辑器中打开chapter11文件夹中的load.html文件。

首先，我们为页面主要部分的符号列表中的每个链接指定一个click事件函数。这个符号列表（＜ul＞标签）有一个ID为newslinks，因此，可以很容易地使用jQuery来选择这些链接中的每一个并给它们指定一个click（）函数。

2.单击$（document）.ready（）函数中的空行，并输入：

$（'#newslinks a'）.click（function（）{

}）；

$（'#newslinks a'）是用来选择这些链接中的每一个的jQuery方法，并且.click（）函数允许给click事件（如果需要回顾事件的知识，请参阅5.2节介绍）指定一个函数（一个事件处理程序）。

下一步是从每个链接中提取URL。

3.在click（）函数内（上面步骤2中的空行）输入var url=$（this）.attr（'href'）；，并且按下回车键创建一个空行。

这行代码创建了一个新的变量（url），并且将链接的href属性的值赋给它。回顾4.6.4节的介绍，当我们给一个jQuery选择（在这个例子中是$（'#links a'））附加一个函数（例如click（）函数）的时候，jQuery遍历选择中的每个元素（每个链接）并且把函数应用到每个链接。$（this）只是获取当前在操作的元素的一种方法。换句话说，$（this）将随着jQuery遍历元素的集合而引用一个不同的链接。attr（）函数（参见4.9节的介绍）可以为一个标签获取或设置特定的元素，在这个例子中，该函数提取href属性来获取链接所指向的页面的URL。在下一步中，该URL将和load（）函数一起使用来获取页面的内容并在该页面的一个＜div＞中显示。

4.输入$（'#headlines'）.load（url）；，以使得脚本如下所示：

$（'#newslinks a'）.click（function（）{

var url=$（this）.attr（'href'）；

$（'#headlines'）.load（url）；

}）；

还记得页面上的空的＜div＞标签吗？下载的HTML将放入其中，该标签有一个ID为headlines，因此$（'#headlines'）选择了这个＜div＞。然后，load（）函数下载前一行代码所获取的URL的HTML，然后把该HTML放入＜div＞标签中。是的，实际上幕后有很多其他的内容使得这一切得以实现，但是，有了jQuery，我们不必再操心这些。

这个页面还没有完成。如果保存该文件并在Web浏览器中预览，那就去试一下吧，你将会注意到，单击一个链接并没有在页面上载入新的内容，它实际上离开了当前页面并载入了链接的页面。Ajax发生了什么情况呢？它实际上仍然存在，只不过Web浏览器遵从在单击链接的时候载入一个新的Web页面的常规行为。要防止这种情况，必须阻止浏览器打开链接。

5.在上一步中输入的代码的后面添加一个新的空行，并且输入return false；，使得脚本如下所示：

$（'#newslinks a'）.click（function（）{

var url=$（this）.attr（'href'）；

$（'#headlines'）.load（url）；

return false；

}）；

这个简单的步骤告诉We b浏览器：“嗨，We b浏览器，不要打开那个链接。”这是阻止浏览器按照其常规行为来响应事件的一种方式。我们也可以使用5.4.4节介绍的jQuery的preventDefault（）函数来实现同样的效果。

6.保存该文件并在Web浏览器中预览。单击其中的一个链接。

现在，还有另外一个问题，我们可以从图11-5中看到。load（）函数能够工作，只不过下载文件拥有很多我们不想要的HTML，尤其是banner“JavaScript＆jQuery”出现了两次。我们真正想要的只是该Web页面的一部分，即包含了新闻项目的区域。幸运的是，load（）函数在这里能够帮上忙。

图　11-5　jQuery的load（）函数将为一个特定的文件下载所有的HTML并且将其放置到当前页面上的一个元素中。如果下载的文件包含了不需要的HTML，例如，重复的banner和页脚，结果看上去好像一个页面在另一个页面之中

7.找到带有load（）函数的一行并在url的后面添加+’#newsItems'。完成后的代码如下所示：

$（'#newslinks a'）.click（function（）{

var url=$（this）.attr（'href'）；

$（'#headlines'）.load（url+’.newsItem'）；

return false；

}）；

正如11.3.2节所介绍，我们可以指定让load（）函数把一个下载后的文件的哪一部分添加到页面。要做到这一点，可以在URL的后面添加一个空格，跟着是一个选择器，表示想要显示下载的页面的哪一部分。

这里来对代码进行仔细分析：首先，每个链接页面上有一个ID为newsItems的＜div＞标签。这个div包含了我们想要的HTML，即新闻项目。因此，可以通过在URL的后面添加一个空格，后面跟着#newsItems，一起传递给load（）函数，从而告诉load（）函数只是插入下载的HTML的一部分。例如，如果想要载入页面today.html并且只将newsItems div中的HTML放入到headlines div中，可以使用如下代码：

$（'#headlines'）.load（'today.html#newsItem'）；

在这个例子中，需要组合两个字符串：url变量的内容和’#newsItems'，来得到正确的代码，因此，我们使用了JavaScript的字符串连接操作符（+符号）：load（url+’#newsItems'）（如果需要回顾组合两个字符串的知识，参阅2.5.3节）。

8.保存该文件并且在Web浏览器中预览。单击链接进行测试。

现在，来自每个链接页面的新闻项目并且只有新闻项目出现在页面中间的框中。Ajax只在数行代码之中。（可以在chapter11文件夹下的complete_load.html中看到本教程的一个完整版本。）

11.3.3　get（）和post（）函数

本书11.3.1节介绍的load（）函数是从Web服务器获取HTML并将其插入一个页面中的一种快速方法。但是，服务器不会总是返回纯粹的HTML，它可能返回一条消息、一个代码编号，或者是需要使用JavaScript进一步处理的数据。例如，如果想要使用Ajax来获取某些数据库记录，服务器可能返回包含了这些记录的一个XML文件（参见本章的“高级用户提示：接收来自服务器的XML”部分）或一个JSON对象（参见11.4节的介绍）。我们不能仅仅把数据插入页面中，首先必须获取数据并以某种方式处理它以产生需要的HTML。

jQuery的get（）和post（）函数提供了简单的工具来向Web服务器发送数据或从Web服务器获取数据。正如11.2.2节的步骤2所介绍，在使用GET或POST方法的时候，我们需要对XMLHttpRequest对象略有区别。然而，jQuery负责这两种方法之间的任何区别，因此get（）和post（）函数以相同的方式工作。（那么，该使用哪一个呢？请阅读本章后面的“常见问题：GET还是POST”部分）。

这两个函数的基本结构是：

$.get（url, data, callback）；

或

$.post（url, data, callback）；

和大多数其他的jQuery函数不同，我们不会把get（）和post（）添加给一个jQuery选择器，换句话说，我们不会做像下面这样的事情：$（'#mainContent'）.get（'products.php'）。这两个函数彼此独立，并且不会和页面上的任何元素连接起来，因此，我们只是使用$符号，后面跟着一个句点，再跟着get或post：$.get（）。

get（）和post（）函数接收3个参数：url是包含了处理数据的服务器端脚本的路径的一个字符串（例如，'processForm.php'）。data参数是一个字符串或者一个JavaScript对象直接量，包含了要发送给服务器的数据（我们将在下一节中学习如何创建它）。最后，callback是处理服务器返回的信息的函数（参见6.4节中的“高级用户提示：在效果完成后执行一个操作”部分，以了解关于编写回调函数的详细内容）。

不管是运行get（）函数还是运行post（）函数，Web浏览器都把数据送出到一个特定的URL。当服务器向浏览器送回数据的时候，浏览器把数据传递给回调函数，回调函数处理信息并且通常以某种方式更新Web页面。我们将在11.3.7节看到一个实际的例子。

11.3.4　格式化发送给服务器的数据

使用Ajax编写JavaScript程序的大多数时候，我们都要向服务器发送信息。例如，如果想要获取一个数据库中存储的一种特定产品的信息，可以发送表示产品的一个单独的号码。当Web服务器从XHR请求获取了号码，它在数据库中查找匹配该号码的这种产品，获取产品信息，并将其发送回Web浏览器。或者，我们可能使用Ajax来提交有用信息的整个表单，作为一个在线订单或“注册邮件新闻组”的表单的一部分。

在这些情况下，我们需要以get（）和post（）函数所理解的某种方式来格式化所请求的数据。传递给这两个函数的第二个参数包含了想要发送给服务器的数据，我们可以把这一数据格式化成一个查询字符串或者是一个JavaScript对象直接量，如接下来的两节所述。

常见问题GET还是POST

将数据提交给Web服务器的两个方法：GET和POST，似乎有很多相同之处。那么该使用哪一个呢？

答案是，看情况而定。在某些情况下，我们无法选择。例如，假设要给服务器上已经准备好并运行的一个服务器端脚本发送信息。换句话说，服务器端编程已经完成了，并且我们只需要使用JavaScript与其通信。在这种情况下，我们使用该脚本所期待的方法。更有可能的是，程序员设置好了脚本接收GET或POST数据。因此，可以和程序员沟通或者查看脚本来确定它使用了哪个方法，然后使用相应的jQuery函数get（）或post（）。

如果你（或者其他程序员）还没有编写JavaScript要与之通信的服务器端脚本，那么，可以选择方法。GET方法适合不会影响到服务器上的一个数据库或文件的状态的那些请求。

换句话说，当我们想要获取信息的时候使用它，例如询问一种特定产品的价格或者获取最流行的产品的列表。POST方法用于发送那些将会改变服务器上的信息的数据，例如，删除一个文件、更新一个数据库或者向数据库插入新的信息的一个请求。

实际上，我们可以使用任何一个方法，并且程序员经常使用一个GET方法来删除数据库信息，而使用POST方法仅仅从服务器获取信息。然而，这里有必需POST方法的一种情况。如果我们向服务器提交了很多表单数据，例如，可能包含上百个单词的一个博客帖子，那么使用POST。GET方法对于可以发送的数据的量有一个内置的限制（这个限制根据浏览器的不同而有所不同，但是通常都在数百个字符左右）。大多数时候，对于不仅仅只包含几个字段的表单，Web设计者都会使用POST。

查询字符串

你可能已经在前面见过了查询字符串：它们经常出现在一个URL的末尾，跟在一个?符号的后面。例如，www.chia-vet.com/products.php?prodID=18＆sessID=1234是一个查询字符串。这个查询字符串包含了两个名/值对：prodID=18和sessID=1234。这个字符串所做的事情基本上就是创建两个变量prodID和sessID，并且把两个值存储到其中。查询字符串是在一个URL中传递信息的一种常用方法。

我们也可以以这种形式使用Ajax来格式化发送给服务器的数据。例如，假设创建了一个页面允许访问者通过单击星级数来给一部电影评定星级。例如，单击5星，向服务器提交一个5星级的评定。在这个例子中，发送给服务器的数据可能类似于rating=5。假设处理这个评级的页面的名字叫做rateMovie.php，使用Ajax向服务器发送评级的代码如下所示：

$.get（'rateMovie.php'，'rating=5'）；

或者，如果使用POST方法：

$.post（'rateMovie.php'，'rating=5'）；

注意：jQuery的get（）和post（）函数并不要求我们必须定义数据或回调函数。我们只需要提供服务器端页面的URL。然而，我们也总是提供数据。例如，在代码$.get（'rankMovie.php'，'rating=5'）；中，只提供了数据和URL，而没有指定回调函数。在这个例子中，访问者只是提交一个评级，并且不需要服务器反馈或回调函数做任何事情。

如果需要给服务器发送多个名/值对，可以在每一对之间插入一个＆：

$.post（'rateMovie.php'，'rating=5＆user=Bob'）；

然而，我们需要小心地使用这种方法，因为当有些字符插入到一个查询字符串中的时候具有特殊的含义。例如，我们使用＆字符给一个字符串包含其他的名/值对，=符号给一个名字赋值。例如，如下的查询字符串是无效的：

'favFood=Mac＆Cheese'//incorrect

这里的＆符号本意是作为“Mac＆Cheese”的一部分，但是，当用作一个查询字符串的一部分的时候，＆符号被解释为另一个名/值对的开始。如果想要使用特殊的符号作为一个名/值对中的名字或值的部分，我们需要转义或者对其编码，以使它们不会错误地被认为是具有特殊含义的一个字符。例如，空格字符用%20表示，＆符号用%26表示，而=用%3D表示。因此，我们需要把“Mac＆Cheese”的例子写作：

'favFood=Mac%20%26%20Cheese'//properly escaped

JavaScript提供了一个方法来正确地转义字符串，即encodeURIComponent（）方法。我们给encodeURIComponent（）方法提供一个字符串，并且它返回正确转义的一个字符串。例如：

var queryString='favFood=’+encodeURIComponent（'Mac＆Cheese'）；

$.post（'foodChoice.php'，queryString）；

对象直接量

对于简短而简单的数据（那些不包含任何标点符号的数据），查询字符串方法工作得很好。但是，jQuery的get（）和post（）函数所支持的一种更为安全的方法是，使用对象直接量来存储名/值对。正如你在本书4.8.3节所了解到的，对象直接量是用来存储名/值对的一个JavaScript方法。对象直接量的基本结构如下：

{

name1：'value1’，

name2：'value2’

}

可以直接给get（）和post（）函数传递一个对象直接量。例如，前面给电影评星级的代码使用查询字符串方法：

$.post（'rankMovie.php'，'rating=5'）；

要使用对象直接量，可以这样重新编写代码：

$.post（'rankMovie.php'，{rating：5}）；

可以把一个对象直接量直接传递给get（）和post（）函数，或者首先将其存储在一个变量中并将该变量传递给get（）和post（）：

var data={rating：5}；

$.post（'rankMovie.php'，data）；

当然，可以在传递给get（）和post（）函数的对象中保存任意多个名/值对：

var data={

rating：5，

user：'Bob'

}

$.post（'rankMovie.php'，data）；

或者，直接把一个对象直接量传递给.post（）：

var data=$.post（'rankMovie.php'，

{

rating：5，

user：'Bob'

}

）；//end post

jQuery的serialize（）函数要为整个表单的所有名/值对创建一个查询字符串或对象直接量，可能是非常繁琐的一项工作。我们必须获取每一个表单元素的名和值，然后将它们都组合到一起来创建一个长长的查询字符串或一个较大的JavaScript对象直接量。幸运的是，jQuery提供了一个函数，使得我们可以很容易地把表单信息转换为get（）和post（）函数可以使用的数据。

我们可以为任何表单（或者甚至只是表单字段的一个选择）应用serialize（）函数，来创建一个查询字符串。要使用它，首先创建包含了表单的一个jQuery查询，然后给它添加serialize（）函数。例如，假设有一个ID为login的表单。如果想要为该表单创建一个查询字符串，可以像下面这么做：

var formData=$（'#login'）.serialize（）；

var formData部分只是创建了一个新的变量，$（'#login'）创建了包含表单的一个jQuery选择；最后，.serialize（）收集了字段名和每个字段中当前的值，并创建了一个单个的查询字符串。

要将这种方法和get（）或post（）函数结合起来使用，只要把序列化的结果传递给函数作为URL之后的第二个参数。例如，假设想要把登录表单的内容发送给名为login.php的表单。可以这样做：

var formData=$（'#login'）.serialize（）；

$.get（'login.php'，formData, loginResults）；

这段代码使用GET方法把访问者输入到表单中的内容发送给了login.php文件。这里的get（）的最后一个参数loginResults是一个回调函数，该函数接收从服务器返回的数据并且对它做一些事情。下面将学习如何创建回调函数。

11.3.5　处理来自服务器的数据

Ajax通常是一条双向大道，JavaScript程序向服务器发送一些数据，而服务器向JavaScript程序返回一些数据，然后可以使用这些数据来更新页面。在上一节中，我们看到了如何格式化数据并使用get（）和post（）函数将其发送给服务器。现在，我们来学习如何接收和处理服务器的响应。

当Web浏览器使用XMLHttpRequest对象向服务器发送完一个请求，它保持对来自服务器的响应的监听。当服务器响应的时候，一个回调函数会处理服务器响应。几个参数会传递给该函数供其使用。首先，也是最重要的，服务器返回的数据作为第一个参数传递。

我们可以格式化服务器以任意多种形式返回的数据。服务器端的脚本可以返回一个数值、一个单词、一段文本或者是一个完整的Web页面。在服务器发送很多信息的情况下（例如来自数据库的一组记录），服务器通常使用XML或JSON（参见11.3.6节的“高级用户提示：接收来自服务器的XML”部分了解关于XML的更多内容，参见11.4节了解关于JSON的介绍）。回调函数的第二个参数是表示响应状态的一个字符串。大多数时候，这个状态是“成功的”，意味着服务器已经成功地处理了请求并返回了数据。然而，有时候，一个请求不会成功，例如，请求的一个文件不存在，或者在服务器端程序中有一个错误。如果一个请求失败了，回调函数接收一个“错误”状态消息。

回调函数以某种方式处理信息，并且，大多数时候，会以某种方式更新Web页面，例如，使用来自服务器的结果替代一个提交的表单，或者直接在页面上显示一条“请求成功”的消息。使用4.7节介绍的html（）和text（）函数可以很容易地更新一个Web页面的内容。操作一个页面的DOM的其他方法在本书第4章中介绍。

要了解整个请求/响应的过程，看一下电影评级示例的基本过程（如图11-6所示）。访问者可以通过单击5个链接中的一个来给电影评级。每个链接表示一个不同的级别。当访问者单击一个链接，评级以及被评级的电影的ID都发送给一个服务器端程序，这给数据库添加了一个分级，然后给电影返回一个平均的评级。然后平均的评级显示在Web页面上。

图　11-6　在这个页面上，访问者单击一个链接来对电影评级（如上图所示）。通过添加Ajax，我们可以向服务器提交评级而不用离开页面。实际上，使用服务器的响应，我们可以更新页面的内容（如下图所示）

为了让这个页面在没有JavaScript的情况下工作，页面上的每个链接都指向可以处理访问者评级的一个动态服务器端页面。例如，5星的评级链接（如图11-6所示）可能是rate.php?rate=5＆movie=123。处理评级的服务器端文件叫做rate.php，而查询字符串（?rate=5＆movie=123）包含了提供给服务器的两段信息：一个评级（rate=5）和一个表示被评级电影的编号（movie=123）。可以使用JavaScript来“劫持”对这些链接的单击并将它们转换为Ajax来调用服务器：

1$（'#message a'）.click（function（）{

2 var href=$（this）.attr（'href'）；

3 var querystring=href.slice（href.indexOf（'?'）+1）；

4$.get（'rate.php'，querystring, processResponse）；

5 return false；//stop the link

6}）；

第1行选择包含在另一个ID为message的标签中的每个链接（＜a＞标签）（在这个例子中，用来对电影评级的每个链接都包含在ID为message的一个＜div＞中）。然后，对每一个链接的click事件应用一个函数。

第2行提取了链接的HREF属性，例如，href变量可能保存了rate.php?rate=5＆movie=123这样的一个URL。第3行使用slice（）方法（参见14.1.4节的介绍）来提取字符串的一部分，从而得到URL中的?后面的部分，而indexOf（）方法（参见14.1.4节）用来确定?的位置（indexOf（）方法使用这一信息来确定从哪里开始分割字符串）。

第4行是Ajax请求。这个请求使用GET方法发送给服务器文件rate.php，请求中包含了用于链接的查询字符串（如图11-7所示）。然后，结果将返回给回调函数processResponse。第5行只是阻止常规的链接行为，防止Web浏览器退出当前的页面并载入链接到的页面。

图　11-7　在这个图中，我们可以看到JavaScript和W e b服务器是如何交互的。g e t（）函数向服务器发送信息，而回调函数processResponse（）处理服务器所返回的信息

注意：如果需要回顾函数如何工作以及如何创建它们，请参见3.4节。

最后，应该创建回调函数。回调函数接收数据和带有响应状态的一个字符串（如果服务器返回信息，该字符串就是'success'）。别忘了，回调函数的名字用于请求中（参见本节前面代码的第4行）。因此，在这个例子中，函数的名字是processResponse。处理服务器的响应的代码如下所示：

1 function processResponse（data）{

2 var newHTML；

3 newHTML=’＜h2＞Your vote is counted＜/h2＞’；

4 newHTML+=’＜p＞The average rating for this movie is'；

5 newHTML+=data+’.＜/p＞’；

6$（'#message'）.html（newHTML）；

7}

这个函数接收data参数，这是Web服务器返回的信息。这个data可能是纯文本、HTML、XML或JSON。第2行创建了一个新的变量，用来保存将要在页面中显示的HTML（例如，“Your vote is counted”）。在第3行和第4行，newHTML变量中将填充一些HTML，包括一个＜h2＞标签和一个＜p＞标签。服务器的响应在第5行之前不会派上用场，在第5行，来自服务器的响应（存储在data变量中）添加到了newHTML变量。在这个例子中，服务器返回了带有电影的平均星级的一个字符串，例如，'3 stars'。

提示：如果想要给站点添加一个星级评定系统，有一个不错的jQuery插件可以处理大部分的细节工作，可以在http：//www.wbotelhos.com/raty/找到。

最后，第6行使用jQuery的html（）函数（参见4.7节的介绍），使用新的HTML替换了ID为message的＜div＞的内容，从而修改了Web页面上的HTML。结果如图11-6的下图所示。

在这个例子中，回调函数定义于get（）函数之外，然而，如果想要把所有的Ajax代码保留在一起，可以使用一个匿名函数（参见4.10.1节）：

$.get（'file.php'，data, function（data, status）{

//callback function programming goes here

}）；

例如，你可以使用一个匿名函数来重写本节评定电影星级代码的第4行，如下所示：

$.get（'rate.php'，querystring, function（data）{

var newHTML；

newHTML=’＜h2＞Your vote is counted＜/h2＞’；

newHTML+=’＜p＞The average rating for this movie is'；

newHTML+=data+’.＜/p＞’；

$（'#message'）.html（newHTML）；

}）；//end get

11.3.6　处理错误

遗憾的是，事情不会总是像计划的那样发展。当使用Ajax和一个Web服务器会话的时候，事情可能会出错。Web服务器可能在某一个时刻宕机了，或者访问者的互联网连接临时断掉。如果发生这种情况，.get（）和.post（）函数将会失效，而不会让访问者知情。尽管这种类型的问题很少见，但最好还是做好准备，并且让访问者知道某些地方临时出现错误，以帮助他们搞清楚怎么做（例如，重新载入页面、再次尝试，或者稍后再来）。

要对一个错误给出响应，直接在.get（）或.post（）函数的末尾添加一个.error（）函数。基本的结构如下所示：

$.get（url, data, successFunction）.error（errorFunction）；

例如，可以将11.3.5节的第4行代码重新编写如下：

$.get（'rate.php'，querystring, processResponse）.error（errorResponse）；

然后，创建一个名为errorResponse的新函数，它通知访问者这里有一个问题。例如：

function errorResponse（）{

var errorMsg="Your vote could not be processed right now."；

errorMsg+="Please try again later."；

$（'#message'）.html（errorMsg）；

}

在这个例子中，只有当服务器或者服务器的连接中存在某种类型的错误的时候，函数errorResponse才会运行。

11.3.7　教程：使用get（）函数

在本教程中，我们将使用Ajax提交来自一个登录表单的信息。当访问者提供了正确的用户名和密码时，将出现一条消息告诉他已经成功登录，如果登录信息不正确，将在同一页面上出现一条错误消息，而不会载入一个新的Web页面。

注意：为了成功地完成本教程，我们需要有一个运行的Web服务器来测试页面。参见本章中的“快速熟悉：安装一个Web服务器”部分关于如何在计算机上安装一个测试服务器的介绍。

概览

我们将从图11-8所示的表单开始。它包含了提供登录到服务器的用户名和密码的字段。当表单提交的时候，服务器试图验证用户存在并且密码正确。如果提供的信息和有效的登录身份相符，那么，服务器将允许该访问者登录。

高级用户提示接收来自服务器的XML

XML是计算机之间交换数据的一种常用格式。和HTML一样，XML允许我们使用标签来区别信息。和HTML不同的是，我们可以自由地提出准确地反映数据的内容的标签。例如，一个简单的XML文件可能如下所示：

＜?xml version="1.0"?＞

＜message id="234"＞

＜from＞Bob＜/from＞

＜to＞Janette＜/to＞

＜subject＞Hi Janette＜/subject＞

＜content＞Janette, let's grab lunch

to-day.＜/content＞

＜/message＞

正如你所看到的，这里有一个名为＜message＞的主标签（叫做根元素），等同于H T M L的＜html＞标签，还有定义了每段数据的含义的其他几个标签。

在使用Ajax的时候，我们可以有一个返回XML文件的服务器程序。jQuery从一个XML文件读取和提取信息都没有问题。当使用get（）或post（）函数的时候，如果服务器返回一个XML文件，发送给回调函数的那个data参数将包含XML文件的DOM。换句话说，jQuery将会读取XML文件并且将其当做另一个文档对待。然后，我们可以使用jQuery的选择器工具来访问XML中的数据。

例如，假设一个名为x m l.p h p的服务器端文件返回上面列出的XM L，并且我们想要获取＜content＞标签内的文本。XML文件变成了返回数据，因此，回调函数可以处理它。我们可以使用jQuery find（）函数，并且使用经常和jQuery一起使用的任何常规选择器，来查找该X ML以找到一个特定的CSS元素。例如，可以找到一个元素、类、ID或者子孙选择器（参见4.6.2节），或者jQuery的过滤器（参见4.6.3节）。例如：

$.get（'xml.php'，'id=234’，processXML）；

function processXML（data）{

var messageContent=$（data）.

find（'content'）.text（）；

}

这里的关键是$（data）.find（'content'），它告诉jQuery选择data变量中的每个＜content＞标签。因此，在这个例子中，data变量包含了返回的XML文件，这段代码告诉jQuery查找XML中的＜content＞标签。

要了解有关X M L的更多内容，请访问w w w.w3schools.com/XML。如果想要获取关于如何从一个服务器生成XML的一些信息，可以查看www.w3schools.com/XML/xml_server.asp。如果想要阅读有关jQuery的find（）函数的内容，可以在http：//docs.jquery.com/Traversing/find#expr找到更多信息。

图　11-8　一个基本的登录页面总是简单的事情：只是几个字段和一个提交按钮。然而，当用户登录的时候，真的没有理由离开该页面。通过添加Ajax，我们可以提交访问者的身份，然后，通知他是否成功登录

我们将通过一个XMLHttpRequest来发送登录信息，从而给页面增加Ajax。服务器将会给回调函数发送一条消息，如果登录信息有效，它会删除表单并且显示一条“logged in”消息，或者如果登录无效，将显示一条错误消息。

编程

参见1.3节中的“注意”部分了解如何下载教程文件。最初的文件包含了HTML表单，准备好了供我们添加一些jQuery和Ajax编程。

1.在文本编辑器中打开chapter11文件夹下的文件login.html。

到jQuery库文件的链接以及$（document）.ready（）函数已经准备好了。首先选择该表单并为其添加一个submit事件。

2.单击$（document）.ready（）函数内的空行并输入：

$（'#login'）.submit（function（）{

}）；//end submit

这个＜form＞标签有一个ID为login，因此jQuery选择器$（'#login'）选择了该表单，而submit（）函数给submit事件添加了一个事件处理程序。换句话说，当访问者试图提交表单时，我们打算创建的函数将会运行。

下一步是从表单收集信息并将其格式化为提交给服务器的一个查询字符串。我们可以通过如下步骤做到这点：找到每个表单字段，提取访问者已经录入的信息，然后通过连接这些不同的信息片段来构建查询字符串。幸运的是，jQuery的serialize（）函数一次性负责所有这些细节。

3.按下回车键以创建一个空行，并输入：

var formData=$（this）.serialize（）；

这行代码首先创建了一个变量来存储表单数据，然后对表单应用了serialize（）函数。别忘了，$（this）引用当前的元素，因此，在这个例子中，它引用了登录表单，和$（'#login'）是相同的（参见4.9.2节了解$（this）是如何工作的）。serialize（）函数（参见11.3.5节的“jQuery的serialize（）函数”部分）接收一个表单，提取字段名和值，并将它们组合为合适的格式以提交给服务器。

现在使用post（）函数来设置XMLHttpRequest。

4.按下回车键并创建另一个空行，并输入：

$.get（'login.php'，formData, processData）；

这行代码给post（）函数传递了3个参数。第一个参数'login.php'是一个字符串，表明了数据应该发送到哪里，在这个例子中，是发送给服务器上一个名为login.php的文件。第二个参数是包含了发送给服务器的数据的查询字符串，即登录信息。第三个参数processData引用了将要处理服务器响应的回调函数。我们现在来创建该函数。

5.在上一行的后面添加另外一个空行并输入：

1 function processData（data）{

2

3}//end processData

这些代码行构成了回调函数的外壳，其中还没有程序。注意，这个函数设置来接收将会是来自服务器的响应的一个参数（data）。服务器端页面通过编程来返回一个单个的单词，如果登录成功的话是pass，如果登录失败的话是fail。

换句话说，根据来自服务器的响应，脚本要么显示出消息让访问者知道他成功地登录了，或者他没有成功登录，这是使用条件语句的理想的地方。

注意：本教程中使用的服务器端页面不是一个完备的登录脚本。如果提供了正确的身份，它确实能够响应，但是，它不能真正地用来对一个站点进行密码保护。有很多种方式可以对一个站点进行有效的密码保护，但是大多需要设置一个数据库或者需要针对Web服务器设置各种配置选项，这些步骤都超出了这个基本的教程的讨论范围。要使用一个真正的、基于PHP的而且使用了jQuery的登录脚本，请访问http：//www.html-form-guide.com/php-form/php-login-form.html。位于http：//www.youtube.com/watch?v=4oSCuEtxRK8的YouTube视频也说明了如何编写必需的PHP。

6.在processData（）函数中（换句话说，在步骤5的第2行中）输入：

1 if（data=='pass'）{

2$（'#content'）.html（'＜p＞You have successfully logged on！＜/p＞'）；

3}

这里的第1行检查从服务器返回的信息是否是字符串'pass'。如果是，登录就成功了，并且显示一条成功消息（见第2行）。表单位于ID为content的＜div＞标签中，因此$（'#content'）.html（'＜p＞You have successfully logged on！＜/p＞'）将用一个新的文本段替换＜div＞中的内容。换句话说，这个表单消失，并且登录成功的消息将会在该位置显示。

要完成任务，我们将添加一个else子句，如果访问者没有提交正确的登录信息，将告知他。

7.给processData（）函数添加一个else子句，使其看上去如下所示（添加的代码部分用粗体显示）：

1 function processData（data）{

2 if（data=='pass'）{

3$（'#content'）.html（'＜p＞You have successfully logged on！＜/p＞'）；

4}else{

5$（'#formwrapper'）.prepend（'＜p id="fail"＞Incorrect?

6 login information.Please try again＜/p＞'）；

7}

8}//end processData

第5行显示了登录失败的消息。注意，这里使用了prepend（）函数。正如本书4.7节所介绍的，prepend（）允许在一个元素的开头添加内容。它不会删除已经有的内容，而只是添加更多的内容。在这个例子中，我们想要让表单留在该处，以便访问者可以再次尝试登录。

8.保存文件并在Web浏览器中预览。

要让这个教程工作，我们必须通过Web浏览器使用一个URL来预览这个页面，例如http：//localhost/chapter11/login.html。参见本章中的“快速熟悉：安装一个Web服务器”部分了解如何安装Web服务器的更多信息。

9.尝试登录站点。

你可能会想：“但是稍等，你还没有给我用户名和密码！”这正是问题所在，当没有正确地登录的时候，看看会发生什么。再次尝试登录，将会看到“Incorrect login information”消息再次出现（如图11-9所示）。因为prepend（）函数并没有删除第一条错误消息，它只是再次添加了该消息。这看上去根本不对。

有几种方法来处理这个问题。例如，在表单下插入一个空的div（＜div id=“failMessage”＞）。然后，当登录失效的时候，直接替换其HTML。然而，在这个例子中，页面上没有空的div标签。相反，使用一个基本的条件语句来检查是否已经存在错误消息，如果是，那么，没必要再次添加它。

图　11-9：jQuery的prepend（）函数给已经存在的一个元素添加HTML。它不会删除任何内容，因此如果不小心，可能最终会重复添加同样的消息

10.添加另外一条语句（如下的第5行和第8行）：

1 function processData（data）{

2 if（data=='pass'）{

3$（'#content'）.html（'＜p＞You have successfully logged on！＜/p＞'）；

4}else{

5 if（$（'#fail'）.length==0）{

6$（'#formwrapper'）.prepend（'＜p id="fail"＞Incorrect?

7 login information.Please try again＜/p＞'）；

8}

9}

10}//end processData

注意，错误消息段落有一个ID为fail，因此，我们可以使用jQuery来检查页面上是否存在该ID。如果它不存在，那么该程序会在页面上显示错误消息。检查一个元素是否已经存在于页面上的一种方法是尝试使用jQuery来选择它。然后可以检查结果的length属性。如果jQuery没有找到任何匹配的元素，length属性为0。换句话说，$（'#fail'）试图找到ID为fail的一个元素。如果jQuery不能找到它，换句话说，错误消息还没有显示到页面，那么length属性为0，条件语句将为true，并且程序显示错误消息。一旦错误消息出现在页面上了，条件语句结果总是false，并且错误消息不会再次出现。

最后，需要告诉Web浏览器不应该自己提交表单数据，我们已经使用Ajax提交了。11.在submit事件的末尾（如下的第15行）添加return false；。完成后的脚本如下所示：

1$（document）.ready（function（）{

2$（'#login'）.submit（function（）{

3 var formData=$（this）.serialize（）；

4$.post（'login.php'，formData, processData）；

5 function processData（data）{

6 if（data=='pass'）{

7$（'#formwrapper'）.html（'＜p＞You have successfully logged on！＜/p＞'）；

8}else{

9 if（！$（'#fail'）.length）{

10$（'#formwrapper'）.prepend（'＜p id="fail"＞Incorrect↵

11 login information.Please try again＜/p＞'）；

12}

13}

14}//end processData

15 return false；

16}）；//end submit

17}）；//end ready

12.保存文件并再次预览该页面

尝试再次登录：用户名为007而密码为secret。本教程的一个完整版本complete_login.html位于chapter11文件夹下。

注意：如11.3.3节所介绍，尽管jQuery的post（）和get（）函数幕后的工作是完全相同的，但是jQuery还是必须执行两套不同的步骤来让Ajax请求正确地工作。可以通过把脚本中的post改为get（参见步骤11中的第4行）来自己检查这一点。本教程的服务器端脚本编写为可以接受GET或POST请求。

11.4　JSON

发送来自服务器的数据的另一种常用格式叫做JSON（JavaScript Object Notation）。JSON是使用JavaScript编写的一种数据格式，并且它类似于XML（参见本章中的“高级用户提示：接收来自服务器的XML”部分），因为它是交换数据的一种方法。

注意：然而，对于Ajax应用程序，JSON有两个主要的好处：第一，数据存储在JavaScript中，因此JavaScript程序很容易操作它；第二，我们可以请求来自其他域的JSON，因此，例如，我们可以请求来自Flickr（www.flickr.com）的图像并且在自己站点的页面上显示它（正如11.3.1节中的“注意”所介绍的，通常我们只能对自己的域进行一个Ajax请求）。

我们将在本节学习了如何创建JSON。实际上，JSON只是一个JavaScript对象直接量，或者是名/值对的一个集合。下面是一个简单的JSON：

{

firstName：'Frank'，

lastName：'Smith'，

phone：’503-555-1212’

}

{表示JSON对象的开始，而}表示其结束。在这之间是成组的名/值对，例如，firstName：'Frank'。每个名/值对用一个逗号隔开，但是，在最后一对的末尾不能放置逗号（否则，Internet Explorer将会产生一个错误）。

注意：或者，我们可以把名/值对中的名字也放入到引号中，如下所示：

{

'firstName'：'Frank'，

'lastName'：'Smith'，

'phone'：’503-555-1212’

}

如果名字中有一个空格或者其他的非文字数字的字符，我们必须使用引号。

可以把名/值对看做一个变量，名就是变量的名字，而值就是存储在变量中的值。在上面的例子中，lastName就像是一个变量，而字符串'Smith'存储在其中。

当Web服务器响应一个Ajax请求，它可能返回格式化为JSON的一个字符串。服务器不会真正地发送JavaScript，它只是发送格式化为一个JSON对象的文本。在字符串转换为一个真正的JSON对象之前，它不是一个真正的、有用的JavaScript。幸运的是，jQuery提供了一个特殊的函数getJSON（），它负责所有的细节。getJSON（）函数的样子和作用与get（）和post（）函数很相似。其基本的结构如下所示：

$.getJSON（url, data, callback）；

传递给该函数的3个参数和get（）与post（）函数的参数相同，分别是服务器端页面的URL、发送给服务器端页面的数据，以及回调函数的名字。不同之处在于，getJSON（）将处理来自服务器的响应（这只是一个字符串）并（通过一些JavaScript魔术）将其转换为一个可用的JSON对象。

注意：PHP 5.2有一个内置的函数，使得我们很容易从一个传统的PHP数组来创建一个JSON对象。因此，对于Ajax应用程序，可以接收一个PHP数组并且快速将其转换为一个JSON对象，以返回给Web浏览器中等待的JavaScript代码。请访问www.php.net/manual/en/function.json-encode.php了解更多信息。

换句话说，getJSON（）的作用和post（）或get（）相似，只不过传递给回调函数的数据是一个JSON对象。要使用getJSON（）函数，我们只需要了解如何使用回调函数处理一个JSON对象。举一个基本的例子，假设想要使用Ajax来请求名为contacts.php的服务器端文件中的一个联络人的相关信息，该文件返回了JSON格式的联络人数据（就像前面的JSON示例）。基本的请求可能如下所示：

$.getJSON（'contacts.php'，'contact=123’，processContacts）；

这行代码向contacts.php发送一个查询字符串contact=123。假设contacts.php使用该信息在一个数据库中找到一个单个的联络人，并且获取联络人的信息。结果送回到Web浏览器并且传递给回调函数processContacts。回调函数的基本结构如下所示：

function processContacts（data）{

}

processContacts（）函数有一个参数：data，其中包含了来自服务器的JSON对象。让我们看看回调函数如何访问JSON对象中的信息。

11.4.1　访问JSON数据

有两种方式访问一个JSON对象中的数据：点语法或数组表示法。点语法（参见2.10节）是表示对象的属性的一种方法，具体地说，在对象名以及想要访问的属性之间添加一个点号。我们已经看到这种方法用于不同的JavaScript对象的属性，例如字符串和数组。例如，'abc'.length访问该字符串的length属性，并且，在这个例子中，返回了字符串'abc'中的字符的数目，即3。

例如，假设创建了一个变量并且将一个对象直接量存储到其中，如下所示：

var bday={

person：'Raoul'，

date：’10/27/1980’

}；

在这个例子中，变量bday包含了该对象直接量，因此，如果想要获取对象中的person的值，可以像下面这样使用点语法：

bday.person//'Raoul'

要获取出生日期：

bday.date//’10/27/1980’

这同样适用于Web服务器返回的一个JSON对象。例如，以下面的getJSON（）和回调函数为例：

$.getJSON（'contacts.php'，'contact=123’，processContacts）；

function processContacts（data）{

}

假设服务器返回了11.4节所示的JSON，该JSON对象赋给了变量data（回调函数processContacts（）的参数），就好像执行了如下的代码：

var data={

firstName：'Frank'，

lastName：'Smith'，

phone：’503-555-1212’

}；

现在，在回调函数中，我们可以像下面这样访问firstName的值：

data.firstName//'Frank'

并且像下面这样获取联系人的lastName：

data.lastName//'Smith'

因此，假设这个小小的Ajax程序的整个任务是获取联络人信息并将其显示在ID为info的一个＜div＞中。完成此任务的所有程序可能如下所示：

$.get JSON（'contacts.php'，'contact=123’，processContacts）；

function processContacts（data）{

var infoHTML=’＜p＞Contact：’+data.frstName；

infoHTML+=’’+data.lastName+’＜br＞’；

infoHTML+='Phone：’+data.phone+’＜/p＞’；

$（'#info'）.html（infoHTML）；

}

将要添加到页面的最终输出如下所示：

Contact：Frank Smith

Phone：503-555-1212

11.4.2　复杂的JSON对象

我们可以使用对象直接量作为一个JSON对象中的值，换句话说，对象直接量嵌套在对象直接量中，从而创建甚至更为复杂的信息集合（对不起，但是不要就此放下本书）。

这里有一个例子。假设我们希望服务器使用JSON送回多个人的联络信息。我们将向名为contacts.php的文件发送一个请求，带上一个查询字符串，指明希望返回多少个联络人的信息。代码可能如下所示：

$.getJSON（'contacts.php'，'limit=2’，processContacts）；

limit=2是发送给服务器的信息，它表示应该返回多少个联络人的信息。Web服务器随后将返回两个联络人的信息。假设第一个人的联系信息和上面的例子相同（Frank Smith），第二个人的联络信息是如下所示的另一个JSON对象：

{

firstName：'Peggy'，

lastName：'Jones'，

phone：’415-555-5235’

}

Web服务器可能返回表示一个单个JSON对象的字符串，它组合了这两个对象，如下所示：

{

contact1：{

firstName：'Frank'，

lastName：'Smith'，

phone：’503-555-1212’

}，

contact2：{

firstName：'Peggy'，

lastName：'Jones'，

phone：’415-555-5235’

}

}

假设回调函数接收了一个名为data的单个参数（例如，函数processContacts-（data））。变量data将会随后分配给该JSON对象，就好像执行了如下的代码：

var data={

contact1：{

firstName：'Frank'，

lastName：'Smith'，

phone：’503-555-1212’

}，

contact2：{

firstName：'Peggy'，

lastName：'Jones'，

phone：’415-555-5235’

}

}；

现在，我们可以在回调函数中访问第一个联络人对象的信息，如下所示：

data.contact1

并且像下面这样访问第一个联络人的名字：

data.contact1.firstName

但是，在这个例子中，由于我们想要处理多个联络人，因此jQuery提供了一个函数允许遍历JSON对象中的每个项目，这就是each（）函数。其基本的结构是：

$.each（JSON, function（name, value）{

}）；

我们给each（）函数传递了JSON对象以及一个匿名函数（参见2.10.1节）。这个匿名函数接收JSON对象中的每个项目的名字和值。下面是在当前的例子中使用JSON对象的样子：

1$.getJSON（'contacts.php'，'limit=2’，processContacts）；

2 function processContacts（data）{

3//create variable with empty string

4 var infoHTML=’’；

5

6//loop through each object in the JSON data

7$.each（data, function（contact, contactInfo）{

8 infoHTML+=’＜p＞Contact：’+contactInfo.frstName；

9 infoHTML+=’’+contactInfo.lastName+’＜br＞’；

10 infoHTML+='Phone：’+contactInfo.phone+’＜/p＞’；

11}）；//end of each（）

12

13//add finished HTML to page

14$（'#info'）.html（infoHTML）；

15}

代码分解分析如下：

1.第1行创建了Ajax请求，并且指定了回调函数。

2.第2行创建了回调函数，它接收服务器送回的JSON对象并将其存储到变量data中。

3.第4行创建了一个空字符串。最终添加到页面的HTML将填充该字符串。

4.第7行是each（）函数，它将会遍历JSON数据中的对象。

each（）函数接收JSON对象作为其第一个参数（data），接收一个匿名函数作为其第二个参数。过程如图11-10所示。基本上，对于主对象中的每个对象（在这个例子中是contact1和contact2），该匿名函数都接收作为字符串的该对象的名字（即第7行中列出的contact参数）以及该对象的值（即contactInfo参数）。在这个例子中，contactInfo变量将保存包含了联络信息的对象直接量。

图　11-10　可以使用jQuery的each（）函数来遍历一个JSON对象，以便在每一个嵌套的对象上执行任务。也可以使用each（）函数来遍历数组。要了解关于这个有用的函数的更多信息，请访问http：//docs.jquery.com/Utilities/jQuery.each#objectcallback

5.第8～10行从一个联络人中提取信息。

还记得吗？each（）函数是一个循环，因此，第8～10行对于每个联络人运行两次。

6.第14行通过把HTML添加到页面来更新Web页面。

最终的结果是如下的HTML：

＜p＞Contact：Frank Smith＜br＞

Phone：503-555-1212＜/p＞

＜p＞Contact：Peggy Jones＜br＞

Phone：415-555-5235＜/p＞

第12章　Flickr和Google Maps

在第11章中，我们学习了Ajax的基础知识，即它是什么，它是如何工作的，以及jQuery如何简化了Ajax编程的过程。由于Ajax完全是Web浏览器和Web服务器之间的双向通信，如果真的想要驾驭Ajax的力量，理解服务器端编程是必需的。然而，要成功地使用Ajax，我们不需要成为服务器端编程的大师。实际上，你可以通过Flickr、Twitter和Google Maps等流行Web站点来获取图像、tweet和地图，并且将它们直接放置到你的Web页面中，从而利用这些服务所提供的优点。

12.1　JSONP简介

在第11章中，我们了解到，基于安全性方面的原因，Ajax请求仅限于同一域。也就是说，发送Ajax请求的页面必须与响应该请求的页面位于同一服务器上。这是Web浏览器强加的策略，为了防止一个站点带有恶意地试图联系另一个站点（例如，银行）。然而，有一种方法可以绕过这种限制。尽管Web浏览器不能向不同的站点发送一个XMLHTTP请求，但它可以从其他站点下载资源，包括图片、样式表和外部JavaScript文件。

JSONP（JSON with padding）提供了一种方式来从另一个站点获取信息。实际上，你可以载入了外部站点中包含JSON代码的一段脚本，而不是发出对外部站点的一个Ajax请求。换句话说，它就像是到Google上的一个外部JavaScript文件的链接。然而，你不能从其他站点获取任何想要的信息。要使用JSONP，外部站点必须设置为响应JSONP。大多数站点没有设置为以这种方式发送信息，但是，像GoogleMaps、Twitter、Flickr、Facebook、Netflix和YouTube这样的大站点提供了一个API，允许你使用jQuery的$.getJSON（）函数来请求诸如地图、照片、影评等数据（如图12-1所示）。

图　12-1：由于Ajax仅限于从同一域请求信息，但是一种叫做JSONP的替代方法允许我们通过从另一个站点请求一个JavaScript文件，从而获取JSON数据。这一技术允许我们从Twitter访问tweet，从Google访问地图，从Flickr获取照片，并且将它们直接嵌入到自己的Web页面中

12.2　给站点添加一个Flickr Feed

Flickr是一个流行的照片分享站点。它已经运营多年，拥有上百万的照片。很多Web站点喜欢包含从站点所有者或Flickr群组中获取的照片（群组是多人围绕着一个共同主题，例如Web设计、风光摄影等，提交照片的一个集合）。

Flickr允许我们以几种方式来获取照片和相关的信息。最为强大但也最为复杂的方法，是使用Flickr的API来搜索照片。这种方法要求我们在Flickr注册，并且获取一个特殊的API键（这是数字和字母的一个字符串，用来识别你的身份）。还需要一些奇妙的编程才能工作。最简单的方式是使用Flickr Feed服务。Feed是让人们了解站点信息的最新更新的一种方式。例如，你可以使用RSS feed来查看站点，从而能够看到来自该站点的最新的新闻和信息。Flickr针对其照片提供了一种类似的服务，你可以获得关于一个特定的个人或群组的20幅最新照片的列表。

在本节中，我们将使用feed方法从Flickr获取照片的一个集合，并且在Web页面上显示它，并且，在此过程中，我们将学习如何使用jQuery的$.getJSON（）函数从另一个Web站点获取JSONP数据。

注意：如果你想要对选取和显示Flickr照片进行更多的控制，有大量的jQuery插件可以帮助你做到这点。jQuery Flickr Photo Gallery（http：//johnpatrickgiven.com/jquery/flickr-gallery/）就非常好。

12.2.1　构建URL

Flickr提供了几个不同的URL以访问不同类型的照片的feed（参见www.flickr.com/services/feeds/获取其完整列表）。例如，我们使用http：//api.flickr.com/services/feeds/photos_public.gne访问来自特定Flickr账户的照片（例如，你所拥有的账户，如果你有账户的话），并且使用http：//api.flickr.com/services/feeds/groups_pool.gne来访问来自特定的群组的照片（例如，Web Design群组，它提供照片和图像以激发更好的Web设计）。

一旦你知道自己喜欢哪种类型的照片feed，以及与其相对应的基本URL，你需要添加一些额外的信息以获取自己感兴趣的照片。为了做到这点，我们可以添加一个查询字符串，其中有带标记的几个信息片段（正如我们在11.3.4节所了解到的，一个查询字符串是一个URL的末尾带有一个?，随后跟着一个或多个名/值对，例如，http：//api.flickr.com/services/feeds/groups_pool.gne?id=25053835@N03＆＆format=json）。

·添加一个或更多ID。要从一个特定群组或一个或多个单个账户选取照片，我们添加id，加上一个=号，后面跟着一个个人账户号或群组账户号。例如，要访问来自Web Design群组的一组照片feed，可以使用这个群组的照片feed，并且添加该群组的ID，如下所示：

http://api. fickr.com/services/feeds/←

groups_pool. gne?id=37996591093@N01

对于个人的Flickr feed，我们使用公共照片feed并且使用一个或多个ID。例如，要访问来自Smithsonian（它拥有自己的Flickr账户）和国会图书馆的照片，我们可以使用id名称和公共照片feed，如下所示：

http://api. fickr.com/services/feeds/←

photos_public. gne?ids=8623220@N02，25053835@N03

要使用多个ID，可以用逗号将每个ID隔开。注意，只可以使用多个ID来访问个人账户，不能使用群组feed并访问多个群组的照片。

提示：如果你知道某人的Flickr用户名，你可以使用这个站点来获取其Flickr ID：http：//idgettr.com/。

·添加JSON格式。Flickr照片feed服务非常灵活，并且，可以以多种不同的格式返回照片信息，从RSS到Atom、CSV和JSON。要让Flickr知道你想要的是JSON格式，我们需要给查询字符串添加＆format=json。例如，要以JSON格式获取Smithsonian Museum的Flickr照片的feed，我们使用这个URL：

http://api. flickr.com/services/feeds/←

photos_public. gne?ids=25053835@N03＆format=json

继续进行并将上面的URL输入到一个Web浏览器中（如果你不想输入，可以直接从教程文件的chapter12文件夹下的flickr_json.txt文件进行复制和粘贴）。你将会看到很多的数据，实际上，是一个对象直接量，它包含了很多的信息。这就是我们使用$.getJSON（）函数（参见本书11.4节）的时候从Flickr所获取的信息。你需要使用JavaScript来解析该对象，然后，使用它来构建图像的一个整齐的小集合（Flickr的JSON feed的结构将在12.2.3节介绍，记住这个feed，以便可以在12.3节的教程中使用它）。

·添加对URL的一个JSONP回调。最后，为了让你的站点上的页面完全成功地从另一个Web站点请求JSON数据，你需要给这个URL添加最后一点内容：＆jsoncallback=?。记住，基于安全性方面的原因，我们不能向一个不同的域发送一个XMLHTTP请求。要解决这个问题，＆jsoncallback=?片段通知Flickr，我们想要接收JSONP数据，并且允许jQuery的$.getJSON（）函数将这个请求当做Web浏览器直接请求一个外部JavaScript文件来对待。换句话说，要获取Smithsonian Museum的最新的Flickr照片的一个feed，我们需要将如下的一个URL传递给$.getJSON（）函数：

http://api. fickr.com/services/feeds/←photos_public.gne?ids=25053835@

N03＆format=json＆jsoncallback=?

公共照片feed的其他几个选项

对于Flickr的公共照片feed，你可以添加一些其他的选项来修饰feed所返回的信息。例如，假设你和几个朋友想要拍摄花栗鼠的照片并将其发布到Flickr上，并且，你想要获取你和自己的朋友所拍摄的最新的20张花栗鼠照片。你可以通过指定一个或多个标签来过滤feed，从而做到这一点。

在Flickr上，你可以使用一个或多个单词来给任何照片添加标签。标签是一个单词或短语，描述了照片的一个要素。例如，你可以将一张特别漂亮的日落的照片标记为“sunset”。任何照片都可以有多个标签，因此，你可能会使用单词“sunset, orange, beach”来标记那张日落的照片。

注意：只能在Flickr的公有照片feed中（www.flickr.com/services/feeds/docs/photos_public/）搜索标签。例如，你不能用来自群组（例如，Flickr的Web Design群组）的一个特定标签或多个标签搜索照片。

Flickr的feed服务提供了一些选项，允许你根据特定的标签来搜索一个feed：

·Tags。给URL添加带有一个或多个逗号和隔开的关键字的tag关键字，以指定一个标签，例如＆tags=fireworks, night。假设你的Flickr ID是8623220@N02，并且你的朋友是25053835@N03。你可以通过诸如下面的一个长长的URL来获取这两个feed，并且只搜索带有“chipmunk”标签的照片：

http://api. flickr.com/services/feeds/8

photos_public. gne?ids=25053835@N03，8623220@N02←

＆tags=chipmunk＆format=json＆jsoncallback=?

·tagmode。通常，当你搜索一组标签的时候，Flickr只是获取匹配所有标签的照片。例如，假设你给一个feed添加了?tags=chipmunks, baseball, winter。这段代码只会查找花栗鼠在冬天玩棒球的照片。如果你想要花栗鼠的照片，或者棒球的照片，或者冬天的照片（换句话说，至少有一个标签），给URL添加＆tagmode=any。例如：

http://api. flickr.com/services/feeds/8

photos_public. gne?ids=25053835@N03，8623220@N02←

＆tags=chipmunk＆tagmode=any＆format=json＆jsoncallback=?

12.2.2　使用$.getJSON（）函数

使用$.getJSON（）函数从Flickr获取一个照片feed的方式，与从你自己的站点获取JSON数据一样。函数的基本结构是相同的。例如，下面是获取Smithsonian的Flickr feed的设置：

1 var flickrURL="http：//api.fickr.com/services/feeds/←

photos_public.gne?ids=25053835@N03＆format=json＆jsoncallback=?"

2$.getJSON（flickrURL, function（data）{

3//do something with the JSON data returned

4}）；//end get

在这个例子中，第1行创建了一个FlickrURL并且存储了URL（使用上一小节中讨论的规则）。第2行向该URL发送了Ajax请求，并且设置了一个匿名函数来处理该数据。在发送一个Ajax请求后，这段代码获取从服务器返回的数据，在这个例子中，这些数据发送给匿名函数并且存储在名为data的变量中。我们将学习如何处理几个页面中的数据，但是首先需要理解Flickr的JSON数据看上去是什么样的。

12.2.3　理解Flickr JSON Feed

正如11.4节所介绍的，JSON只不过是一个JavaScript对象直接量。它可能像下面这样简单：

{

firstName：'Bob'，

lastName：'Smith'

}

在这段代码中，firstName充当一个值为Bob的键，这是一个简单的字符串值。然而，这个值也可以是另一个对象（参见图11-10），因此，你可能经常得到一个复杂的嵌套式结构。这就是Flickr JSON Feed的样子。下面是这些feed之一的一小段。它展示了用来获取两张照片的信息：

1{

2"title"："Uploads from Smithsonian Institution"，

3"link"："http：//www.flickr.com/photos/smithsonian/"，

4"description"：""，

5"modified"："2011-08-11T13：16：37Z"，

6"generator"："http：//www.flickr.com/"，

7"items"：[

8{

9"title"："East Island, June 12，1966."，

10"link"："http：//www.flickr.com/photos/smithsonian/5988083516/"，

11"media"：{"m"："http：//farm7.static.flickr.com/6029/5988083516_bfc9f41286_m.jpg"}，

12"date_taken"："2011-07-29T11：45：50-08：00"，

13"description"："Short description here"，

14"published"："2011-08-11T13：16：37Z"，

15"author"："nobody@flickr.com（Smithsonian Institution）"，

16"author_id"："25053835@N03"，

17"tags"："ocean birds redfootedbooby"

18}，

19{

20"title"："Phoenix Island, April 15，1966."，

21"link"："http：//www.flickr.com/photos/smithsonian/5988083472/"，

22"media"：{"m"："http：//farm7.static.flickr.com/6015/5988083472_c646ef2778_m.jpg"}，

23"date_taken"："2011-07-29T11：45：48-08：00"，

24"description"："Another short description"，

25"published"："2011-08-11T13：16：37Z"，

26"author"："nobody@flickr.com（Smithsonian Institution）"，

27"author_id"："25053835@N03"，

28"tags"：""

29}

30}；

31}

Flickr的JSON对象通常拥有一些关于feed的信息：这是初始的内容，包括“title”、“link”等。“title”元素（第2行）是该feed的名称。在这个例子中，就是“Uploads from Smithsonian Institution”，而“link”元素（第3行）指向了Smithsonian institution的Flickr主页面。例如，我们可以使用这些信息，作为显示照片之前的标题栏来展示。

要访问这些信息，我们使用本书2.10节所介绍的点语法。例如，假设使用前面小节中的代码：匿名函数用来处理存储在变量名data中的JSON反馈数据（参见12.2.2节的第2行）。然后，要访问feed的标题，可以像下面这样访问“data”对象的“title”属性：

data. title

这个feed的最重要的部分是“items”属性（第7行），它包含了额外的对象，每个对象都包含了关于一张照片的信息。例如，第8～18行提供了一张照片的信息，而第19～29行是关于另一张照片的信息。在每个item对象中，我们都可以看到其他的属性，例如照片的标题（第9行），到该照片的Flickr页面的一个链接（第10行），照片的拍摄日期（第12行），一段说明（例如，第13行的“Short description here”（Smithsonian的管理员肯定是那天有点偷懒而没有填它））等。

对于每张照片来说，另一个重要的元素是“media”，这是另一个对象。例如：

{

"m"："http：//farm7.static.flickr.com/6029/5988083516_bfc9f41286_m.jpg"

}

M表示“medium”，其值是到照片的一个URL。Flickr照片常常有不同的大小，例如，medium、thumbnail和small（这是一个很小的方形图像）。如果你想要在页面上显示一幅图像，那么，这个URL就是你需要的内容。你可以使用它在一个页面中插入一个＜img＞标签，并且指向Flickr服务器上的照片。在后面的教程中，我们将会看到如何做到这点。

12.3　教程：给站点添加Flickr图像

在本教程中，我们将所有内容组合到一起，从Smithsonian Institution获取照片feed，显示照片的缩略图像，并且添加到每张图像的链接，以便访问者可以单击缩略图进入Flickr，并看到照片的页面。

注意：参见本书1.3节的“注意”部分以了解如何下载教程文件的信息。

1.在文本编辑器中打开chapter12文件夹下的fickr.html文件。

首先，创建几个变量来存储需要发送给Flickr的URL的各个组成部分。

2.在$（document）.ready（）中的空行上单击，并且输入：

var URL="http：//api.flickr.com/services/feeds/photos_public.gne"；

var ID="25053835@N03"；

var jsonFormat="＆format=json＆jsoncallback=?"；

这里的每个变量都只是12.2.1节介绍的较长的URL的一部分。将每个片段分解为单独的变量，这使得很容易调整这段代码。例如，如果你想要从另一个Flickr用户那里获取一个照片feed，只要更改ID变量（如果你有一个Flickr账户，尝试将你的ID号插入到这里。如果你不知道自己的Flickr ID，访问http：//idgettr.com/）。

提示：如果想要获取来自一个Flickr群组的照片，例如Web Design Group，那么，在步骤2中将URL更改为http：//api.flickr.com/services/feeds/groups_pool.gne?id=37996591093@N01，将ID更改为该Flickr群组的ID。

接下来，组合这些变量以构建一个完整的URL。

3.在刚才输入的代码之后添加另外一行代码：

var ajaxURL=URL+"?id="+ID+jsonFormat；

这组合了所有的变量以及查询字符串的开始部分，即?id=，构成了一个完整的URL，就像在12.2.1节所看到的URL那样，http：//api.flickr.com/services/feeds/photos_public.gne?id=25053835@N03＆format=json＆jsoncallback=?。现在，应该进入Ajax部分，并且使用jQuery$.getJSON（）函数了。

4.在刚才添加的一行之后，输入：

$.getJSON（ajaxURL, function（data）{

}）；//end get JSON；

这是$.getJSON（）函数的基本shell：它将联系我们在步骤2和步骤3中构建的URL，并且接收从Flickr返回的数据。这些数据传递给一个匿名函数，并且存储在一个名为data的变量中。随后，可以使用这些数据并且开始在页面上使用它。首先，我们将获取该feed的标题，并且使用它替换当前页面上的＜h1＞标签。

5.将如下粗体所示的代码行添加到步骤4的代码中：

$.getJSON（ajaxURL, function（data）{

$（'h1'）.text（data.title）；

}）；//end get JSON；

这是一个基本的jQuery选择器（$（'h1'））和函数（.text（）），它们选择了当前页面上的＜h1＞标签，并且替换了其中的文本。实际的JSON feed存储在data变量中。要访问其组件，你可以使用点语法（参见本书2.10节），因此，data.title获取该feed的标题。如果现在保存页面并在浏览器中预览，将会看到一个粗体的标题显示“Uploads from Smithsonian Institution”。

要添加照片，需要循环遍历Flickr feed所返回的items对象（参见本书12.3节）。6.将如下粗体所示代码行添加到步骤5的代码中：

$.getJSON（ajaxURL, function（data）{

$（'h1'）.text（data.title）；

$.each（data.items, function（i, photo）{

}）；//end each

}）；//end get JSON；

4.10　节已介绍过.each（）函数，该函数用来遍历一个jQuery选择。$.each（）有所不同，但功能类似。它是一个通用循环工具，可以用来遍历一个数组（参见本书2.8节）或者一系列对象。我们传递给$.each（）函数一个数组或者一个对象直接量，以及一个匿名函数。$.each（）函数随后遍历该数组或对象直接量，并且对每一项运行一次该匿名函数。该匿名函数接收两个参数（在这一步的代码中，是i和photo），它们是包含了项的索引和项本身的变量。索引是用来遍历具体项的数字，它和数组的索引一样工作（参见本书2.8.2节）。例如，循环中的第一行的索引为0。第二个参数（在这个例子中是photo）是真正的照片对象，它包含了照片的名称、说明、URL等，正如本书12.3节所介绍的那样。

在Flickr feed的例子中，data.items表示JSON feed中（参见12.3节）的照片对象，因此$.each（）函数将每张照片的这个对象传递给photo变量中的匿名函数。换句话说，这段代码遍历了feed中的每一张照片，然后做些事情。在这个例子中，我们将直接创建一系列的缩略图图像，它们连接到每张照片的Flickr页面。目标是创建一些基本的HTML来显示每张图像并包含一个链接。例如：

＜span class="image"＞

＜a href="http：//www.flickr.com/photos/smithsonian/5988083516/"＞

＜img src="http：//farm7.static.flickr.com/6029/5988083516_bfc9f41286_s.jpg"＞

＜/a＞

＜/span＞

只有两段信息是构建这段HTML所必需的，即照片的Flickr页面的URL以及到照片文件的路径。我们将构建一个长长的字符串，它看上去就像上面的HTML，但是需要具体根据每张照片来替换URL和照片的路径。

7.在$.each（）函数中添加如下粗体所示的代码：

$.each（data.items, function（i, photo）{

var photoHTML=’＜span class="image"＞’；

photoHTML+=’＜a href="’+photo.link+’"＞’；

photoHTML+=’＜img src="’+photo.media.m+’"＞＜/a＞’；

}）；//end each

这段代码首先创建一个变量photoHTML，它存储了一个开始＜span＞标签，后续的每一行代码都给这个变量添加了更多的内容（要回忆+=的含义及其是如何工作的，参见2.5.5节）。这里的关键元素是photo.link和photo.media.m。如果你查看12.2.3节的JSON代码，可以看到每张照片都有各种不同的属性，例如title（照片的名称）和description（照片的简要说明）。Link属性指向Flickr.com上的照片页面，而media对象有一个名为“m”的属性，它包含了到图像文件的中等大小版本的路径。这段代码一起构建了步骤6中所示的HTML。现在，你只需要向页面添加代码。

8.添加如下粗体所示的代码：

$.each（data.items, function（i, photo）{

var photoHTML=’＜span class="image"＞’；

photoHTML+=’＜a href="’+photo.link+’"＞’；

photoHTML+=’＜img src="’+photo.media.m+’"＞＜/a＞’；

$（'#photos'）.append（photoHTML）；

}）；//end each

$（'#p hotos'）部分选择页面上已经存在的一个＜div＞标签，而append（）函数（参见4.7节的介绍）在div的末尾添加了HTML。换句话说，每次经过循环的时候，都有另一段HTML添加到该div。

9.保存该页面并且在Web浏览器中预览。

你应该看到页面载入了20幅照片（如果你没有看到任何内容，仔细检查你的代码，并使用Web浏览器的错误控制台（参见1.6节）来查找任何语法错误）。问题是，照片都具有不同的大小，并且不会在页面上形成一个漂亮的网格。这是因为Flickr feed只是提供了到中等大小的Flickr图像的路径。

Flickr还拥有其所有的照片的一个漂亮的方阵。将这些同样大小的照片一起放在一个页面上，漂亮、有序地展示出来。幸运的是，请求这些缩略图图像很容易。Flickr为其图像使用一个一致的命名惯例：到中等大小的图像的路径类似http：//farm7.static.flickr.com/6029/5988083516_bfc9f41286_m.jpg，而同样图像的缩略图路径是http：//farm7.static.flickr.com/6029/5988083516_bfc9f41286_s.jpg。唯一的区别在于文件名的末尾：_m表示是一个中等大小的图像，_s表示是一个小方块的缩略图（75像素×75像素），_t是一个小图像，长度或高度最大为100像素，_o表示最初的图像（上传到Flickr上的初始文件的大小），_b是较大的图像（高和宽最大为1024像素）。只要调整文件名（例如，用_s替换_m），你可以显示一个不同大小的文件。幸运的是，JavaScript提供了一种方便的方法来快速交换字符串中的字符。

10.在这段代码中，将photo.media.m修改为photo.media.m.replace（'_m'，’_s'）。页面的最终代码应该如下所示：

$（document）.ready（function（）{

var URL="http：//api.flickr.com/services/feeds/photos_public.gne"；

var ID="25053835@N03"；

var jsonFormat="＆format=json＆jsoncallback=?"；

var ajaxURL=URL+"?id="+ID+jsonFormat；

$.getJSON（ajaxURL, function（data）{

$（'h1'）.text（data.title）；

$.each（data.items, function（i, photo）{

var photoHTML=’＜span class="image"＞’；

photoHTML+=’＜a href="’+photo.link+’"＞’；

photoHTML+=’＜img src="’+photo.media.m.replace（'_m'，’_s'）+’"＞＜/

a＞’；

$（'#photos'）.append（photoHTML）；

}）；//end each

}）；//end get JSON

}）；//end ready

JavaScript的replace（）方法（在14.2.6节介绍）操作字符串，并且接收两个参数：要找到的字符串（在这个例子中是’_m'）以及用来替换它的字符串（'_s'）。

11.保存页面并且在Web浏览器中预览。

现在，你应该看到一个来源不超过20张的整齐排列的、方形缩略图的照片（如图12-1所示）。单击一个缩略图来查看Flickr站点上较大一点的图像。本教程的一个有效的版本complete_flickr.html可以在chapter12文件夹下找到。

注意：这个Flickr feed最多只提供20张图像。可以通过任何一个feed获取20张以上的照片。如果你只想要显示来自feed的10张图像，该怎么办呢？查看chapter12文件夹下的complete_flickr_limit_photos.html文件以了解解决方案。

12.4　向站点添加Google地图

Google Maps（http：//maps. google.com）是JavaScript复苏的最初典范。放大和缩小地图，沿着城市街道移动，以及在闪动的驱车指示等功能使得Google Maps成为了令人难以置信的有用站点。得益于对Ajax的聪明的使用，这个站点的响应性使得人们感觉它几乎就像是一个桌面程序。

但是Google Maps为Web设计者提供了更为强大的功能：Google Maps服务允许我们在自己的站点中嵌入一个地图。如果运行一个具体的业务（例如为商务构建站点），能够提供一个易于理解的地图和方向指示，这可以使你顾客盈门。幸运的是，使用jQuery的jMaps插件，可以很容易且直接地给Web页面添加交互式地图（如图12-2所示）。

高级用户提示进一步使用jQuery和Ajax

还有很多其他的jQuery插件可以使得Ajax开发进行得更快。在某些情况下，我们需要提供服务器端编程，插件只负责JavaScript部分。还有一些其他的程序也提供基本的服务器端编程。下面是一些较好的插件：

·Form插件。jQuery Form插件是给表单提交添加Ajax的一种简单方法。该插件远远超出了第11章讨论的基本功能，并且包含了文件上传的功能。它还带有表单验证。要了解详细信息，请访问http：//plug-ins.jquery.com/project/form。

·Autocomplete。这个jQuery UI项目提供了一个自动填充插件（http：//jqueryui.com/demos/auto-complete/），它允许你添加在Google和Amazon的搜索字段中经常看到的一种漂亮的功能。开始在文本字段输入一个单词，并且会显示一个下拉式菜单来提示可能的匹配。这会省去访问者很多录入，还会提供一个有用的提示列表。通过和Web服务器和Ajax一起工作，我们可以为搜索表单提供类似读懂访问者心理的功能。

·Ajax文件上传。如果你想要给站点添加文件上传，可以通过使用Ajax来发送该文件，确保这一过程对用户来说是无缝的。Uploadify插件（www.uploadify.com/）使得这一过程变得容易。

·Taconite。使用Ajax，我们可以从服务器接收信息并更新Web页面。然而，我们可能想要更新一个页面的多个区域，例如，如果访问者使用一个Ajax表单登录，我们可能想要在页面的一个部分显示其登录状态，在页面的另一个部分显示他上次在本站点访问过的页面的列表，并且在页面的另一个区域显示一个购物车。Taconite插件允许从Web服务器接收一个基本的XML文件，其中带有关于在Web页面的什么区域更新以及使用什么信息的简单说明。我们可以在http：//plug-ins.jquery.com/project/taconite找到关于这个插件的更多说明。

·Twitter。如果想要通过一种容易的方式将一个Twitter流添加到Web页面，查看Tweet！插件（http：//tweet.seaofclouds.com/）。Tweet！允许你添加自己的Twitter流，搜索其他Twitter用户的tweet，甚至只是按照关键字来搜索Twitter。

Jevgenijs Shtrauss开发的The GoMap插件（www.pittss.lv/jquery/gomap/）允许我们为任何Web页面添加Google地图，查询地图上两点之间的驱车指示，添加标记来突出显示地图上的一个位置等。使用该插件的基本步骤是：

1.添加来自Google Maps的一个外部JavaScript文件。

为了访问地图服务，我们需要从Google载入一个脚本。我们像下面这样使用＜script＞标签：

＜script src="http：//maps.google.com/maps/api/js?sensor=false"＞＜/script＞

这段代码载入了Google访问其地图服务所依赖的JavaScript。gmap3插件将这些代码构建到这个文件中，以提供一种更为友好的方式将地图添加到你的页面中。

图　12-2　尽管一张简单的地图图片是标识你（或者你的客户）的公司的位置的好方法，但如果可以使用像http：//maps.google.com这样的一个交互式地图会更好。访问者可以很容易地放大、缩小并移动一个Google地图。查找从一个地方到另一个地方的驱车指示，简直是小菜一碟。有了jMaps jQuery插件，可以很容易地把Google地图添加到你自己的Web站点

注意：GoMap使用Google Maps API的版本3。较早的Google Maps API要求你在Google注册，并且获取一个唯一的key，以便在你的站点上使用Google Maps。幸运的是，你不再需要在Google注册才能将地图添加到你的站点了。

2.添加两个jQuery文件。

当然，我们需要jQuery库，以及GoMap文件。GoMap文件可以从www.pittss.lv/jquery/gomap获得。这个文件提供了使得给站点添加地图变得如此容易的所有程序。因此，除了步骤2中的＜script＞标签，我们还需要添加如下所示的代码：

＜script src="js/jquery-1.6.3.min.js"＞＜/script＞

＜script src="js/jquery.gomap-1.3.2.min.js"＞＜/script＞

注意：我们已经将GoMap插件文件的一个副本包含在了教程文件中的_js文件夹下（参见1.3节的“注意”部分以了解下载教程文件的信息）。

3.给页面添加一个带有ID的空白＜div＞标签。

GoMap将在这个空白标签中添加地图，因此，把＜div＞放置到希望地图在页面中出现的位置。此外，我们需要提供一种方式来识别这个标签，因此，添加一个ID。这个标签的HTML如下所示：

＜div id="map"＞＜/div＞

此外，可以给页面的样式表添加一条CSS规则，用来定义页面上的地图的高度和宽度。例如：

#map{

width：760px；

height：400px；

}

4.调用goMap（）函数。

最后，添加一个新的＜script＞标签、document.ready（）函数，并且调用goMap（）函数。要使用goMap（）函数，我们使用jQuery选择器$（'#map'）选择地图＜div＞，然后添加goMap（）：

＜script＞

$（document）.ready（function（）{

$（'#map'）.goMap（）；

}）；

＜/script＞

仅仅调用goMap（）函数，将会给你一张以Latvia的某处为中心的地图。（除非你来自Latvia）你更可能想要指向一个特定位置（例如，你的公司或你的客户的公司的位置）的地图。我们接下来将学习如何做到这点。

12.4.1　为地图设定位置

一个Google Map有一个中心点，由所表示的位置的经度和纬度的数值来定义。如果想要把地图的中心设置到一个地方，例如，你的公司的地址或者你的下一个生日宴会的地点，需要获取该位置的经度和纬度。这很简单：

1.访问http：//itouchmap.com/latlong.html，在Address栏中输入一个地址，然后单击Go按钮。

这个有用的Web站点提供了你指定地址的任何位置的经度和纬度。记下这些信息，我们就可以在下一步中使用它们了。

提示：你也可以直接从Google Maps获取经度和纬度信息。访问http：//maps.google.com并且搜索你想要在地图中使用的一个位置。然后，在浏览器的地址栏中输入javascript：vo id（prompt（'’，gApplication.getMap（）.getCenter（）））；。将会出现一个JavaScript警告框，其中带有当前地图中心位置的经度和纬度。

2.通过添加如下代码更新goMap（）函数：

1＜script＞

2$（document）.ready（function（）{

3$（'#map'）.goMap（{

4 latitude：45.53940，

5 longitude：-122.59025

6}）；//end goMap

7}）；//end ready

8＜/script＞

在这个例子中，我们传递了一个对象直接量，即第3行的开始{到第6行的结束}，它包含了该插件的选项。第一项latitude指定了地图中心的纬度，而第二项longitude是地图中心的经度。你应该使用想要的纬度和经度来替换第5行和第6行所列出的两个数字。

幸运的是，GoMap插件功能多样，能够直接使用一个地址来定位地图。你可以直接使用包含了想要的地址的一个字符串的address项，来替代latitude和longitude项。例如：

$（'#map'）.goMap（{

address：’2200 NE 71st Ave Portland, OR, USA'

}）；//end goMap

如果只需要一个大概的区域范围，例如，以一个特定城市为中心的地图，也可以做到这点：

$（'#map'）.goMap（{

address：'Portland, OR, USA'

}）；//end goMap

实际上，你可以使用在Google Maps搜索框（http：//maps.google.com）中的任何内容来作为address元素，即便是知名的地标的名称，例如，“Kennedy Space Center”或“Eiffel Tower”。然而，Google可能并不总是能够定位一个地址，并且，当你想要在一个很大的区域突出显示一个特定的位置的时候，地址并不总是很好，例如喜爱的一个公园中的一个位置。在这种情况下，你将需要使用上面所介绍的纬度和经度值。

注意：另一个值得一看的Google Maps插件是GMAP3（http：//gmap3.net/）。它提供了比GoMap插件更多的功能，但是，它用起来也更为复杂，并且文件很大。GMAP3的一项漂亮的功能是，在地图上生成驾驶方向。

12.4.2　其他GoMap选项

除了地址、纬度和经度，在创建一个新地图的时候，还可以设置很多其他的选项。你应该将每个选项整合到传递给goMap（）函数的对象直接量中。例如，要将一个地图中心设置在经度和纬度为45/-122的地方，并且设置地图以便可以放大来查看细节，可以像下面这样调用goMap函数：

$（'#map'）.goMap（

{

latitude：45，

longitude：-122，

zoom：15

}）；//end goMap

换句话说，我们在本节中所了解的选项只是作为一个对象直接量的一部分来传递。

以下是可以设置的一些选项。

·控制地图的比例。有时候，我们想让地图聚焦到最好的细节，就像一个街道级别的地图。还有些时候，我们可能想要有一个较广泛范围的地图，并且在屏幕上看到整个城市或整个州。我们可以通过为Zoom选项提供一个数字来控制如何缩放地图。设置为0表示完全缩小（也就是说，整个地球的一张地图），而0以上的每个数字表示更大的缩放。按照通常的规则，如果想要看到每条街道的名字，设置为15是不错的；如果想要有一个较好的鸟瞰，设置为13是不错的。上限值（最大的缩放）取决于Google对于该地区的一个地图有多详细，并且该值总是在17～23变化。像下面这样设置该选项：

zoom：15

·指定你喜欢的地图类型。通常，GoMap显示一种所谓的混合地图，即带有街道的卫星地图，上面加上了地标的名称。然而，你可能想要的地图是纯粹的平原路线图，或者只是一张卫星图像，或者是一张带有植被、高度变化的地势图等。将maptype选项设置为如下值之一：'HYBRID'、'ROADMAP'、'SATELLITE'或'TERRAIN'。例如：

maptype：'TERRAIN'

·添加一个比例尺标志。印刷版地图上经常列出本地图使用的比例尺，这是很常见的，例如，“1”等于1英里。Google Map也可以拥有一个比例尺标志（如图12-3所示）。要在地图的左下方添加一个小的可见的比例尺标志，可以把scaleControl属性设置为true：

scaleControl：true

如果你不想看到比例尺标记，不需要做任何事情，goMap通常是不显示比例尺的。

图　12-3　除了地图本身外，Google Map还提供了各种控件和信息标志，使得可以缩放地图、在窗口中移动地图、确定地图的比例尺，以及标注地图上的确定位置

·删除导航控件。如果想要显示地图但不带有放大/缩小或面板控件，可以将navigationControl项设置为false，如下所示：

navigationControl：false

这一行代码将这些控件隐藏了起来使访问者看不到，但是，他们仍然可以使用鼠标滚轮或者通过双击来缩放。如果真的想要阻止访问者导航地图，那么，将这个添加到传递给goMap（）函数的对象直接量中：

navigationControl：?false，

scrollwheel：?false，

disableDoubleClickZoom：?true，

然而，GoMap不允许你关闭拖拽功能，即便你阻止访问者缩放地图，但是他们仍然可以拖拽地图。

·地图类型控件。我们可以将Google Map地图显示为路线图、卫星地图，或者二者的混合地图，或者是带有高低细节的地势图。通常，地图包含了用来在不同类型的地图之间进行切换的控件。我们可以通过将mapTypeControl选项设置为false来隐藏该控件，如下所示：

mapTypeControl：false

如果你真的喜欢地图类型按钮，可以控制这些按钮的风格及其位置。我们给mapTypeControlOptions项一个对象直接量，它由position设置和一个style组成。在页面上放置一个地图类型控件的可能的值是TOP、TOP_LEFT、TOP_RIGHT、BOTTOM、BOTTOM_LEFT、BOTTOM_RIGHT、LEFT、RIGHT；该控件的style的值是DEFAULT、DROPDOWN_MENU和HORIZONTAL_BAR。例如，假设你想要使用下拉菜单样式并且将其放在地图的右下方。可以将这个传递给用于goMap（）函数的对象直接量：

mapTypeControlOptions：{

position：'BOTTOM_RIGHT'，

style：'DROPDOWN_MENU'

}

12.4.3　添加标志

要强调地图上的一个地点，可以添加一个红色的按钮标志，如图12-3所示。这些标志是表示我们的公司地点或者地图上的相关的一个地点的一种很好的方法。要提供关于标志的更多信息，还可以使用HTML添加一个弹出提示框（如图12-3所示）。GoMap使得添加这些细节变得很容易。

GoMap提供了几种方法来添加一个标志：第一种方法可以同时创建一个标志和一个地图。或者，可以向页面添加一个地图，然后向地图添加一个或多个标志。第二种方法允许你动态地控制一个标志，例如，当访问者单击页面上的一个按钮的时候，添加一个标志。第一种方法涉及调用goMap（）函数并将带有一个markers项的对象直接量传递给它。下面是一个例子：

$（'#map'）.goMap（{

markers：[

{

latitude：45.53940，

longitude：-122.59025，

title：'Marker 1’

}]

}）；//end goMap

markers属性在地图上创建了一个或多个标志。这是一个数组，它类似于一个杂货店列表，其中包含了一个或多个项（参见2.8节）。要创建一个名为arrayItems的、包含3个字符串的数组，可以这样编写：

var arrayItems=['item1’，'item2’，'item3’]；

markers项也是类似这样的一个数组，但是，它不包含字符串，而包含了对象直接量（参见4.8.3节）。换句话说，一个marker是一个对象直接量：

{

latitude：45.53940，

longitude：-122.59025，

title：'Marker 1’

}

这段代码表示一个标志，并且，实际上它有一个latitude、longitude和标志的title组成（title是一个文本，当访问者的鼠标光标移动到标志上的时候，它会显示出来）。

通过向数组添加额外的对象直接量，你可以向地图添加更多的标志。例如，下面是添加3个标志的代码：

$（'#map'）.goMap（{

markers：[

{

latitude：45.53940，

longitude：-122.59025，

title：'Marker 1’

}，{

latitude：45.53821，

longitude：-122.59796，

title：'Marker 2’

}，{

latitude：45.53936，

longitude：-122.58159，

title：'Marker 3’

}

]

}）；//end goMap

当你以这种方式创建一个地图的时候，仍然可以将上面所介绍的同样的选项传递给goMap（）函数。例如，要创建一张路线图，它带有一个标志、缩放到15级以及一个比例尺标志，你可以编写如下的代码（注意，这些选项位于markers数组之外）：

$（'#map'）.goMap（{

markers：[

{

latitude：45.53940，

longitude：-122.59025，

title：'Marker 1’

}]，

zoom：15，

maptype：'ROADMAP'，

scaleControl：true

}）；//end goMap

也可以使用一个地址来设置一个标志，而不是使用纬度和经度，如下所示：

$（'#map'）.goMap（{

markers：[

{

address：’2200 NE 71st Ave Portland, OR, USA'，

title：'Marker 1’

}

]，

zoom：15，

}）；//end goMap

我们也可以在创建了地图之后添加标志。如果你不想要标志位于地图的中心，这一技术很方便：例如，你可能想要一张公园的地图，并且使用标志标记出公园中感兴趣的位置。你想要让公园位于地图的中心，但是，不想让标志位于中心（当你像12.4.3节所述的那样同时创建标志和地图的时候，可能会发生这种事情）。你也可能想要动态地添加标志，可能是作为对用户单击之类的事情的响应。例如，以公园地图为例，你可能想要一个按钮显示“Show me all the slides in the park”。单击这个按钮，将会使得标记出现，突出显示公园内的所有滑梯。

按照这种方式，你首先添加一幅地图，然后，调用GoMap插件的一个特殊函数createMarker（）。例如，要创建一个地图然后添加一个标志，你可以像下面这样编写代码：

$（'#map'）.goMap（

{

latitude：45，

longitude：-122，

zoom：15

}）；//end goMap

$.goMap.createMarker（{

latitude：45.53940，

longitude：-122.59025，

title：'Marker 1’

}）；

注意，在这种方式中，GoMap插件没有添加给一个jQuery选择以添加标志。换句话说，它是$.goMap.createMarker（），而不是$（'#map'）.goMap.createMarker（）。像这样，我们每次只能添加一个标志，因此，需要添加多个标志的时候，需要多次调用这个函数。一种好的办法是，将你的所有标志定义为一个数组，然后使用jQuery的$.each（）函数遍历该数组，并且应用createMarker（）函数。示例如下：

var markers=[

{

latitude：45.53940，

longitude：-122.59025，

title：'Marker 1’

}，{

latitude：45.53821，

longitude：-122.59796，

title：'Marker 2’

}，{

latitude：45.53936，

longitude：-122.58159，

title：'Marker 3’

}]

$（'#map'）.goMap（

{

latitude：45，

longitude：-122，

zoom：15

}）；//end goMap

$.each（markers, function（i, marker）{

$.goMap.createMarker（marker）；

}）；//end each

此外，你可能想要删除标志（例如，有一个显示Hide Markers的按钮）。GoMap有一个clearMarkers（）函数。你可以像createMarker（）函数一样使用它，也就是说，将它附加到jQuery对象而不是一个选择，例如：

$.goMap.clearMarkers（）；

因此，假设页面上有一个ID为removeMarkers的按钮。可以给它附加一个click处理程序，它将会从页面删除标志：

$（'#removeMarkers'）.click（function（）{

$.goMap.clearMarkers（）；

}）；//end click

如果你只是想要删除一个特殊的标志，需要首先给标志添加一个ID，然后，调用GoMap的removeMarker函数。例如，下面的代码创建了一个新的地图（第1～13行），然后，给带有ID remove的一个页面元素添加一个click事件。

1$（'#map'）.goMap（{

2 markers：[

3{

4 latitude：45.53940，

5 longitude：-122.59025，

6 title：'Marker 1’，

7 id：'marker1’

8}

9]，

10 zoom：15，

11 maptype：'ROADMAP'，

12 scaleControl：true

13}）；//end goMap

14$（'#remove'）.click（function（）{

15$.goMap.removeMarker（'marker1'）；

16}）；

创建标志过程中重要的部分是第7行，即id：'marker1’，它给该标志分配了一个名称。一旦一个标志有了ID，我们就可以识别它，并使用removeMarker函数删除它（第15行）。GoMap还提供了一个showHideMarker（）函数，它将一个标志在可见和不可见之间切换。例如，你可以重新编写第14～16行的代码，以便每次单击该页面元素的时候，都会隐藏或显示特定的标志，例如：

$（'#remove'）.click（function（）{

$.goMap.showHideMarker（'marker1'）；

}）；

12.4.4　给标志添加信息窗口

你可以给每个标志添加一个弹出对话气泡（称为一个信息窗口）。为了做到这点，针对每个名为html的标志（该html标志包含另一个对象直接量，这个直接量又包含了属性的content和popup）的对象直接量，添加另一个项。例如：

$（'#map'）.goMap（{

markers：[

{

address：’2200 NE 71st Ave Portland, OR, USA'，

title：'Marker 1’，

html：{

content：’＜p＞A fun place to play＜/p＞’，

popup：true

}

}

]，

zoom：15，

}）；//end goMap

content属性设置了想要在弹出对话气泡中显示的文本，在其中可以使用常规HTML标记和文本。popup属性指定了当地图显示的时候对话气泡是否可见（像上面的例子中一样，将该属性设置为true），或者当访问者看到信息气泡之前是否需要单击该标志（例如，将popup设置为false，即popup：false）。

你可以添加任何想要的HTML，例如，表格、图像和项目符号列表。可以向这些信息气泡添加相当多的内容，它们将自动重新调整大小。

提示：要样式化HTML气泡中的HTML，你可以使用一个子孙选择器。例如，如果针对包含地图的＜div＞的ID使用名称map（参见12.4节的步骤3），可以创建一个子孙选择器#map p，来格式化气泡中的＜p＞标签的外观。

12.4.5　GoMap教程

在本教程中，我们将经历给Web页面添加GoMap插件的必需步骤。

注意：参见1.3节中的“注意”部分以获取如何下载教程文件的信息。

1.在文本编辑器中打开chapter12文件夹下的map.html。

在开始向页面添加任何JavaScript之前，我们将添加一个用来保存地图的空白＜div＞修改HTML。

2.在页面的正文中找到＜h1＞标签（＜h1＞Google Maps＜/h1＞）。在这个标签后面的空行中单击并添加：

＜div id="map"＞＜/div＞

我们只是创建了一个占位符，GoMap插件最终将向其中添加一个Google地图。接下来将添加一个CSS样式来设置地图的高度和宽度。

3.在该文件的顶部是一个内部样式表。在开始的＜style＞标签之后添加一条CSS规则，输入：

#map{

height：400px；

width：760px；

}

在这里，地图区域设置为500像素的正方形。现在该添加一些JavaScript了。4.单击结束的＜/head＞标签上方的空白行并输入：

＜script src="http：//maps.google.com/maps/api/js?sensor=false"＞＜/script＞

＜script src="../_js/jquery-1.6.3.min.js"＞＜/script＞

＜script src="../_js/jquery.gomap-1.3.2.min.js"＞＜/script＞

第一个＜script＞标签从Google.com载入了JavaScript文件，其中包含了和Google Maps服务通信所需的代码。第二个＜script＞标签载入了jQuery，而第三个＜script＞标签载入了GoMap插件文件。现在，我们准备好创建地图了。

5.在步骤4中添加的＜script＞标签的下面再添加另一个＜script＞标签，并包含jQuery的$（document）.ready（）函数：

＜script＞

$（document）.ready（function（）{

}）；//end ready

＜/script＞

现在，只需要对步骤2中创建的空的＜div＞应用goMap（）函数。6.在$（document）.ready（）函数内部添加如下所示的代码：

$（'#map'）.goMap（{

latitude：45.53940，

longitude：-122.59025

}）；//end goMap

这段代码在步骤2中添加的div内放置了一张地图，显示了一个特定的经度和纬度作为地图的中央点（如果你想要显示一个不同地方的地图，随意修改这个值）。

7.保存文件并在Web浏览器中预览。

假设你连接到了Internet（因而浏览器可以连接到Google.com），将会看到一个760×400像素宽的地图。

让我们来把地图放大点。

8.编辑脚本，在mapCenter选项的后面添加一个逗号并插入一个mapZoom设置（修改的地方用粗体显示）：

$（'#map'）.goMap（{

latitude：45.53940，

longitude：-122.59025，

zoom：16

}）；//end goMap

我们可以完全缩放（0），或者缩放到街道级别（17）。保存该页面并在Web浏览器中预览其变化。接下来，我们将添加一个比例尺标志，以便访问者可以通过地图感受到距离。

9.再次编辑脚本。这次，在mapZoom选项的后面添加一个逗号，并插入另一行代码：

$（'#map'）.goMap（{

latitude：45.53940，

longitude：-122.59025，

zoom：16，

scaleControl：true

}）；//end goMap

这行代码在地图的左下方添加了一个小小的比例尺标志。最后，我们将地图的类型（通常，GoMap显示为一张混合地图，即带有街道及其名称的卫星图）修改为仅仅是一张卫星照片。

10.最后一次编辑goMaps（）函数，添加最后一个选项：

$（'#map'）.goMap（{

latitude：45.53940，

longitude：-122.59025，

zoom：16，

scaleControl：true，

maptype：'SATELLITE'

}）；//end goMap

基本的地图现在设置好了。要强调在这个地图中已经选定的位置，我们要添加一个红色的按钮标志。

11.在前面添加的goMap（）函数的后面输入：

$.goMap.createMarker（{

latitude：45.53743，

longitude：-122.59473，

title：'hole1’

}）；//end createMarker

这就在地图中央点的位置设置了一个标志。当然，我们可以提供不同的经度和纬度值，以便在地图的其他地方添加一个标志。

最后，我们将为这个标志添加一个信息窗口。

12.编辑在最后一步中输入的代码，并添加一个信息窗口（修改的地方用粗体显示）：

$.goMap.createMarker（{

latitude：45.53743，

longitude：-122.59473，

title：'hole1’，

html：{

content：’＜h2＞Hole 1＜/h2＞＜p＞Par 4，346 yards＜/p＞’，

popup：true

}

}）；//end createMarker

这段代码添加了一个弹出窗口，其中带有一些HTML。保存这个页面，并且在Web浏览器中最后查看它一下。你可以在chapter12文件夹下看到教程文件的一个完整版本，即complete_map.html。

第五部分：提示、技巧和除错

第13章　让jQuery发挥最大的作用

j Query大大地简化了JavaScript编程，并且给Web设计者提供了一种工具，允许他们快速并容易地添加复杂的交互。但是，jQuery并不总是简单的，并且，你需要一定的知识才能够充分使用它。在本章中，我们将学习如何进一步使用jQuery：如何使用jQuery文档，如何通过插件来使用预先编写好的交互，以及使用jQuery的一些有用的提示和技巧。

13.1　有用的jQuery提示和信息

jQuery使得编程更为容易，但是，在这之上，我们还有一些方法让jQuery编程变得更容易。如下是使你能够更深入jQuery以便充分用好它的信息。

13.1.1　$（）等同于jQuery（）

在Web上关于jQuery的很多文章和博客帖子中，你都可能会遇到类似下面这样的代码：

jQuery（'p'）.css（'color'，’#F03'）；

尽管你熟悉$（'p'），它选择了页面上的所有＜p＞标签，你可能会对jQuery（）函数感到疑惑。实际上，它们是相同的。上面的代码也可以写成下面这样：

$（'p'）.css（'color'，’#F03'）；

$（）是jQuery（）的一个别名，并且二者是可以互换的。jQuery的创始人John Resig，意识到程序员可能会经常使用主jQuery函数，他认为将其缩写为$（）会是更好的选择，而不是迫使人们一次又一次地录入jQuery（）。

实际上，你可以使用jQuery（）或者$（）；这取决于你。然而，由于$（）录入起来更快，你可能想要坚持使用它（就像大多数程序员一样）。

注意：另一个名为Prototype的JavaScript库（www.prototypejs.org）也使用$（）。如果你恰好也在自己的站点上使用了Prototype，你可能想要使用jQuery（）方法。此外，jQuery提供了一个特殊的函数来处理这种情况，该函数名为.noConflict（）。你可以在http：//api.jquery.com/jQuery.noConflict/了解到关于这个函数的信息。

13.1.2　将选择保存到变量中

每次使用$（）对页面元素做出选择的时候，例如，使用$（'#tooltip'），我们正在调用jQuery函数。每次我们这么做的时候，访问者的浏览器已经运行了很多的代码。这往往会使程序变得没必要地慢。例如，假设我们想要像下面这样对一个选择应用几个jQuery函数：

$（'#tooltip'）.html（'＜p＞An aardvark＜/p＞'）；

$（'#tooltip'）.fadeIn（250）；

$（'#tooltip'）.animate（{left：100px}，250）；

这段代码选择了ID为tooltip的一个元素，并且在其中插入了一个＜p＞标签。然后，它再次选择了该元素，并将其淡入视线。最后，它第三次选择了该元素，然后将其left属性动画到100像素。每次这样的选择，即每个$（'#tooltip'），都会运行jQuery函数。由于这三行代码都影响同一个元素，我们真的只需要选择它一次。

一种方法（在本书前面4.6.4节介绍过）是使用jQuery的函数串联功能。选择元素，然后一个接一个地添加函数，如下所示：

$（'#tooltip'）.html（'＜p＞An aardvark＜/p＞'）.fadeIn（250）.animate（{left：

100px}，250）；

但是，有时候串联会变得很长，并且难以阅读。另一种选择时，只运行jQuery函数一次，将其结果保存在一个可以重用的变量中。通过这种方法，上面的代码可以写成：

1 var tooltip=$（'#tooltip'）

2 tooltip.html（'＜p＞An aardvark＜/p＞'）；

3 tooltip.fadeIn（250）；

4 tooltip.animate（{left：100px}，250）；

第1行运行jQuery函数，创建了ID为tooltip的一个元素的选择，并且将其存储在一个名为tooltip的变量中。一旦进行了这个选择并存储了，你就不需要再这么做了。可以直接使用该变量（它现在包含了一个jQuer y选择），并且在其上运行jQuery函数。

当使用这种方法的时候，很多程序员喜欢在变量名的前面添加一个$，这帮助提醒他们，该变量存储了一个jQuery选择，而不是像字符串、变量、数组或对象直接量这样的其他数据类型。例如：

var$tooltip=$（'#tooltip'）；

在使用事件的时候，将一个选择存储到一个变量中也是很常见的。正如参见2.10.2节所介绍过的，当在一个事件函数中时，变量$（this）引用了事件所应用的元素。然而，每次使用$（this）的时候，我们都调用jQuery函数，因此，在事件函数中重复使用$（this），也会浪费计算机资源。相反，我们可以在事件函数的开始处，将$（this）存储到一个变量中，并且重复地使用该变量而不需要继续调用jQuery函数，如下所示：

$（'a'）.click（function（）{

var$this=$（this）；//store a reference to the＜a＞tag

$this.css（'outline'，’2px solid red'）；

var href=$this.attr（'href'）；

window.open（href）；

return false；

}）；//end click

13.1.3　尽可能少几次添加内容

在第4章中，我们学习到一些jQuery函数，它允许我们向页面元素添加内容：例如，.text（）函数（参见4.7节）允许你替换一个元素中的文本，而.html（）函数（参见4.7节）允许你替换元素中的HTML。例如，如果你想要在ID为passwordError的一个span标签中插入一条错误消息，可能会编写下面的代码：

$（'#passwordError'）.text（'Passwords must by at least 7 characters long.'）；

另外两个函数允许我们在一个元素之后（a p p e n d（），参见4.7节）或之前（prepend（），参见4.7节）添加内容。

添加和修改内容允许我们添加错误消息、弹出的工具提示（参见10.4节）、插入提取引用（参见4.11节），但是，它确实要求浏览器做很多工作。每次添加内容的时候，浏览器都需要做很多工作，你基本上修改DOM（参见4.5节），并且当发生这种情况的时候，浏览器在幕后做很多工作。多次更改内容，可能会显著地影响到Web页面的性能。

添加的内容的量并没有关系，你更改页面的次数会影响到性能。例如，假设你想要创建一个工具提示效果：当某人将鼠标光标悬停在一个框上，带有某些额外内容的一个div会出现。为了做到这一点，你需要把这个框以及额外的内容添加到页面。如下是添加该div的一种方法：

1//add div to end of element

2$（'#elemForTooltip'）.append（'＜div id="tooltip"＞＜/div'）；

3//add headline to tooltip

4$（'#tooltip'）.append（'＜h2＞The tooltip title＜/h2＞'）；

5//add contents

6$（'#tooltip'）.append（'＜p＞The tooltip contents here＜/p＞'）；

上面这段代码将很好地工作：第2行向访问者将要鼠标光标悬停的元素添加了一个div；第4行给工具提示框添加了一个标题；第6行给工具提示添加了一个段落。然而，在这个过程中，通过3次不同的添加操作，DOM修改了3次。在整个过程中，实际上是对Web浏览器进行过多盘剥，并且，减少必须修改DOM的次数，将会显著地提升页面的性能。

在这个例子中，你可以通过构建整个工具提示HTML，将其存储到一个变量中，然后将该变量的内容附加到页面，从而将添加操作的次数减少到仅仅一次，如下所示：

1 var tooltip=’＜div id="tooltip"＞＜h2＞The tooltip title＜/h2＞↵

＜p＞The tooltip contents here＜/p＞＜/div＞；

2$（'#elemForTooltip'）.append（tooltip）；

注意：一行代码末尾的*符号表示下一行代码实际上应该作为第1行代码的一部分。但是，由于一行很长的JavaScript代码可能无法在本书的一页中放下，因此将其分为两行。

在这段代码中，第1行创建了一个变量，其中包含了用于工具提示的所有HTML，并且，第2行代码将该HTML添加给了一个页面元素。这里只有一次添加操作，并且，根据访问者浏览页面的浏览器的不同，这段代码可能比使用3个.append（）函数的情况要快上20倍。

底线是，如果你想要将一段HTML插入到页面上的一个位置，在单独一次操作中这么做，而不是使用多次插入分部分地添加HTML。

13.1.4　优化选择器

jQuery的灵活性意味着我们有很多种方式来达到同样的目标。例如，可以以多种方式选择一个页面元素；例如，可以使用任何CSS选择器，或者使用参见13.3节介绍的DOM Traversal函数来精细化一个jQuery选择。然而，如下的技术将使得你的选择速度更快，并使得JavaScript程序更加高效。

·尽可能地使用ID选择器。选择页面元素的最快的方式，是使用一个ID选择器。在JavaScript刚刚诞生的时候，浏览器就已经提供了一种方法来使用ID选择元素，并且，它仍然是最快的方法。如果你担心性能，那么，你可能想要在计划选取的每一个元素上添加一个ID，而不是依赖于子孙选择器这样的方法。

·先使用ID，作为一个子孙选择器的一部分。只使用ID选择器的问题在于，你只能获取单个的元素。如果需要获取多个元素，例如，一个div中的所有＜a＞标签，或者一个页面上的所有段落，该怎么办呢？如果你的页面是以这样一种方式构造的，即你想要选择的所有元素都位于一个带有ID的元素中，那么，先使用包含这个ID的一个子孙选择器。当所有那些标签都位于一个ID为main的div标签中的时候，就发生了这种情况。使用这种选择器会更快一些：

$（'#main a'）

而不是使用选择器：

$（'a'）

·使用.find（）函数。jQuery包含了一个函数，用来在一个选择中查找元素。它的作用类似于这样的一个子孙选择器，该选择器在其他标签内部查找标签。我们将在参见13.3节更详细地介绍这个函数，但是，总而言之，我们从一个jQuery选择开始，使用.find（），并且将一个选择器传递给它。换句话说，你可以像下面这样编写$（'#main a'）：

$（'#main'）.find（'a'）；

实际上，在某些情况下，使用.find（）而不是子孙选择器要快两倍。

注意：可以在http：//jsperf.com/sawmac-selector-test对.find（）函数进行一次速度测试。

·避免过多的具体化。你可能习惯于使用CSS的具体性规则来创建以页面上的特定元素为目标的CSS样式。像#main.sidebar.note ul.nav li a这样的一条规则就非常具体，但是，当你对其执行jQuery函数的时候，它可能执行得很慢。如果可能的话，使用一个较短且更为精炼的子孙选择器，例如，$（'.sidebar.nav a'）；或者使用前面一点中所提到的.find（）函数：$（'#main'）.find（'.sidebar'）.find（'.nav a'）。

注意：如果你想要了解提高jQuer y的性能的其他方法，查看http：//addyosmani.com/jqprovenperformance/以及http：//jqfundamentals.com/#chapter-9的PPT。要了解关于使用jQuery的众多很好的提示和技巧，访问http：//tutorialzine.com/2011/06/15-powerful-jquery-tips-and-tricks-for-developers/的博客。

13.2　使用jQuery文档

jQuery Web站点提供了针对jQuery的非常详细的文档（http：//docs.jquery.com/），如图13-1所示。你将会发现一些有用的链接，介绍如何开始使用jQuery、在哪里找到帮助、教程以及更多内容，但是，该站点最为重要的部分是介绍jQuery API。API表示Application Programming Interface（应用编程接口），它只是表示jQuery允许你使用的一组函数，例如，第5章介绍的事件处理函数（.click（）、.hover（）），第4章介绍的CSS函数（.css（）、.addClass（）和.removeClass（）），还有更为重要的，基本的jQuery函数自身$（），它允许我们选择页面上的元素。

图　13-1　jQuery文档主页面包含了到很多有用信息的链接，但是，帮助你搞清楚jQuery所提供的所有函数的链接，在页面主区域中的A P I标题下（大纲在右边）或者在左边的边栏（大纲在左边）列出。当你访问其他的页面的时候，将会发现左边的边栏可用

图13-1突出显示了API的主要分类。单击分类之一，可以跳转到一个页面，它列出了和该jQuery部分相关的函数。主要的分类包括：

·jQuery Core（http：//api. jquery.com/category/core/）。在这里，你将会看到关于一些函数的信息，这些函数中的大多数都是你不需要访问的高级函数。你还将看到主jQuery（）函数，它是jQuery的核心。访问其页面（http：//api.jquery.com/jQuery/），将会帮助你找到关于jQuery（）函数的更深入信息。

注意：我们已经了解了$（）函数以及如何使用它选择一个页面元素，例如，$（'p'）。对于名为jQuery（）的函数，也有相同的形式。即$（）是名为jQuery（）的函数的别名。这两者是可以互换的，因此你可能会在一个教程或者本书中看到类似下面的代码：

jQuery（'p'）.css（'color'，’#F03'）；

在功能上等价于：

$（'p'）.css（'color'，’#F03'）；

·Selectors（http：//api. jquery.com/category/selectors/）提供了对一些最有用的jQuery函数的访问。这个页面值得经常访问，因为它列出了使用jQuery选择页面元素的多种不同方式。我们将在本书第4章了解到更多内容，但是，当你访问jQuery文档的这个部分的时候，将会找到更多的方式。

·Attributes（http：//api. jquery.com/category/attributes/）。访问这个页面，将会看到获取和设置HTML元素的属性的各种jQuery函数，例如，给标签添加一个类、找到或设置一个属性的值（例如，一个＜a＞标签上的href属性），或者获取一个form元素的值。

注意：你常常会发现，相同的函数在jQuery文档站点的多个位置列出。例如，通常用来读取和设置一个表单字段的值的.val（）函数，在该站点的Attributes和Forms分类下都列出了。

·Traversing（http：//api. jquery.com/category/traversing/）指用来操作一组页面元素的函数。例如，.find（）函数允许你在一个jQuery选择中找到一个元素：当你想要选择一个页面元素时（例如，一个＜ul＞标签），在其上执行一个操作（例如，添加一个类或将其淡入到视线），然后在该页面元素中找到另一个元素来做些其他的事情（例如，在初始的＜ul＞标签中找到一个＜li＞标签），这个时候，这个函数很方便。jQuery提供了用来遍历HTML元素的很多函数，并且，你可以在本章稍后了解其中的一些。

·Manipulation（http：//api. jquery.com/category/manipulation/）。当你想要向页面添加内容，或者从页面删除内容的时候，你需要操作页面的DOM（Document Object Model，文档对象模型，参见4.5节）。这个页面列出了可以用来修改页面的众多函数，包括你在第4章（参见4.7节）了解到的那些，例如，用来给页面添加HTML的.html（），以及给页面添加文本的.text（）等。函数的这个分类非常重要，因为很多JavaScript编程是关于动态地更改Web页面的内容和外观的。

·CSS页面（http：//api.jquery.com/category/css/）列出了用来读取和设置元素上的CSS相关属性的jQuery函数，包括添加和删除类，直接设置CSS属性，并且控制或读取一个元素的高度、宽度和位置。我们已经在4.8.2节介绍了这些函数中的一些。

·Events（http：//api. jquery.com/category/events/）。在第5章中，我们学习了如何使用jQuery来响应用户交互，例如，鼠标光标悬停在一个链接上，或者访问者单击页面上的一个按钮。你可以在这个页面上找到众多事件相关的jQuery函数的一个列表。在13.5节中，我们将了解一些高级的事件函数。

·通过Effects页面（http：//api.jquery.com/category/effects/），可以访问与jQuery的效果相关的函数的信息，例如，我们在第6章中了解到的.slideDown（）、.fadeIn（）和.animate（）函数。

·Ajax分类（http：//api.jquery.com/category/ajax/）列出了相关的函数，这些函数根据向Web服务器发送信息或者从Web服务器获取的信息来动态更新页面。可以在本书第四部分学习Ajax。

·Utilities（http：//api. jquery.com/category/utilities/）。jQuery还提供了很多函数，专门用来简化常见的编程任务，例如，在一个数组中查找一个元素（参见2.8节），在数组或对象中的每一项上执行操作（4.10节介绍的$.each（）函数），以及一些其他的细节。在你目前的编程生涯中，你可能还不需要任何这些函数（它们真的不会给你带来任何漂亮的效果，或者帮助你更新页面上出现的内容），但是，随着你获得更多的进步，看看jQuery文档站点的这个页面是很值得的。

奇怪的是，当你访问分类页面之一的时候（例如，Ajax或Selectors），左边的边栏会针对主页面上没有列出的jQuery API添加更多的函数分类。

·Data分类（http：//api.jquery.com/category/data/）列出了与向页面元素添加数据相关的函数。jQuery提供了一个.data（）函数来向一个元素添加数据，可以将其看做向任何页面（类似一种微型数据库）元素添加名/值对的一种方式。当你开发一个需要存储和记录数据的Web应用程序的时候，这个函数和其他与数据相关的函数用起来很方便。要找到关于使用这些数据函数的一个便于理解的介绍，查看http：//tutorialzine.com/2010/11/jquery-data-method/。

·Deferred对象（http：//api.jquery.com/category/deferred-object/）。只需要看看这个分类的介绍（它说明了一个deferred对象是“一个串联的工具对象，可以将多个回调注册到回调队列中，调用回调队列，并且传递任何同步或异步函数的成功或失败状态”），就可以知道jQuery的Deferred对象是一头复杂的怪兽。基本上，它帮助排列函数以控制它们的运行顺序。如果你想要了解更多，访问jQuery文档站点的该分类页面。

·Dimensions（http：//api. jquery.com/category/dimensions/）引用确定对象的宽度和高度的函数。这些函数也在前面提到的CSS分类下列出。

·Forms分类（http：//api.jquery.com/category/forms/）列出了与drum-roll please表单相关的函数。它主要列出了和表单元素一起使用的事件，但是也包括用来读取和设置一个表单字段的值的.val（）函数，以及让使用Ajax提交表单（参见本书第四部分）更容易的几个函数。

·Internals（http：//api. jquery.com/category/internals/）是一组不同用途的函数。例如，.jquery属性，返回了页面上使用的jQuery的版本：

//open alert box with version number

alert（$（）.jquery）；//1.6.2，for example

我们可以过着快乐、悠长的人生，而不用使用这里的任何函数。

·Offset分类页面（http：//api.jquery.com/category/offset/）列出了与确定一个对象相对于屏幕或相对于其父元素的位置的函数。当设置或获取页面上的一个元素的位置的时候，我们将使用这些函数。

阅读jQuery Docs站点上的一个页面

每个jQuery函数都有其自己的页面，记录了它做什么以及如何工作。图13-2展示了说明jQuery的.css（）函数的页面的一部分。这个页面列出了函数名（在这个例子中是.css（）），并且在下面列出了分类及其子分类。你可以单击分类和子分类链接，以跳转到一个页面，该页面列出了属于该分类的所有函数。

在某些情况下，一个函数充当两种甚至三种角色，并且根据我们提供给它的参数的类型和数量的不同来执行不同操作。在这种情况下，我们会看到函数以几种不同的方式列在函数名下。例如，在图13-2中，我们可以看到.css（）函数以两种不同的方式使用（图中的编号1和编号2）。

第一种方式#1是css（propertyName）的形式。在这里，propertyName表示我们给该函数传递一个参数，它应该是一个CSS属性的名称。jQuery随后返回指定元素的该属性的值（注意图中部靠右边的Returns：String）。这个页面上列出的信息随后让我们知道要传递一个参数，并接收返回的一个字符串。例如，假设你想要确定ID为tooltip的一个＜div＞标签的宽度，可以使用如下代码：

var tipWidth=$（'#tooltip'）.css（'width'）；//get the width value

在上面的代码中，'width'传递给该函数，并且返回了一个字符串值。（然而，在这个例子中，因为它是一个宽度，那么字符串实际上是宽度的像素数，例如，该元素是’300’。）

图　13-2　jQuery函数的每个文档页面都列出了可以使用一个函数的不同方式。在这个例子中，在Contents下列出，你可以看到，可以把单个的参数（#1）或两个参数（#2）传递给.css（）函数。该函数根据我们所选的路径不同而执行不同的操作。通过单击蓝色的向下箭头（圆圈圈出的地方），我们可以直接跳转到页面上描述该函数用法的位置

使用.css（）函数的第二种方式（#2）是css（propertyName, value），表示你可以给该函数传递两个参数，即一个CSS属性的名称和一个值。使用这种方式，.css（）函数设置了该元素的CSS属性。例如，如果你想要设置ID为tooltip的一个＜div＞标签的宽度，应该将width作为第一个参数传递，并且将你想要的宽度值作为第二个参数，如下所示：

$（'#tooltip'）.css（'width'，300）；//set div to 300px wide

该文档还列出了使用.css（）函数设置CSS属性的两种其他方式：

·. css（propertyName, function（index, value））表示你可以使用一个函数动态地生成一个值，用来设置一个CSS属性。当你有一组页面元素，并且想要为每个元素设置略微不同的值的时候，这很方便；例如，有一系列的div，我们想要通过设置left属性来放置它们，但是，想要每个div都以略微不同的left位置彼此靠近放置。

·. cs s（map）在4.8.2节介绍过，它表示你可以传递一个对象直接量，目的是一步中设置几个CSS属性和值。

需要理解的重要的一点是，相同的jQuery函数常常可以接收不同的参数并做不同的事情。例如，.css（）函数可以接收和设置一个CSS值。我们常常发现jQuery函数以这种方式工作，既充当“getters”（也就是说，它们获取关于一个元素的信息），也充当“setters”（即它们设置元素的一个特定属性的值）。

文档页面将会描述每个函数的用法，并且通常会提供函数如何工作的示例。jQuery的文档维护得很好，并且就目前的技术文档情况来说，确实很易于阅读和理解。你应该花些时间访问这些页面，特别是阅读你经常使用的那些函数。

13.3　遍历DOM

我们已经了解了如何使用jQuery和基本的CSS语法来选择页面元素，例如$（'p'），它可以选择页面上所有的段落。一旦你选择了元素，可以对它们做很多事情，例如，添加或删除一个类、更改一个CSS属性，或者让该元素消失。但是，有时候，你可能想要找到和最初的选择相关的其他的页面元素。用JavaScript的语言来描述，这叫做遍历DOM。

当你想要给一个元素添加一个事件，但随后想要对另一个元素做一些事情的时候，经常需要遍历DOM。例如，假设你有一个＜div＞标签，其ID为gallery，这个div包含了一系列的缩略图像。当访问者用鼠标单击该div的时候，你想要缩略图做些事情：缩小、放大、晃动或者类似的事情。添加给该＜div＞标签的事件如下所示：

$（'#gallery'）.click（function（）{

}）；//end click

在该函数内部，你需要添加程序代码来实现图像动画。因此，当用户单击了div，你想要对图像做些事情。在上面的代码中，你只是选择了该＜div＞标签，并且在函数$（this）内部将引用该div（如果这对你来说很新颖的话，请翻到4.10.2节回顾一下什么是$（this））。因此，＜div＞标签是click函数中选择的元素，你需要找到该div中的图像。幸运的是，jQuery恰好提供了这样的解决方案：.find（）函数。它的作用是，通过在当前选择中搜索和给定选择器匹配的其他标签，来产生一个新的页面元素选择。因此，你可以通过添加如下粗体所示的代码，在该div内部选择图像：

$（'#gallery'）.click（function（）{

$（this）.fnd（'img'）；

}）；//end click

$（this）. find（'img'）创建了一个新的元素选择，$（this）引用该div，然后，.find（'img'）查找该div中的每一幅图像。当然，这段代码没有对新的选择做任何事情，但是，你可以添加在上一章中所学到过的任何效果。例如，假设你想要图像临时淡出视线，然后再次淡入到视线。可以使用如下代码：

$（'#gallery'）.click（function（）{

$（this）.fnd（'img'）.fadeTo（500，.3）.fadeTo（250，1）；

}）；//end click

正如在6.1.1节所介绍的，fadeTo（）函数接收一个duration值和一个opacity值作为参数。因此，这段代码首先在500毫秒内将所有图像淡出到30%的不透明度，然后，在250毫秒内将其淡入回到100%的不透明度（参见教程chapter13文件夹下的find.html文件，可以看看这一效果的作用）。

实际上，遍历DOM是如此常见的任务，以至于jQuery提供了众多的函数来帮助你选择元素，然后找到和初次选择的元素相关的其他元素。为了更好地理解这些函数是如何工作的，我们使用图13-3中的一小段HTML来表示：http：//api.jquery.com/category/manipulation/。它是一个ID为gallery的＜div＞标签，其中包含了4个链接，每个链接都包含一幅图像。

正如在4.1.5节所介绍的，你可以使用家庭的关系来描述页面上的标签之间的关系。例如，在图13-3中，＜div＞标签是＜a＞标签的父亲，并且，每个＜a＞标签是位于其中的＜img＞标签的父亲。相反，＜a＞标签是＜div＞标签的孩子，每个＜a＞标签是其他＜a＞标签的兄弟。每个＜img＞标签都是包围它们的＜a＞标签的孩子，并且，由于每个＜a＞中没有其他的标签，因此＜img＞标签没有兄弟。

图　13-3　当你用jQuery进行选择时，例如，给一个元素添加一个事件处理程序，如这里的图中描述的＜a＞标签，你常常想要对选择的元素相关的另一个元素执行操作（例如，给div标签添加一个边框，或者变换img标签）。jQuery的DOM遍历函数可以帮忙

下面是最常用的几个jQuery DOM遍历函数：

·. find（）。找到当前选择中的特定元素。你可以从一个jQuery选择开始，添加.find（）并将其传递给一个CSS选择器，代码如下所示：

$（'#gallery'）.find（'img'）

这段代码找到gallery div中的所有图像。当然，你可以使用诸如$（'#gallery img'）的一个子孙选择器，来实现同样的目标。正如上面所提到的，当你已经有一个选择，并且也已经对它做了一些事情的时候（例如，添加了一个click事件），在这种情况下，你更可能使用.find（）来创建一个新的选择以对其执行操作。

你使用.find（）来选择当前选择的一个子孙（即位于一个标签中的另一个标签）。因此，在图13-3所描述的例子中，我们可以在包含了div的一个选择上使用.find（），以选择＜a＞标签或＜img＞标签。

注意：查看13.1.4节了解.find（）所带来的巨大的性能好处。和使用一个子孙选择器相比，它通常是一种更快的选择元素的方式。

·children（）类似于.find（）。它也接收一个选择器作为参数，但是，它将自己的选择限制在当前选择的直接孩子上。例如，假设你有一个div标签，它包含了一系列的其他div。当你单击main div时，它显示出之前隐藏的div，并且在其周围添加一个红色的边框。假设你使用.find（）函数来实现这一目标，代码如下所示：

$（'#mainDiv'）.click（function（）{

$（this）.find（'div'）.show（）.css（'outline'，'red 2px solid'）；

}）；

如果m a i n d i v中的一个d i v其中也有一个d i v标签的话，问题就出现了。.find（'div'）找到所有的div标签，甚至包括div之中的其他div。你可能最终得到一个页面，其中所有的div都用边框圈了起来，而你想要的只是让main div直接包含的孩子div带上边框。为了解决这个难题，你可以使用.children（）重新编写上面的代码，如下所示：

$（'#mainDiv'）.click（function（）{

$（this）.children（'div'）.show（）.css（'outline'，'red 2px solid'）；

}）；

现在，这段代码只是找到main div的直接孩子的那些div，并且避免了孩子div之中可能存在的任何div。

·. parent（）。.find（）找到当前元素中的元素，.parent（）向上遍历DOM，找到当前标签的父亲标签。这可能会很方便，例如，要给图13-3中的＜a＞标签添加一个hover事件，但是，你想要对＜div＞标签执行操作（例如，给该div添加一个边框或背景颜色）。在这种情况下，你要使用.parent（）来找到div并对其应用操作，如下所示：

$（'#gallery a'）.hover（

function（）{

//add outline to link

$（this）.css（'outline'，’2px solid red'）；

//add background color to div

$（this）.parent（）.css（'backgroundColor'，'white'）；

}，

function（）{

//remove outline from link

$（this）.css（'outline'，’'）；

//remove background color from div

$（this）.parent（）.css（'backgroundColor'，’'）；

}

）；//end hover

在这个例子中，在一个链接上悬停鼠标光标，就会在该链接周围添加一个边框，然后，选择该链接的父亲元素（div）并且对其应用一种背景颜色。当鼠标光标离开该链接的时候，将会删除边框和背景颜色（参见5.4.2节，以了解关于.hover（）事件的更多信息）（参见教程chapter13文件夹下的parent.html文件，以了解使用这个函数的一个例子）。

·. closest（）找到和一个特定选择器匹配的最近的祖先元素。和.parent（）不同，.parent（）只是找到当前标签的直接父亲元素，而.closest（）接收一个选择器作为参数，并且找到匹配该选择器的最近的祖先元素。例如，在图13-3中，每个＜a＞标签中都有一幅图像，换句话说，＜a＞标签是图像的父亲元素。然而，如果你想要选择包含了＜a＞标签的＜div＞标签（HTML链级中更高一层的一个祖先元素），该怎么做呢？可以像下面这样使用.closest（）：

1$（'#gallery img'）.click（function（）{

2$（this）.css（'outline'，’2px red solid'）；

3$（this）.closest（'div'）.css（'backgroundColor'，'white'）；

4}）；//end click

在第3行中，$（this）引用＜img＞标签，.closest（'div'）表示找到是＜div＞的一个最近的祖先元素。最近的祖先元素是＜a＞标签，但是，由于它不是一个div, jQuery会略过它并找到下一个祖先元素，依次类推，直到找到一个＜div＞。

·当你想要选择与当前选择相同级别的一个元素的时候，.siblings（）函数很方便。以图13-3为例，当一个访问者单击一个链接，你想要让所有其他的链接都慢慢地淡出并淡入视线中。在这个例子中，click事件应用于一个link标签，但是，你想要对div中所有其他的链接执行这一效果。换句话说，你从一个链接开始，但是想要选择所有的兄弟链接。可以像下面这样来做到这点：

1$（'#gallery a'）.click（function（）{

2$（this）.siblings（）.fadeTo（500，.3）.fadeTo（250，1）；

3}）；//end click

在上面的代码中，$（this）引用单击的链接，因此，.siblings（）选择该div中所有其他的链接。

.siblings（）函数还接收一个参数，即一个选择器的名称，来将选择的兄弟元素限定为某种类型的标签。例如，假设在图13-3所示的＜div＞标签的内部，在链接的前面有一个标题和介绍性的段落。由于标题和段落都在＜div＞标签中，与所有的＜a＞标签在一起，因此它们也是＜a＞标签的兄弟标签。换句话说，使用上面的代码，单击一个链接也会导致所有的标题和段落淡出后再淡入回视线中。要将这种效果仅限于其他链接，我们可以重新编写上面的第2行代码，如下所示：

$（this）.siblings（'a'）.fadeTo（500，.3）.fadeTo（250，1）；

siblings（'a'）内部的'a'把选择限定为仅仅是＜a＞标签的那些兄弟元素。参见教程chapter13文件夹下的siblings.html文件，看看这个函数是如何工作的。

·. next（）会找到当前选择的下一个兄弟元素。5.6节的“一页的FAQ”教程中看到过这个函数的使用方法。在该教程中，单击一个问题将会打开并随后关闭一个＜div＞标签，其中包含了该问题的解答。每个问题都使用一个＜h2＞标签来表示，每个解答用一个div标签表示，并且紧跟在该＜h2＞标签之后。标题和div是兄弟，但是，它们也是该页面上的其他的问题和解答的兄弟元素。因此，当单击一个标题的时候，你必须选择相邻的兄弟（换句话说，下一个兄弟元素）。和.siblings（）一样，.next（）接收一个可选的选择器，以便你可以将选择限定为特定类型的、相邻的兄弟（参见教程chapter05文件夹下的complete_faq.html文件，以查看使用.next（）的一个示例）。

·. prev（）像.next（）一样工作，只不过它选择的是紧邻的前一个兄弟元素。

注意：要了解更多的用来遍历DOM的jQuery函数，访问http：//api.jquery.com/category/traversing/。

13.4　用于操作HTML的更多函数

我们常常需要动态地添加、删除和修改一个页面的HTML。例如，当访问者单击表单上的一个提交按钮时，我们可能想要让“Send form information.Please wait.”文本出现在屏幕上。或者，当访问者的鼠标光标悬停在一张照片上时，我们想在照片的顶部显示一个框，其中带有标题和照片拍摄者。在这两种情况下，我们都需要给页面添加HTML。我们在4.7节学习了大多数的常见的函数。这里只是一个快速回顾：

·. text（）使用你传递给函数的选择来替换一个选择中的文本。例如：

$（'#error'）.text（'You must supply an e-mail address'）；

·. html（）像.text（）一样工作，但是允许你插入HTML而不只是文本：

$（'#tooltip'）.html（'＜h2＞Esquif Avalon＜/h2＞＜p＞Designed for canoe camping.＜/

p＞'）；

高级用户诊断室

给DOM遍历设置一个.end（）

为了编写最少的代码干最多的活儿，jQuery允许我们将函数串联到一起使用。本书4.6.4节介绍了串联功能，但是，概括起来说，它允许你选择某些页面元素，对它们做一些事情，然后对它们做些这样或那样的其他的事情，而只需要将一个函数添加到另一个函数之后就可以做到这些。例如，如果你想要选择页面上的所有的段落，并且让它们淡出视线然后再淡入视线，可以编写下面的代码：

$（'p'）.fadeOut（500）.fadeIn（500）；

可以将任意多个函数链接到一起，包括前面讨论的DOM遍历方法。例如，你可能选择了一个＜div＞标签，在其周围添加一个边框，然后选择了该＜div＞中的所有＜a＞标签，并且更改其文本颜色，如下所示：

$（'div'）.css（'outline'，’2px red solid'）.

find（'a'）.css（'color'，'purple'）；

将其分解为代码片段，其含义为：

1.$（'div'）选择所有的＜div＞。

2..css（'outline'，’2px red solid'）给该div添加一个2像素的红色边框。

3..find（'a'）随后将选择从div更改为div中的所有＜a＞标签。

4..css（'color'，'purple'）使得所有链接（不是div标签）的文本变为紫色。

当把一个DOM遍历函数添加到链中的时候，会改变选择。例如，在上面的代码中，jQuery首先选择该div，然后在链的中间，将选择更改为div中的链接。但是，有时候，你可能想要让选择回到其最初状态。换句话说，你想要选择一个对象，然后选择和第一次选择相关的另一个对象，然后，再回到第一个选择。例如，假设当一个访问者单击不透明度为50%的一个div的时候，你想要该div淡入到不透明度为100%，更改该div中的每个标题的颜色，并且给该div中的每个p添加一个背景颜色。一个动作（例如单击）需要触发页面上不同元素的数个操作。做到这点的一种方法如下所示：

$（'div'）.click（function（）{

$（this）.fadeTo（250，1）；//fade div in

$（this）.find（'h2'）.

css（'color'，’#F30'）；

$（this）.

find（'p'）.

（'backgroundColor'，’#F343FF'）；

}）；//end click

这就是串联真正能够起作用的地方，我们将调用$（this）一次并将所有的函数链接到一起，而不是调用$（this）3次。然而，如果你试图像下面这样将各个函数链接起来，将会遇到麻烦：

$（'div'）.click（function（）{

$（this）.fadeTo（250，1）

.find（'h2'）.css（'color'，’#F30'）

.find（'p'）.（'backgroundColor'，’#F343FF'）；

}）；//end click

这看上去是对的，但是，在.find（'h2'）之后会出现问题，它将选择从div更改为div中的h2标签。当下一个.find（）函数（.find（'p'））运行的时候，jQuery试图找到h2标签中的p标签，而不是div标签中的p标签。幸运的是，你可以使用jQuery的.end（）函数，将对选择的修改“返回”到其最初的状态。在上面的例子中，你可以使用.end（）将选择返回到div，然后，找到div中的＜p＞标签，代码如下所示：

$（'div'）.click（function（）{

$（this）.fadeTo（250，1）

.find（'h2'）.css（'color'，’#F30'）.end（）

.find（'p'）.（'backgroundColor'，’#F343FF'）；

}）；//end click

注意.css（'color'，’#F30）；之后的.end（），这段代码将jQuery选择返回到div，以使得后续的.find（'p'）可以找到div中的所有＜p＞标签。

·. append（）允许在一个元素末尾添加HTML（例如，在一个div的末尾，刚好在结束＜/div＞标签之前）。对于向一个列表底部添加项目来说，这个函数很适用。

·. prepend（）允许在元素开始处添加HTML（例如，在一个div的开始处，刚好在开始＜div＞标签之后）。

·. before（）在选择之前添加内容。

·. after（）像.before（）一样工作，只不过内容添加在选择之后（在结束标签之后）。

使用哪个函数，完全取决于你最终的目标是什么。本书4.4节提到，JavaScript只不过是将Web设计师通常手动执行的任务自动化了，例如，添加HTML和CSS来创建Web页面。如果你要编写一个程序以动态地向一个页面添加内容，例如一个工具提示、一条错误消息、一个提取引用等，只需要想象一下，最终完成的产品应该是什么样的以及实现它们所需的HTML和CSS。

例如，如果当访问者的鼠标光标悬停在一个按钮上的时候，你想要在页面上创建一条特定的消息，尝试构建一个显示该消息的Web页面而不使用JavaScript，只使用CSS和HTML。一旦你有了有效的HTML/CSS模型，看一下用来实现这一效果的HTML：它是否放在某些其他的元素之前？如果是，使用.before（）函数。HTML应该在一个特定的标签中吗？那么，使用.append（）或.prepend（）函数。

jQuery还提供了从页面删除内容的一些函数：

·. replaceWith（）使用你传递给函数的任何内容来完全替换选择（包括标签以及标签中的所有内容）。例如，使用文本“processing……”替换页面上的一个提交按钮，可以使用如下这段代码：

$（'：submit'）.replaceWith（'＜p＞processing……＜/p＞'）；

·. remove（）从DOM删除选择，实际上是将其从页面删除。例如，要将ID为error的一个div从页面中删除，可以编写如下代码：

$（'#error'）.remove（）；

尽管你可能只需要上面列出的以及第4章介绍的那些函数，jQuery还提供了其他函数，给出了操作页面的HTML的其他方式。

·. wrap（）会将一个选择中的每个元素包围到一对HTML标签中。例如，如何对页面上的图像创建一种好看的标题效果呢？首先，我们可以选择页面中的图像，将它们包围到带有诸如figure这样的class的一个＜div＞中，并且在该div中添加一个＜p＞标签，这个＜p＞标签有一个caption的class。然后，使用CSS，我们可以将div和caption格式化为想要的形式。如下是完成这些工作的一种方法：

1//loop through the list of images

2$（'img'）.each（function（）{

3//save reference to current image

4 var$this=$（this）；

5//get the alt property for the caption

6 var caption=$this.attr（'alt'）；

7//add the HTML

8$this.wrap（'＜div class="figure"＞＜/div＞'）.after（'＜p＞’+caption+’＜/p＞'）；

9}）；//end each

上面的代码首先选择页面上的所有图像，然后使用.each（）函数（参见4.9节）遍历图像的列表；在第4行，循环中的当前图像存储到一个变量中（正如本书13.1.2节所述，真是一种好的做法）。在第6行，从图像获取alt属性，并将其存储到名为caption的一个变量中。最后，第8行将图像包围到一个＜div＞标签中，然后，使用前面介绍的.after（）函数在图像的后面添加一个标题。

注意：在教程chapter13文件夹下的wrap.html文件中，可以看到第8行中的.wrap（）代码的应用。

我们将一组完整的标签传递给.wrap（）函数，如$（'p'）.wrap（'＜div＞＜/div＞'），甚至可以传递下面这样的一组嵌套标签：

$（'#example'）.wrap（'＜div id="outer"＞＜div id="inner"＞＜/div＞＜/div＞'）；

在上面的代码中，jQuery将用两个div来包围选择，使得HTML如下所示：

＜div id="outer"＞

＜div id="inner"＞

＜div id="example"＞This is the original code on the page＜/div＞

＜/div＞

＜/div＞

·. wrapInner（）将选择中的每个元素的内容包含到HTML标签中。例如，假设你的HTML中有如下的代码：

＜dio id="outer"＞

＜p＞This is the contents of outer＜/p＞

＜/div＞

如果浏览器遇到代码$（'#outer'）.wrapinner（'＜div id="inner"＞＜/div＞'），在页面中它将HTML翻译成如下形式：

＜div id="outer"＞

＜div id="inner"＞

＜p＞This is the contents of outer＜/p＞

＜/div＞

＜/div＞

·. unwrap（）直接将包围选择的父标签删除。例如，假设一个页面的HTML如下：

＜div＞

＜p＞a paragraph＜/p＞

＜div＞

运行代码$（'p'）.unwrap（），将会把该HTML修改为：

＜p＞a paragraph＜/p＞

外围的＜div＞直接删除了。注意，和这里讨论的其他函数不同，.unwrap（）不接收参数，换句话说，在.unwrap（）的括号中不要放置任何东西，否则它无法工作。

·. empty（）删除一个选择的所有内容，但是，保留选择不动。例如，假设你的Web页面上有一个ID为messageBox的div。使用JavaScript，你可以动态地给这个div添加内容，以便当访问者与页面交互的时候，向他显示消息。你可能使用很多目录标题、图像和段落来填充这个div，以便为访问者提供状态消息。你可能想要在程序中的某个位置清空这个框（例如，没有当前消息要显示的时候），但是，保留该框在那个位置，以便稍后可以为其添加状态消息。为了删除该框中的所有标签，我们可以使用如下所示的代码：

$（'#messageBox'）.empty（）；

和.unwrap（）一样，.empty（）也不接收参数。

注意：jQuery提供了甚至更多的函数来操作HTML。可以在http：//api.jquery.com/category/manipulation/了解到所有的这些函数。

13.5　高级事件处理

在第5章中，我们了解到将事件添加给元素的方便的jQuery函数。例如，如果每次默认单击一个一级标题的时候，你想要弹出一个警告框，那么，可以像下面这样使用.click（）事件：

$（'h1'）.click（function（）{

alert（'ouch！'）；

}）；//end click

jQuery提供了函数用于反馈不同的事件，例如，用于访问者提交一个表单的.submit（），或者当访问者的鼠标的光标离开一个元素的时候做某些事情的.mouseout（）函数。所有这些函数都仅是5.5节介绍的使用jQuery.bind（）函数的快捷方法。

.bind（）函数接收几个参数：事件类型（'click'、'mouseover'等）以及当发生该事件时候运行的一个函数。例如，我们可以重新编写上面的代码，如下所示：

$（'h1'）.bind（'click'，function（）{

alert（'ouch！'）；

}）；//end bind

对于.bind（）和.click（）及.hover（）的快捷事件函数，都有一个大问题：它们只适用于页面当前的HTML。对于本书中的示例，这没有问题，HTML载入，浏览器对该HTML应用事件监听器，并且一个用户与该页面交互。然而，随着你的程序变得越来越高级，我们将开始使用jQuery的操作函数（例如，本章前面介绍的.append（）、.before（）等），对Web页面的内容执行很多动态更新。遗憾的是，页面上已有的时间处理程序并不适用于你随后添加的HTML，这可能会导致产生问题。

例如，假设你创建了一个Web游戏：该游戏的目标是剔除花园中的所有杂草。玩家必须单击一个杂草，将其从屏幕上删除。但是，当然，杂草会继续填满屏幕。基本上，程序保持添加杂草，并且玩家需要单击杂草直到将它们都删除。在这个例子中，每个“杂草”都可以通过一个＜div＞标签表示，其中带有一副杂草的图片。程序持续地将div添加到页面，而玩家尝试通过单击div来删除它们。换句话说，每个div必须有一个click事件处理程序应用于它，以便它对于访问者的单击做出响应。你可以在程序中添加一些代码来做到这点，如下所示：

$（'.weed'）.click（function（）{

$（this）.remove（）；

}）；//end click

这段代码的问题是，它只适用于已经存在的元素。如果通过编程添加新的div，例如，＜div class="weed"＞，click事件处理程序并不适用于它们。

当我们使用本书第四部分所介绍的Ajax的时候，代码只适用于已有的元素，这也是个问题。Ajax允许我们使用从Web服务器获取的信息来更新页面的内容。例如，Gmail通过持续地从Web服务器获取内容，并且在Web服务器中更新内容，从而做到当你接收邮件的时候就可以显示它。在这个例子中，当你第一次开始使用Gmail的时候，接收的邮件的列表就发生变化。在页面载入的时候，适用于页面内容的任何事件，并不适用于从服务器添加的新的内容。

无论何时更新页面，我们都可以重新应用事件处理程序，但是，这种方法很慢而且效率较低。幸运的是，jQuer y提供了另一个函数来处理这种情况，这就是.delegate（）。即便新的元素添加到页面的时候，.delegate（）函数仍然保持活跃；也就是说，你只需要在程序中使用它一次，并且，它将能够对随后添加到页面的元素的事件做出响应。如下是.delegate（）的基本语法：

$（'#container'）.delegate（'selector'，'event'，function（）{

//code to respond to event

}）；//end delegate

这语法看上去有点奇怪，因此，我们分解说明一下：

·首先，选择一个容器元素。这有点容易令人混淆，因为使用.bind（），我们选择想要对其添加事件的元素，而在这里，我们选择的元素则包含了想要对其添加事件的元素。例如，如果想要给一个ID为siderbar的div中的所有列表项添加一个click事件处理程序，首先像下面这样选择该div：

$（'#sidebar'）

如果想要对页面上的所有＜a＞标签应用一个mouseover事件处理程序，另一方面，我们选择body自身：

$（'body'）

·接下来，我们调用delegate（）函数，并且向它传递3个参数：想要对其添加事件的选择器、事件的名称，以及响应该事件的函数。例如，使用代理来对ID为sidebar的一个div中的列表项应用一个click事件处理程序，我们可以编写如下代码：

$（'#sidebar'）.delegate（'li'，'click'，function（）{

//do something here

}）；//end delegate

同样，要在页面中对所有＜a＞标签应用一个mouseover事件处理器，你可以这样做：

$（'body）.delegate（'a'，'mouseover'，function（）{

//do something here

}）；//end delegate

要查看.delegate（）的实际应用，打开教程chapter13文件夹中的delegate.html文件。

注意：jQuery还提供了一个名为.live（）的函数，该函数类似于.delegate（），因为它对于函数运行之后才添加的页面元素应用事件处理程序。然而，它比.delegate（）慢很多，因此最好避免使用它。

第14章　深入JavaScript

本章将介绍帮助你成为一名更好的JavaScript程序员的各种概念。要编写功能完备的JavaScript程序，并不需要这里介绍的大多数思想，因此，如果你根本不能理解它们，也不要担心。前面几个小节提供了有用的提示和方法，用来操作字符串、数字和日期，一旦掌握了这些基本知识，这些小节真的可以帮助你处理表单中的访问者输入，操作HTML和HTML属性，并且为日历生成日期。14.5节的“综合应用”小节给出了对于初学者的一些很好的建议，但是，我们可以较长时期愉快地编程，而不需要知道本章其他各节的信息。但是，如果你想要扩展自己的技能，本章将指出正确的方向。

14.1　操作字符串

字符串是最经常操作的数据类型之一：来自字段的输入、图像的路径、URL以及想要在页面上替换的HTML，这些都是组成字符串的字母、符号和数字的例子。我们在第2章中学习了字符串的基础知识，但是，JavaScript提供了很多有用的方法来使用和操作字符串。

14.1.1　确定字符串的长度

很多时候，我们想要知道字符串包含了多少个字符。例如，假设某人在你的高度保密Web站点上创建账户的时候要进行确认，他们创建了多于6个字母但少于15个字母的一个新密码。字符串有一个length属性，刚好给出了这一信息。在变量名的后面添加一个句点，后面跟着length来获取该字符串的长度，例如name.length。

例如，假设你有一个表单，它带有一个文本字段。该字段的ID为password。要确保密码具有合适的字符数，可以使用条件语句（参见3.1.1节）来测试密码长度，如下所示：

var password=$（'#password'）.val（）；

if（password.length＜=6）{

alert（'That password is too short.'）；

}else if（password.length＞15）{

alert（'That password is too long.'）；

}

注意：在上面的示例中，这段代码片段也用到了submit（）事件处理程序中（参见9.1.4节），以便你可以测试在提交表单的时候访问者是否输入了足够长的密码。

14.1.2　更改字符串的大小写

JavaScript提供了两个方法，将字符串转换为全部大写或全部小写，从而可以将“hello”更改为“HELLO”，或者将“NOT”更改为“not”。你可能会问，为什么？将字符串中的字母转换为相同的大小写，这可以使得比较两个字符串变得更容易。例如，假设你创建了一个类似第3章中所介绍的一个谜题程序（参见3.4节），并且，一个问题是“Who was the first American to win the Tour De France?”你可能用类似下面的代码来检查参加答题者的答案：

var correctAnswer='Greg LeMond'；

var response=prompt（'Who was the first American to win the Tour De8

France?’，’'）；

if（response==correctAnswer）{

//correct

}else{

//incorrect

}

答案是Greg LeMond，但是，如果参加答题的人输入了Greg Lemond，结果会如何呢？条件看上去类似这样：'Greg Lemond'=='Greg LeMond'。由于JavaScript是区分大小写字母的，Lemond中的小写字母'm'不会匹配LeMond中的'M'，因此，参加答题者可能会得到答错的结果。如果参加答题者按下了Caps键，并且输入了GREG LEMOND，也会得到同样的结果。

要解决这个难题，我们可以将两个字符串都转换为同样的大小写，然后再比较它们：

if（response.toUpperCase（）==correctAnswer.toUpperCase（））{

//correct

}else{

//incorrect

}

在这个例子中，条件语句将参加答题者的答案和正确的答案都转换为大写字母，因此，'Greg Lemond'变成了'GREG LEMOND'，并且'Greg LeMond'变成了'GREG LEMOND'。

要获得全部小写的字符串，像下面这样使用toLowerCase（）方法：

var answer='Greg LeMond'；

alert（answer.toLowerCase（））；//'greg lemond'

注意，这些方法都没有真正改变实际存储在变量中的字符串，它们只是以全部大写或全部小写的形式返回了该字符串。因此，在上面的例子中，即便提示出现以后，answer仍然包含'Greg LeMond'（换句话说，这些方法就像3.4.3节介绍的函数一样工作，它们返回某些其他的值）。

14.1.3　搜索一个字符串：indexOf（）技术

JavaScript提供了几种技术，来在字符串中搜索一个单词、数字或其他的一串字符。搜索可能很方便，例如，如果你想要知道访问者使用哪种Web浏览器来浏览你的站点。每个Web浏览器在一个字符串中标识关于自己的信息，该字符串包含了很多不同的统计数据。可以通过在一个Web页面中添加下面这段JavaScript，并且在Web浏览器预览，从而看到这个字符串：

＜script＞

alert（navigator.userAgent）；

＜/script＞

Navigator是一个Web浏览器对象，并且userAgent是navigator对象的一个属性。userAgent属性包含了长长的一串信息，例如，对于运行在Windows XP上的Internet Explorer 7，其userAgent属性是：Mozilla/4.0（compatible；MSIE 7.0；Windows NT 5.1）。因此，如果你想看看Web浏览器是IE 7的情况，你可以在userAgent字符串中只搜索“MSIE 7”。搜索字符串的方法之一是indexOf（）方法。在字符串之后添加一个句点，然后是indexOf（），并且提供你要查找的字符串。基本的结构如下所示：

string.indexOf（'string to look for'）

indexOf（）方法返回一个数字：如果没有找到搜索字符串，该方法返回-1。因此，如果你想要检查Internet Explorer，可以这样做：

var browser=navigator.userAgent；//this is a string

if（browser.indexOf（'MSIE'）！=-1）{

//this is Internet Explorer

}

在这个例子中，如果indexOf（）没有在userAgent字符串中找到'MSIE'，它将返回-1，因此，条件测试查看结果是否不等于（！=）-1。当indexOf（）方法确实找到了要搜索的字符串，它返回一个数字，它等于要查找的字符串的开始位置。如下的示例使得事情更加清晰一些：

var quote='To be, or not to be.’

var searchPosition=quote.indexOf（'To be'）；//returns 0

在这里，indexOf（）在字符串'To be, or not to be.’中搜索'To be'的位置。较大的字符串以'To be'开始，因此，indexOf（）在第一个位置就找到了'To be'。但是，按照编程的方式，第一个位置认为是0，第二个字母（o）在位置1，并且第三个字母（在这个例子中是一个空格）是2（正如2.8.2节所介绍的，数组也以同样的方式计数）。

indexOf（）方法从字符串的开头开始搜索。你也可以使用lastIndexOf（）方法，从字符串的末尾开始搜索。例如，在莎士比亚的名言中，单词'be'出现在两个位置，因此，可以使用indexOf（）找到第一个'be'，并使用lastIndexOf（）找到最后一个'be'：

var quote="To be, or not to be."

var firstPosition=quote.indexOf（'be'）；//returns 3

var lastPosition=quote.lastIndexOf（'be'）；//returns 17

这两个方法的结果如图14-1所示。在这两个例子中，如果'be'不存在于字符串中的任何位置，结果将会是-1；如果只有一个搜索字符串的实例，indexO f（）和lastIndexOf（）将会返回相同的值，也就是搜索字符串在较大的字符串中开始的位置。

图　14-1　indexOf（）和lastIndexOf（）方法在一个较大的字符串中搜索一个特定的字符串。如果找到了搜索字符串，返回它在较大的字符串中的位置

14.1.4　使用slice（）提取一个字符串的部分

要提取一个字符串的一部分，可以使用slice（）方法。这个方法返回一个字符串的一部分。例如，假设你有一个类似http：//www.sawmac.com的字符串，并且想要删除http：//部分。做到这点的一种方法就是提取字符串中http：//之后的每一个字符，如下所示：

var url='http：//www.sawmac.com'；

var domain=url.slice（7）；//www.sawmac.com

slice（）方法要求一个数字，表示提取的字符串的开始的索引位置（参见图14-2）。在这个例子中是url.slice（7），7表示字符串的第8个字母（记住，第一个字母位于位置0）。该方法返回了从指定的索引位置开始到字符串结束的所有字符。

图　14-2　如果没有为slice（）方法提供第二个参数，它只是从指定位置（在这个例子中是7）一直到字符串的末尾提取一个字符串

也可以通过给slice（）提供第二个参数，从一个字符串中提取指定数目的字符。slice（）方法的基本结构如下：

string.slice（start, end）；

start值是一个数字，表示要提取的字符串的第一个字符。End值有点容易令人混淆，它不是提取的字符串的最后一个字母，它实际上是最后一个字母的位置+1。例如，如果我们想要提取字符串'To be, or not to be'的前5个字母，我们将第一个参数指定为0，将第二个参数指定5。正如你在图14-3中看到的，0是字符串的第一个字母，而5是第6个字母，但是，指定的最后一个字母不会从字符串中提取。换句话说，第二个参数指定的字符不是提取的字符串的一部分。

提示：如果你想要从一个字符串提取特定数目的字符，只要用该数字加上开始的值就行了。例如，如果你想要获取一个字符串的前10个字母，第一个参数应该是0（第一个字母），最后一个参数应该是0+10或者10：slice（0，10）。

你也可以指定一个负数，例如，quote.slice（-6，-1）。负数表示从字符串的末尾开始往回计数，如图14-3所示。

图　14-3　slice（）方法提取一个字符串的一部分。实际的字符串不会有任何变化。例如，在这个例子中，quote变量中包含的字符串并不会被quote.slice（0，5）改变。这个方法直接返回了提取的字符串，我们可以将其存储到一个变量中，显示在一个警告框中，甚至作为一个参数传递给一个函数

提示：如果你想要提取一个字符串，它包含了从字符串末尾倒数第6个字母，一直到字符串结束的所有字符，那么，可以省略掉第二个参数：

quote.slice（-6）；

14.2　在字符串中查找模式

有时候，我们想要搜索一个字符串，但不是针对一个具体的值，而是针对一种特定模式的字符。例如，当一个访问者填写一个订货表单的时候，我们想要确保他以正确的格式提供了一个电话号码。我们实际上不是要查找类似503-555-0212的一个具体号码。相反，我们要查找一种通用的模式：3个数字，一个连字符，3个数字，另一个连字符，然后是4个数字。你想要检查访问者输入的值，如果它匹配这个模式（例如，它是415-555-3843、408-555-3782或者212-555-4828等），那么，一切正常。但是，如果它不匹配该模式（例如，访问者输入了823lkjxdfglkj），那么，你可能要显示类似“Hey buddy, don't try to fool us！”的一条消息。

JavaScript允许你使用正则表达式在一个字符串中查找模式。正则表达式是一系列的字符，它定义了你想要搜索的一种模式。和很多编程术语一样，正则表达式的名字也有一点误导。例如，下面是一个常见的正则表达式的样子：

/^[-\w.]+@（[a-zA-Z0-9][-a-zA-Z0-9]+\.）+[a-zA-Z]{2，4}$/

除非你是超级外星人，否则，从正则表达式看不出什么非常有规则的内容。要创建一个模式，可以使用类似*、+、?和\w的字符，它们经过JavaScript解释器的翻译，可以匹配字符串中真正的字符，如字母、数字等。

注意：专业人士常常将正则表达式简称为regex。

14.2.1　创建和使用基本的正则表达式

要在JavaScript中创建正则表达式，必须创建一个正则表达式对象，它是两个斜杠之间的一系列字符。例如，要创建于单词“hello”匹配的一个正则表达式，你可以输入如下内容：

var myMatch=/hello/；

就像一个开始引号和一个结束引号创建一个字符串一样，开始/和结束/创建了一个正则表达式。

JavaScript还有几个字符串方法，它们充分利用了正则表达式的优点（我们将从14.2.5节开始学习它），但是，最为基本的方法就是search（）方法。它的工作方式和indexOf（）方法很像，但是，它在一个字符串中搜索一个模式（一个正则表达式），而不是试图在一个较大的字符串中搜索一个字符串。例如，假设你想要在字符串'To be or not to be.’找到'To be'。你在14.1.3节看到了如何使用indexOf（）方法做到这一点，这里将介绍如何使用正则表达式做到这一点：

var myRegEx=/To be/；//no quotes around regular expression

var quote='To be or not to be.’；

var foundPosition=quote.search（myRegEx）；//returns 0

如果search（）方法找到一个匹配，它返回了匹配的第一个字母的位置，并且，如果它没有找到一个匹配，它返回-1。因此，在上面的例子中，变量foundPosition是0，因为，'To be'从字符串非常靠前（第一个字母）的地方开始。

正如我们在14.1.3节介绍过的，indexOf（）方法以同样的方式工作。你可能会想，如果两个方法是相同的，为什么还要这么麻烦地使用正则表达式？正则表达式的好处是，它们可以在字符串中找到模式，因此，它们能够进行比indexOf（）方法更为复杂和微妙的比较，而后者总是查找和一个具体字符串的匹配。例如，如果要查找一个字符串是否包括Web地址http：//www.missingmanuals.com/，可以使用indexOf（）方法，但是，要查找与URL的格式匹配的任何文本，例如，当验证某个人在你的博客上发表评论的时候是否提供了一个Web地址，那么，必须使用正则表达式。

然而，要掌握正则表达式，你需要学习构造一个正则表达式所必需的、经常令人混淆的符号。

14.2.2　构建一个正则表达式

尽管一个正杂表达式经常由一个单词或几个单词构成，更常见的是，使用字母和特殊字符的组合来定义想要匹配的模式。正则表达式提供了不同的符号来表示不同类型的字符。例如，一个单个的句点（.）表示一个单个的字符，任意字符，而\w匹配任意字母或数字（但是不匹配空格，或者类似$或%的符号）。表14-1提供了大多数常见的模式匹配字符的一个列表。

注意：如果对于“正则”表达式的所有讨论会让你头疼，你可能会很高兴地获知，本书提供了一些有用的正则表达式（参见14.2.4节），你可以在自己的脚本中复制和使用它们（而不需要真的知道它们是如何工作的）。

学习正则表达式，通过示例来解释更容易一些，因此，本节剩下的部分将带领你来看一些正则表达式的示例，以帮助你完全理解这一主题。假设你想要匹配连续的5个数字，可能要检查字符串中是否有一个U.S.Zip代码。

1.匹配一个数字。

第一步是直接搞清楚如何匹配一个数字。如果你参考表14-1，将会看到，有一个专门的正则表达式符号用于这个目的，这就是\d，它匹配任何单个的数字。

2.匹配连续的5个数字。

由于\d匹配单个的数字，匹配5个数字的一种简单方式是，使用正则表达式\d\d\d\d\d（参见14.2.3节，那里介绍了编写这种代码的更紧凑的方式）。

3.仅匹配5个字母。

正则表达式就像是一颗精确制导的导弹：它将目标设置为所匹配的字符串的第一部分。因此，有时候，我们得到的匹配只是一个完整单词或一组字符的一部分。这个正则表达式匹配所遇到的前5个连续的字母。例如，它将匹配数字12345678998中的12345。显然，12345678998不是邮政编码，因此，你需要仅仅以5个数字为目标的而一个正则表达式。

\b字符（叫做单词分界字符）匹配任何非字母或非数字的字符，因此我们可以重新编写自己的正则表达式如下：\b\d\d\d\d\d\b。我们也可以使用^字符来匹配一个字符串的开始，并且使用$来匹配一个字符串的结尾。如果想要整个字符串匹配你的正则表达式，这种技巧很方便。例如，如果某人在一个订购表单的邮政编码字段中输入“kjasdflkjsdf 88888 lksadflkjsdkfjl”，你可能想要让访问者在订购之前搞清楚（并修正）其邮政编码。毕竟，我们确实在查找类似97213的内容（字符串中没有其他字符）。在这个例子中，正则表达式将会是^\d\d\d\d\d$。

注意：邮政编码可以有5个以上的数字。ZIP+4格式在前5个数字之后，包含一个斜杠和4个额外的数字，例如，97213-1234。要了解处理这种可能性的正则表达式，查看14.2.4节。

4.将正则表达式用于JavaScript中假设你已经将用户的输入捕获到一个名为zip的变量中，并且想要进行测试，看看表单中的输入是否是一个有效的5个数字的邮政编码：

var zipTest=/^\d\d\d\d\d$/；//create regex

if（zip.search（zipTest）==-1）{

alert（'This is not a valid zip code'）；

}else{

//is valid format

}

这些步骤中的正则表达式示例是有效的，但是，它似乎需要太多工作，要输入\d五次之多。如果想要匹配连续的100个数字，该怎么办呢？幸运的是，JavaScript带有几个符号，可以匹配同一字符的多次出现。表14-2包含了这些符号的一个列表。你可以将这些符号直接放在想要匹配的字符的后面。

例如，要匹配5个数字，可以编写\d{5}。\d匹配一个数字，随后，{5}告诉JavaScript解释器，匹配5个数字。因此，\d{100}将匹配连续的100个数字。

让我们来看另一个例子。假设你想要从字符串中找到任何GIF文件的名称。此外，你想要确切的文件名，并且可能以某种方式在脚本中使用（例如，你可以使用14.2.5节介绍的match（）方法）。换句话说，你想要找到与一个GIF文件名的基本模式匹配的任何字符串，例如，logo.gif、banner.gif或ad.gif。

1.识别这些名称之间的常用模式。

要构建一个正则表达式，首先需要知道要查找的字符是什么模式。在这里，由于要查找GIF，我们知道，其文件名将以.gif结尾。换句话说，在.gif之前，可能有任意多个字母、数字或其他字符。

2.查找.gif。

由于我们要查找字面字符串’.gif'，你可能会想，正则表达式的这个部分应该只是.gif。然而，如果你查看一下表14-1，将会看到，一个点号具有特殊的含义，表示“匹配任何字符串”。因此，.gif将会匹配“.gif”，但是，它也会匹配“tgif”。一个句点匹配任意单个的字符，因此，除了匹配一个句点，它还匹配tgif中的“t”。要创建带有一个句点的正则表达式，可在其前面添加一个反斜杠，因此，\.解释为“找出句点符号”。因此，该正则表达式将会查找.gif而不是\.gif。

3.找到.gif之前的任意多个字符。

要找到任意多个字符，我们可以使用.*，它解释为“找到一个字符（.）0次或多次（*）”。这个正则表达式匹配任何字符串中的所有字母。然而，如果你使用它创建.*\.gif这样的一个正则表达式，你最终匹配的将不只是一个文件名。例如，如果有一个字符串'the file is logo.gif'，那么，正则表达式.*\.gif将匹配整个字符串，而你实际想要的只是logo.gif。要做到这一点，使用\S字符，它匹配任何的非空格字符：\S*\.gif将只匹配该字符串中的logo.gif。

4.让搜索不区分大小写。

这个正则表达式中还有一个问题：它只查找以.gif结尾的文件，但是，.GIF也是有效的文件扩展名，因此，这个正则表达式不会选出类似logo.GIF的一个名称。要让一个正则表达式忽略大写字母和小写字母之间的区别，创建一个正则表达式的时候，我们使用i参数：

/\S*\. gif/i

注意，i放在模式之外，并且放在定义正则表达式模式的结尾的/符号的右边。

5.付诸应用：

var testString='The file is logo.gif'；//the string to test

var gifRegex=/\S*\.gif/i；//the regular expression

var results=testString.match（gifRegex）；

var file=results[0]；//logo.gif

这段代码会从字符串提取出该文件名（我们将在14.2.5节学习match（）方法是如何工作的）。

14.2.3　模式的分组部分

我们可以使用圆括号在模式中创建一个子分组。当使用表14-2中的任何字符来匹配同一模式的多个实例的时候，子分组非常方便好用。

例如，假设想要看看一个字符串是否包含了“Apr”或“April”，这二者都以“Apr”开头，因此，我们知道要匹配它，但是，我们不能只匹配“Apr”，因为“Apricot”或“Aprimecorp”中也会匹配“Apr”。因此，我们必须匹配“Apr”后面跟着一个空格，或者其他的单词结尾（这要用到表14-1中所述的\b正则表达式字符），或者是April后面跟着别的单词结尾。换句话说，“il”是可选的。下面展示了如何使用圆括号做到这一点：

var sentence='April is the cruelest month.’；

var aprMatch=/Apr（il）?\b/；

if（sentence.search（aprMatch）！=-1）{

//found Apr or April

}else{

//not found

}

这里使用的正则表达式/Apr（il）?\b/，使得“Apr”成为必需的，但是，子模式（il）是可选的（即?字符意味着出现0次或一次）。最后，\b匹配单词的结尾，因此，我们不会匹配“Apricot”或“Aprilshowers”。（参见14.2.7节了解子模式的另一用法。）

提示：在www.regexlib.com可以找到正则表达式的完整的库。在这个Web站点，我们可以找到用于任何情况的正则表达式。

14.4.4　有用的正则表达式

创建正则表达式也有其自身的挑战。不仅需要理解不同的正则表达式字符是如何工作的，还必须搞清楚针对不同可能的搜索的合适的模式。例如，如果你想要找到邮政编码的一个匹配，你需要考虑到邮政编码可能只有5个数字（97213）或者5+4个数字（97213-3333）的事实。为了帮助你开始使用正则表达式，这里介绍一些常用的正则表达式。

提示：如果你不喜欢输入这些正则表达式（谁也不能怪你），可以在本书教程chapter14文件夹下名为example_regex.txt的文件中找到已经设置好的正则表达式。

美国邮政编码

邮政编码在各个国家都不同，但是，在美国，它们要么是5个数字，要么是5个数字后面跟着一个连字符和4个数字。如下是匹配这两种选项的正则表达式：

\d{5}（-\d{4}）?

注意：要了解与其他国家的邮政编码匹配的正则表达式，请访问http：//regexlib.com/Search.aspx?k=postal+code。

该正则表达式可以划分为如下几个较小的部分：

·\d{5}匹配5个数字，例如97213。

·（）创建一个子模式。圆括号之间的任何内容都看做需要匹配的一个单个模式。稍后，我们将看到为什么这很重要。

·-\d{4}匹配一个连字符后面跟着4个数字，例如-1234。

·匹配之前的模式的0次或1次实例。在这里是圆括号出现的部分，（-\d{4}）当做一个单个的部分对单，因此，?意味着匹配一个连字号后面跟着4个数字的0次或一次实例。由于你不一定必须包含连字符+4个数字，这个模式可能出现0次。换句话说，如果测试97213这样的一个值，将会得到一个匹配，因为连字号后面跟着4个数字这是可选的。

注意：要确保整个字符串匹配一个正则表达式，以^开始一个正则表达式，并且以一个$结束它。例如，如果想要确保某人只能在邮政编码表格字段输入一个格式有效的邮政编码，使用正则表达式^\d{5}（-\d{4}）?$来防止“blah 97213 blah blah”之类的一条回应。

美国电话号码

美国电话号码由一个3位的区号以及后面跟着7位数字组成。然而，人们也有很多种不同的方式来写电话号码，例如，503-555-1212、（503）555-1212、503.555.1212或者只是503 555 1212。用于这一模式的一个正则表达式是：

\（?（\d{3}）\）?[-.]（\d{3}）[-.]（\d{4}）

注意：对于匹配其他国家的电话号码的正则表达式，请访问http：//regex-lib.com/Search.aspx?k=phone+number。

这个正则表达式看上去有些复杂，但是，如果将其细分（下面进行了很好地解释），其含义就清楚了：

·\（匹配一个字面的开始圆括号字符。因为圆括号用来分组模式（参见前面的邮政编码示例），开始圆括号在正则表达式中具有特殊的含义。要告诉JavaScript解释器匹配一个实际的开始圆括号，我们需要使用反斜杠字符将字符转义（就像2.3.3节介绍的转义引号）。

·?表示（符号是可选的，因此，像503-555-1212这样不带圆括号的电话号码仍然匹配。

·（\d{3}）是一个子模式，匹配任意3个数字。

·\）?匹配一个可选的结束圆括号。

·[-.]匹配一个空格、连字符或句点（注意，通常你必须像\.这样转义一个句点，以告诉JavaScript解释器，我们想要匹配一个句点字符，而不是将其当做一个特殊的正则表达式符号来匹配任何字符；然而，当在一个方括号之内的时候，一个句点总是当做字面值对待）。

·（\d{3}）是匹配任意3个数字的另一个子模式。

·[-.]匹配一个空格、连字符或句点

·（\d{4}）是最后的一个子模式，它匹配任意4个数字。

注意：子模式是出现在圆括号中的模式，例如，上面的电话号码正则表达式中的（\d{3}）。正如本书14.2.7节所述，当使用replace（）方法的时候，它们用起来很方便。

E-mail地址

在接受来自表单的用户输入的时候，检查一个有效的E-mail地址也是一种常见的任务。很多人试图使用类似“none of your business”的一条回应来避免提供有效的E-mail地址，或者人们只是输错了自己的E-mail地址（例如，missing@sawmac.commm）。如下的正则表达式可以检查一个字符串是否包含了格式正确的E-mail地址：

[-\w.]+@（[A-z0-9][-A-z0-9]+\.）+[A-z]{2，4}

注意：这个正则表达式并不检查一个地址是否是某个人的真实有效的E-mail地址，它只是检查其格式是否像是一个真正的E-mail地址。

这个正则表达式可以分解如下：

·[-\w.]+匹配一个连字符、任意个单词字符或者一个句点一次或多次。因此，它将匹配“bob”、“bob.smith”或“bob-smith”。

·@是E-mail地址中的@符号，例如，missing@sawmac.com。

·[A-z0-9]匹配一个字母或数字。

·[-A-z0-9]+匹配一个字母、数字或连字符的一个或多个实例。

·\.是一个句点字符，因此，它将匹配sawmac.com（http：//www.sawmac.com）中的句点。

·+匹配包含了上面提到的3个匹配的模式的一个或多个实例。这个字符考虑到类似bob@mail.sawmac.com中的子域名。

·[A-z]{2，4}是任意字母的2次3次或4次匹配。这会匹配.com中的com或.uk中的uk。

注意：上面列出的E-mail正则表达式并不会匹配所有技术上有效的E-mail地址。例如，！#$%＆’*+-/=?^_.{|}～@example.com从技术上讲是一个有效的E-mail地址，但是，这里描述的正则表达式无法匹配它。它涉及找到人们实际可能使用的E-mail地址。如果你想要更加精确，可以使用如下的正则表达式。注意，将其作为单独一行输入：

/^[\w！#$%＆\’*+\/=?^{|}～.-]+@（?：[a-z\d][a-z\d-]*（?：\.[a-z\d][a-z\d-]*）?）+\.（?：[a-z][a-z\d-]+）$/i

日期

日期也可以以很多不同的方式书写，例如，09/28/2007、9-28-2007、09 28 2007或者甚至是09.28.2007（这些只是美国常用的格式。在世界上其他的地方，日期出现在月份之前，如28.09.2007）。由于访问者可能以这些格式的任意一种输入一个日期，因此，我们需要一种方式来检查他们是否提供了一个格式有效的日期。（在14.4.3节）你将了解到如何将这些格式的任意一种转换为一个单个的、标准的格式，以便确保你从一个表单上接收的所有日期都是格式正确的。）

下面是检查一个正确输入的日期的正则表达式：

（[01]?d）[-\/.]（[0123]?\d）[-\/.]（\d{4}）

·（）将接下来的两个正则表达式模式括起来，以将它们分组。它们一起组成了表示月份的数字。

·[01]?匹配0或者1，?使其成为可选的。这用于月份的第一个数字。显然，它不能比1大，因为没有22月。此外，如果月份是从1～9月，你可能只会得到5而不是05。这就是为什么它是可选的。

·\d匹配任意的数字。

·[-\/.]将匹配一个连字符、反斜杠、句点或空格字符。这是月份和日期直接可接收的分隔符，例如，10/21、1021、10.21或10-21。

·（）是下一个子模式，它用来捕获日期。

·[0123]?匹配0、1、2或3 0次或一次。由于一月中没有第40天，我们将日期的第一个数字限制在这4个数字之一。这个模式是可选的（由?所确定的），因为一些人可能输入9而不是09来表示日期。

·\d匹配任何数字。

·[-\/.]和上面所述的一样。

·（）捕获年份。

·\d{4}匹配4个数字，例如1908或2880。

Web地址

如果你要求访问者输入其Web地址，并且你想要确认他提供了一个Web地址，或者如果你想要扫描某些文本并识别出列出的每个URL，那么，匹配一个Web地址是很有用的。用于URL的一个基本的正则表达式如下：

（（\bhttps?：\/\/）|（\bwww\.））\S*

这个表达式有点复杂，因为，它使用了很多圆括号来分组表达式的不同部分。图14-4可以帮助你理解这一正则表达式。一对圆括号（#1）将两个其他的圆括号（#2和#3）包括起来。两个组之间的|符号表示“或”的意思。换句话说，正则表达式需要匹配#2或者#3。

图　14-4　你可以使用圆括号分组表达式，并且使用|（管道）符号来查找两个表达式之一。例如，外围的表达式（#1）将匹配与#2或#3匹配的任何文本

·（是外围组的开始（图14-4中的#1）。

·（是内层组的开始（图14-4中的#2）。

·\b匹配一个单词的开始。

·http匹配以http开始的完整的Web地址的开始。

·s?是可选的。因为Web页面可能通过一个安全连接发送，一个有效的Web地址也可能是以https开头的。

·：\/\/匹配：//。因为反斜杠在正则表达式中有特殊的含义，因此，需要在其前面放置一个斜杠，以使其匹配反斜杠字符。

·）是内层组的结束（图14-4中的#2）。综合起来，这个组将匹配http：//或https：//。

·|匹配一个组或另一个组（图14-4中的#2或#3）。

·（是第二个内层组的开始（图14-4中的#3）。

·\b匹配一个单词的开始。

·www\.匹配www.。

·）是第二个内层组的结束（图14-4中的#3）。这个组将捕获没有http：//但以www开始的一个URL。

·）是外层组的结束（图14-4中的#1）。此时，正则表达式将匹配以http：//、https：//或www开始的文本。

·\S*匹配一个或多个非空格字符。

这个表达式并不是非常安全的（例如，它无法匹配一个类似http：//#$*%＆*@*的无意义的URL），但是，它相对比较简单，并且可以成功地匹配类似http：//www.sawmac.com/missing/js/index.html的一个真正的URL。

提示：要查看一个字符串是否包含一个URL（在URL之前或之后没有其他内容），在正则表达式的开始使用^符号，在其末尾使用$字符，并且删除掉\b字符：^（（https?：\/\/）|（www\.））\S*$

14.2.5　匹配一个模式

14.2.1　节介绍的search（）方法是查看一个字符串是否包含一个特定的正则表达式模式的一种方式。match（）方法则是另一种方式。对一个字符串使用它，你不仅可以查看字符串中是否存在一个模式，而且可以捕获该模式，以便稍后在自己的脚本中使用它。例如，假设表单上有一个文本字段，供访问者添加对你的站点的评论。可能你想要看看评论是否包含一个URL，如果是这样的话，获取该URL以供进一步处理。

如下的代码使用match（）查找并捕获一个URL：

//get the contents of the text area

var text='my website is www.missingmanuals.com'；

//create a regular expression

var urlRegex=/（（\bhttps?：\/\/）|（\bwww\.））\S*/

//find a match for the regular expression in the string

var url=text.match（urlRegex）；

alert（url[0]）；//www.missingmanuals.com

首先，这段代码创建了一个变量，它包含了一个字符串，该字符串包含了URL www.missingmanuals.com。这个变量在这里只是用于测试的目的（以便你可以看到match（）方法做了些什么）。如果你实际上想要测试表单上的一个文本区域的内容，可以像下面这样使用这段代码：

var text=$（'#comments'）. val（）；

接下来，这段代码创建了匹配URL的一个正则表达式（参见14.2.4节了解正则表达式的更多细节）。最终，它在该字符串上运行match（）方法。match（）函数是一个字符串方法，因此，你可以从包含字符串的一个变量的名称开始，添加一个句点，后面跟着match（）。传递给match（）方法一个正则表达式以供匹配。

在上面的例子中，变量url保存了匹配的结果。如果在字符串中没有找到正则表达式模式，那么，结果是一个叫做null的特殊的JavaScript值。如果有匹配，脚本返回一个数组，数组的第一个值就是匹配的文本。例如，在这个例子中，变量url包含了一个数组，第一个数组元素是匹配上的文本。在这个例子中，url[0]包含了www.missingmanuals.com（参见本书2.8节对数组的介绍）。

注意：在JavaScript中，一个null值当做false一样对待，因此，你可以像下面这样测试看看match（）方法是否真正匹配了某些内容：

var url=text.match（urlRegex）；

if（！url）{

//no match

}else{

//match

}

匹配一个模式的每个实例

根据如何设置正则表达式，match（）方法以两种方式工作。在上面的示例中，该方法返回带有第一个匹配的文本的数组。因此，如果你有一个长字符串包含了多个URL，只有第一个URL会找到。然而，你也可以开启一个正则表达式的全局搜索属性，以便在一个字符串中搜索多个匹配。

我们可以通过创建正则表达式的时候在其末尾添加一个g，从而使其进行全局搜索（就像是14.2.3节所介绍的，添加一个i用来进行不区分大小写的搜索）：

var urlRegex=/（（\bhttps?：\/\/）|（\bwww\.））\S*/g

注意，g放在了结束/（该符号用来结束实际的模式）之外。这个正则表达式执行全局搜索，当和match（）方法一起使用时，它搜索字符串中的每一个匹配并且将返回所有匹配的文本的一个数组，这是在一个博客条目中找出每一个URL的一种很好的方法，例如，在长长的一段文本中找出一个单词的每一个实例。

我们可以使用一个全局搜索来重新编写14.2.5节的代码，如下所示：

//create a variable containing a string with a URL

var text='there are a lot of great websites like↵

www.missingmanuals.com and http：//www.oreilly.com'；

//create a regular expression with global search

var urlRegex=/（（\bhttps?：\/\/）|（\bwww\.））\S*/g

//find a match for the regular expression in the string

var url=text.match（urlRegex）；

alert（url[0]）；//www.missingmanuals.com

alert（url[1]）；//http：//www.oreilly.com

我们可以通过访问结果数组的length属性来确定匹配的数目：url.length。这个示例将返回数字2，因为我们在测试字符串中找到了两个URL。此外，我们通过使用数组的索引编号（参见2.8.2节来访问每一个匹配的字符串，因此，在这个例子中，url[0]是第一个匹配，而url[1]是第二个匹配。

14.2.6　替换文本

我们也可以使用正则表达式来替换一个字符串中的文本。例如，假设有一个字符串，它包含了类似10.28.2008格式的一个日期。然而，我们想要的日期格式是10/28/2008。replace（）方法可以做到这点。它的形式如下：

string.replace（regex，'replace'）；

replace（）方法接收两个参数：第一个参数是要在字符串中搜索的一个正则表达式；第二个参数是要替换这个正则表达式的一个字符串。因此，要将格式从10.28.2008修改为10/28/2008，我们可以这样做：

1 var date=’10.28.2008’；//a string

2 var replaceRegex=/\./g//a regular expression

3 var date=date.replace（replaceRegex，’/'）；//replace.with/

4 alert（date）；//10/28/2008

第1行创建了一个变量，并且在其中存储了字符串’10.28.2008’。在一个真实的程序中，这个字符串可能是一个表单的输入。第2行创建了正则表达式：/和/分别表示正则表达式模式的开始和结束；\.表示一个字面的句点；g表示一次全局替换，即句点的每次出现都会被替换。如果漏掉了g，只有第一次匹配的句点会被替换，并且，我们最终会得到’10/28.2008’。第3行执行实际的替换，将每个.更改为一个/，并且将结果存储回date变量。最终，新的格式的日期10/28/2008显示于一个警告框中。

高级用户诊断室

使用子模式替换文本

replace（）方法不仅可以使用一个字符串（例如/）来替换匹配的文本（例如，10.28.2008中的.），而且，它还可以记住一个正则表达式中的子模式，并且在替换文本的时候使用这些子模式。正如14.2.4节的“注意”部分所提到的，一个子模式是正则表达式中用圆括号括起来的任何部分。例如，正则表达式/Apr（il）?\b/中的（il）就是一个子模式。

前面内容展示了用replace（）方法将10.28.2008更改为10/28/2008。但是，如果我们还想要将10 28 2008或10-28-2008这样格式的日期转换为相同的10/28/2008格式，该怎么办呢？我们可以创建一个通用的模式来匹配任何这些格式，而不是编写多行JavaScript代码来替换句点、空格和连字符：

var date=’10-28-2008’；

var regex=/（[01]?\d）[-\/.]（[0123]?\d）

[-\/.]（\d{4}）/；

date=date.replace（regex，’$1/$2/$3'）；

这个示例使用14.2.4节介绍的正则表达式来匹配一个日期。注意圆括号中的模式组，例如（[01]?\d）。每个子模式匹配日期的一部分。replace（）方法记住了对这些子模式的匹配，并且可以使用它们作为替换的字符串的一部分。在这个例子中，替换字符串是’$1/$2/$3’。一个美元符号后面跟着一个数字，这个数字表示匹配的子模式之一。例如，$1匹配第一个子模式，即月份。因此，这个替换字符串可以翻译为：“将第1个子模式放在这里，后面跟着一个/，跟着第2个子模式匹配，后面跟着另一个/，然后是最后一个子模式。”

14.2.7　尝试正则表达式

在教程的example_regex.txt文件中，我们可以找到示例的正则表达式。此外，我们在testbed文件夹下可以找到一个名为regex_tester.html的文件。我们可以在浏览器中打开这个Web页面，并且动手尝试创建自己的正则表达式（如图14-5所示）。只要在“String to Search”框中输入想要搜索的字符串，然后，在框中输入一个正则表达式（在JavaScript中创建一个正则表达式的时候，漏掉在开始和结尾使用的/符号，只是输入搜索模式）。然后，我们可以选择要使用的方法，Search、Match、Replace或任何其他选项，例如，不区分大小写或全局搜索。单击Run按钮，并且看看你的正则表达式是如何工作的。

图　14-5：这个示例页面包含在教程文件中，它允许你使用不同的方法来测试正则表达式，例如Search或Match方法，并且可以尝试不区分大小写或全局搜索等不同选项

14.3　操作数字

数字是编程的一个重要部分。它们允许我们执行类似这样的任务：计算总的销售成本，确定两点之间的距离，或者通过产生1～6之间的一个随机数来模拟掷色子。JavaScript有很多不同的方法来操作数字。

14.3.1　将字符串更改为数字

当我们创建一个变量的时候，可以像下面这样在其中存储数字：

var a=3.25；

然而，有时候，数字实际上是一个字符串。例如，如果我们使用prompt（）方法（参见2.7节）来获取访问者的输入，即便某人输入了3.25，我们最终还是得到包含了数字的一个字符串。换句话说，结果是’3.25’（一个字符串）而不是3.25（一个数字）。通常，这种方法不会导致问题，因为JavaScript解释器在看到需要一个数字的时候，通常会将一个字符串转换为数字。例如：

var a=’3’；

var b=’4’；

alert（a*b）；//12

在这个例子中，即便变量a和b都是字符串，JavaScript解释器也会将它们都转换为数字，以执行乘法运算（3×4），然后返回结果12。然而，当你使用+操作符的时候，JavaScript解释器不会进行这一转换，它最终得到了有些奇怪的结果：

var a=’3’；

var b=’4’；

alert（a+b）；//34

在这个例子中，a和b都是字符串；+操作符不仅执行数学加法，它还会将两个字符串组合（连接）起来（参见2.5.2节）。因此，在这个例子中，将3和4相加，得到的不是7，最终得到的是将两个字符串连接在一起的结果：34。

当我们需要将一个字符串转换为一个数字的时候，JavaScript提供了如下几种方法：

·Number（）将传递给它的任何字符串转换为一个数字，例如：

var a=’3’；

a=Number（a）；//a is now the number 3

因此，将两个包含数字的字符串相加所导致的问题，可以通过如下方法修正：

var a=’3’；

var b=’4’；

var total=Number（a）+Number（b）；//7

一种更快的方法是使用+操作符，它和Number（）做相同的事情。只要在包含字符串的一个变量的前面添加一个+，并且JavaScript解释器会将该字符串转换为一个数字：

var a=’3’；

var b=’4’；

var total=+a++b//7

这两种方法的缺点是，如果字符串包含了数字以外的任何内容，一个句点，或者是字符串开始处的一个+或-符号，我们最终会得到一个非数字值，或者是JavaScript值NaN，后者表示“不是一个数字”（参见后面内容的介绍）。

·parseInt（）也试图将一个字符串转换为一个数字。然而，和Number（）不同，parseInt（）甚至尝试将带有字母的一个字符串转换为一个数字，只要该字符串是以数字开始的。当我们得到一个类似’20 years'的字符串作为对年龄问题的一个回答的时候，这个函数很方便：

var age=’20 years'；

age=parseInt（age，10）；//20

parseInt（）方法在字符串的开始查找一个数字或一个+或-符号，直到它遇到一个非数字。因此，在上面的例子中，它返回数字20并且忽略掉字符串的其他部分'years'。

注意：你可能会问，parseInt（age，10）；中的10是做什么用的。JavaScript可以处理八进制数字（它是以8为基数，包含0～7的不同数字；和十进制数不同，后者是以10为基数，包含0～9的不同数字）；当我们给parseInt（）函数添加10的时候，我们告诉JavaScript解释器，将任何的输入都当做十进制数字对待。通过这种方法，JavaScript可以将提示窗口或表单字段中‘08’这样的一个字符串正确地按照十进制来解释。例如，在这段代码中，age可能等于0：

var age=’08 years'；

age=parseInt（age）；

然而，在下面的代码中，变量age可能保存了值8：

var age=’08 years'；

age=parseInt（age，10）；

换句话说，当使用parseInt（）方法的时候，总是添加一个10。

·parseFloat（）类似于parseInt（），但是，当一个字符串可能包含一个十进制小数的时候使用它。例如，如果有一个类似’4.5 acres'的字符串，我们可以使用parse Float（）来获取包含小数位数的整个值：

var space=’4.5 acres'；

space=parseFloat（space）；//4.5

如果在上面的例子中使用parseInt（），最终得到的只是数字4，因为parseInt（）只是尝试获取整数。

无论使用上面介绍的哪个方法，都需要根据情况而定：如果目标是将两个数相加，但是它们都是字符串，那么使用Number（）或+操作符。然而，如果想要从可能包含字母的一个字符串中提取一个数字，例如，’200px'或’1.5em'，那么使用parseInt（）来捕获整数（例如，200），或者使用parseFloat（）来捕获小数（例如，1.5）。

14.3.2　测试数字

当使用JavaScript操作用户输入的时候，我们常常需要验证访问者提供的信息是正确的类型。例如，如果你问某人的出生年份，你想要确定他提供了一个数字。类似地，当你执行一次数学计算的时候，如果用来计算的是日期而非数字，那么脚本会失效。

要验证一个字符串是数字，可以使用isNaN（）方法。该方法接收一个字符串作为参数，并且测试该字符串是否是一个数字。如果该字符串包含了一个+或-（表示正数和负数）后面跟着一个数字和一个可选的小数数值以外的任何内容，它认为这“不是一个数字”，因此，字符串’-23.25’是一个数字，但’24 pixels'不是一个数字。这个方法返回true（该字符串不是一个数字）或者false（字符串是一个数字）。我们可以使用isNaN（）作为一个条件语句的一部分，如下所示：

var x=’10’；//is a number

if（isNaN（x））{

//won't run because x IS a number

}else{

//will run because x is a number

}

14.3.3　对数字取整

JavaScript提供了一种方法，对一个小数值取整，例如，将4.5取整为5。当我们要执行一个计算，而它必须得到一个整数结果的时候，取整操作很方便。例如，假设我们要使用JavaScript来根据浏览器窗口的高度动态地设置页面上的一个＜div＞标签的像素高度。换句话说，使用窗口的高度来计算出＜div＞的高度。所进行的任何计算都可能导致一个小数值（例如，300.25），但是，由于没有.25像素这样的位置，我们需要将最终的计算结果舍入到最近的整数（例如，300）。

我们可以使用Math对象的round（）方法来对一个数字取整。其语法看上去有点不同寻常：

Math.round（number）

我们将一个数字（或者包含了一个数字的变量）传递给round（）方法，并且它返回一个整数。如果最初的数值有一个小于.5的小数部分，这个数字将会舍掉；如果小数部分是.5或更大，它将进位。例如，4.4将会向下舍入为4，而4.5将向上舍入为5。

var decimalNum=10.25；

var roundedNum=Math.round（decimalNum）；//10

注意：JavaScript还提供了另外两个方法来舍入数字：Math.ceil（）和Math.floor（）。可以像Math.round（）一样使用它们，Math.ceil（）总是将数字向上舍入（例如，Math.ceil（4.0001）返回5），而Math.floor（）总是将数字向下舍入，Math.floor（4.99999）返回4。要搞清楚这两个方法，记住，天花板是上面，而地板是下面。

14.3.4　格式化货币值

在计算产品价值或购物车总价的时候，我们通常会包括成本，带上两个小数位数，例如9.99。但是，即便货币值是一个整数，它通常也带有两个0，例如10.00。并且，像8.9这样的货币值也写成8.90。遗憾的是，JavaScript不会以那样的方式来看待数字：它舍去0（例如，用10而不是10.00，用8.9而不是8.90）。

幸运的是，有一个用于数字的方法toFixed（），它允许我们将一个数字转换为一个字符串，以匹配想要保留的小数位数。要使用这个函数，在数字的后面添加一个句点（或者在包含一个数字的变量名的后面），后面跟着toFixed（2）：

var cost=10；

var printCost=’$’+cost.toFixed（2）；//$10.00

传递给toFixed（）的数字决定了放置多少个小数位数。对于货币，使用2最终会得到类似10.00或9.90的结果；如果使用3，将会得到3个小数位数，例如10.000或9.900。

如果数字一开始所带有的小数位数比你指定的位数要多，数字将会舍入到指定的小数位数。例如：

var cost=10.289；

var printCost=’$’+cost.toFixed（2）；//$10.29

在这个例子中，10.289向上舍入为10.29。

注意：toFixed（）方法只对数字有效。如果你使用一个字符串，最终将得到一个错误：

var cost=’10’；//a string

var printCost=’$’+cost.toFixed（2）；//error

要解决这个问题，需要像14.3.1节介绍的那样，将该字符串转换为一个数字：

var cost=’10’；//a string

cost=+cost；//or cost=Number（cost）；

var printCost=’$’+cost.toFixed（2）；//$10.00

14.3.5　创建一个随机数

随机数可以帮助程序增加多样性。例如，假设有一个问题的数组用于谜题问答程序（如3.5节教程所介绍的那个程序）。我们可以随机地从数组中选择一个问题，而不是每次按照同样的顺序问同样的问题。或者，我们可以使用JavaScript随机地从数组选择一个图形文件的名称，以便每次页面载入的时候显示不同的图像。这些任务都需要一个随机数。

JavaScript提供了Math.random（）方法来创建随机数。该方法返回0～1之间的一个随机产生的数字（例如，.9716907176080688或.10345038010895868）。尽管你不太可能需要像这样的数字，但是，我们可以使用一些简单的数学运算来产生从0到任何数字之间的一个整数。例如，要产生0到9之间的整数，可以使用如下的代码：

Math.floor（Math.random（）*10）；

这段代码分为两个部分。Math.floor（）方法内的部分，Math.random（）*10，产生了0～10之间的一个随机数。它将产生类似4.190788392268892这样的数字，由于随机数在0～10之间，它不会是10。要得到一个整数，随机的结果传递给Math.floor（）方法，它将任何小数向下取舍到最近的整数，因此，3.4448588848变成了3，而.1111939498984变成了0。

如果想要获取1和另一个数字之间的一个随机数，只要将random（）方法和最大的数字相乘，并且使用Math.ceil（）方法（它将一个数字向上取舍到最近的整数）。例如，如果想要模拟一个掷色子程序，得到1～6之间的一个数字：

var roll=Math.ceil（Math.random（）*6）；//1，2，3，4，5 or 6

随机选择一个数组元素

我们可以使用Math.random（）方法随机地从一个数组中选取一项。正如2.8.2节所述，可以使用一个索引编号来访问数组中的每一项。使用索引值0来访问数组中的第一项，使用数组中的项目数减去1这样的一个索引值，来访问数组中的最后一项。使用Math.random（）方法，就可以很容易地随机选取一个数组项：

var people=['Ron'，'Sally'，'Tricia'，'Bob']；//create an array

var random=Math.floor（Math.random（）*people.length）；

var rndPerson=people[random]；//

这段代码的第1行创建了带有4个名字的一个数组。第2行做了两件事情：首先，它产生了0到数组中的项目数之间（people.length）的一个随机数，在这个例子中，是0～4之间的一个数字。然后，它使用Math.floor（）方法向下舍入到最近的整数，由此，它产生了数字0、1、2和3。最后，它使用这个数字来访问数组中的一个元素，并且将其存储到名为rndPerson的一个变量中。

用于选择随机数的函数

函数是创建有用的、可复用的代码（参见3.4节）的一种好办法。如果你经常使用随机数，可能想要通过一个简单的函数来帮助你在任意两个数字之间选择随机数，例如，1～6之间的一个数字，或者1～100甚至1000之间的一个数字。如下的函数使用两个参数来调用：第一个参数是最小的可能值（例如1），第二个参数是最大的可能值（例如，6）：

function rndNum（from, to）{

return Math.floor（（Math.random（）*（to-from+1））+from）；

}

要使用这个函数，将其添加到你的Web页面上（参加3.4节的介绍），然后像下面这样调用它：

var dieRoll=rndNum（1，6）；//get a number between 1 and 6

14.4　日期和时间

如果想要记录当前的日期和时间，可以使用JavaScript的Date对象。这个特殊的JavaScript对象允许你确定年份、月份、星期几、小时等。要使用它，创建一个变量并在其中存储一个新的Date对象，如下所示：

var now=new Date（）；

new Date（）命令创建了一个Date对象，它包含了当前的日期和时间。一旦创建了，我们可以使用各种与日期相关的方法（参见表14-3）来访问时间和日期信息的不同部分。例如，要获取当前的年份，使用getFullYear（）方法，如下所示：

var now=new Date（）；

var year=now.getFullYear（）；

注意：new Date（）获取的当前时间和日期是由访问者的计算机确定的。换句话说，如果某人的计算机时钟设置不对，那么日期和时间也不准确。

14.4.1　获取月份

要从Date对象获取月份，使用getMonth（）方法，它返回月份的数字：

var now=new Date（）；

var month=now.getMonth（）；

然而，这个方法返回的数字对人类来说没有意义（我们认为1表示1月），会少一个数字。例如，0表示1月，1表示2月，依此类推。如果想要获取一个数字能表示我们所认为的月份，只要给它加1就行了，如下所示：

var now=new Date（）；

var month=now.getMonth（）+1；//matches the real month

没有内置的JavaScript命令能够告诉我们月份的名称。幸运的是，当我们想要确定月份的实际名称的时候，JavaScript奇怪的计数月份的方式却很方便。我们可以首先创建月份名称的一个数字，然后，使用该月份的索引编号来访问该名称，从而实现这一功能：

var months=['January'，'February'，'March'，'April'，'May'，

'June'，'July'，'August'，'September'，

'October'，'November'，'December']；

var now=new Date（）；

var month=months[now.getMonth（）]；

第1行创建了一个数组，它包含了所有12个月份的名称，按照它们出现的顺序（从January到December）。记住，我们使用索引编号来访问数组项，并且，数组是从0开始编号的（参见2.8.2节）。因此，要访问数组months的第一项，可以使用months[0]。因此，通过使用getMonth（）方法，我们可以获取一个数字，以用作months数组的索引并由此获取该月份的名称。

14.4.2　获取星期几

getDay（）方法获取表示星期几的数字。并且，和getMonth（）方法一样，JavaScript解释器返回的数字总是比我们实际期望的表示星期几的数字要少1：0表示星期日，即一周的第一天；而6表示星期六。由于星期几的名称通常对于访问者来说更有用，我们可以使用一个数字来存储星期几的名称，并且使用getDay（）方法来访问数字中的特定的一天，如下所示：

var days=['Sunday'，'Monday'，'Tuesday'，'Wednesday'，

'Thursday'，'Friday'，'Saturday']；

var now=new Date（）；

var dayOfWeek=days[now.getDay（）]；

14.4.3　获取时间

Date对象还包括了当前时间，因此，我们可以在Web页面上显示当前时间，或者使用时间来判断访问者是在上午还是下午浏览页面。然后，我们可以根据该信息做些事情，例如，显示太阳当空或月亮当空的一幅背景图像。

高级用户诊断室

幕后的Date对象

JavaScript允许我们访问Date对象的特定元素，例如，年份或者日期。然而，JavaScript解释器实际上认为日期是一个数字，表示从1970年1月1日开始所经过的毫秒数。例如，2012年2月1日，对于JavaScript解释器来说，实际上是131328083200000。

这不是一个笑话：对JavaScript来说，时间的开始就是1970年1月1日。这个日期是由创建UNIX操作系统的程序员在20世纪70年代随意选取的（叫做Unix epoch），因为他们都同意以一种方式来记录时间。从那时候起，这种记录日期的方式在很多编程语言和平台中变得通用起来。无论何时，当你使用诸如getFullYear（）的一个Date方法，JavaScript解释器执行数学计算来搞清楚它是哪一年（根据它从1970年1月1日算起经过了多少毫秒）。如果你想要看到一个特定日期的毫秒数字，可以使用getTime（）方法：

var sometime=new Date（）；

var msElapsed=sometime.getTime（）；

以毫秒记录日期和时间，使得很容易计算日期之间的差距。例如，我们可以这样确定到下一个元旦的时间，首先获取从1/1/1970到下一个元旦的毫秒数，再减去从1/1/1970到今天的毫秒数：

//milliseconds from 1/1/1970 to today var today=new Date（）；

//milliseconds from 1/1/1970 to next new year

var nextYear=new Date（2013，0，1）；

//calculate milliseconds from today to next year

var timeDiff=nextYear-today；

将两个日期相减，得到的结果是二者之间相差的毫秒数。如果你想要将其转换为有用的内容，只需要用这个数字除以一天中的毫秒数（来确定有多少天），或者除以一个小时的毫秒数（来确定有多少个小时），依次类推：

var second=1000；//1000 milliseconds in

a second

var minute=60*second；//60 seconds in

a minute

number of days

var hour=60*minute；//60 minutes in an

hour

var day=24*hour；//24 hours in a day

在这个例子中，你可能注意到了一种创建日期的不同方法：new Date（2009，0，1）。可以从14.4.4节了解这种方法的更多内容。

var totalDays=timeDiff/day；//total

我们可以使用getHours（）、getMinutes（）和getSeconds（）方法来获取小时、分钟和秒。因此，要在Web页面上显示时间，可以在想要让时间出现的位置的HTML中添加如下代码：

var now=new Date（）；

var hours=now.getHours（）；

var minutes=now.getMinutes（）；

var seconds=now.getSeconds（）；

document.write（hours+"："+minutes+"："+seconds）；

这段代码产生6：35：56这样的输出，表示上午6点35分56秒。然而，它也可能会产生你可能不喜欢的输出，例如，18：4：9表示下午6点4分9秒。一个问题是，对于阅读本书的大多数人来说，除非是军人，他们是不使用24小时时钟的。他们不知道18表示下午6点。甚至一个更大的问题是，时间使用以两位数表示分钟和秒钟的格式（即便分钟和秒钟都少于10），例如，6：04：09。幸运的是，调整上面的脚本来满足这些需求并不难。

将时间更改为a.m.和p.m.

要将时间从24小时时钟修改为12小时时钟，我们需要做几件事情。首先，我们需要确定时间是在上午（这样，我们可以在时间的后面添加'am'）还是在下午（添加'pm'）。其次，我们需要将大于12的任何小时数都转换为等值的12小时时钟（例如，将14改为2.p.m）。

下面的代码完成这一任务：

1 var now=new Date（）；

2 var hour=now.getHours（）；

3 if（hour＜12）{

meridiem='am'；

5}else{

4

6

meridiem='pm'；

7}

8 hour=hour%12；

9 if（hour==0）{

10

hour=12；

11}

12 hour=hour+’’+meridiem；

注意：左边的那列数字只是为了方便你阅读下面的讨论而为行添加的编号。在自己的代码中，不要输入这些行号。

第1行和第2行获取当前的日期和时间，并且将当前的小时数存储在一个名为hour的变量中。第3～7行确定这个小时数是在下午还是上午，如果hour小于12（午夜以后的hour为0），那么，是在上午（a.m.），否则是在下午（p.m.）。

第8行引入了一种叫做模除的数学运算，它用一个百分号表示（%）。它返回除法运算的余数。例如，5除以2，得到结果为2，还有1个余数。换句话说，5%2得1。因此，在这个例子中，如果hour是18，18%12得到6（18除以12，商为1，余数为6）。18就是6 p.m.，这是我们想要的时间。如果第一个数字小于除数（例如，8除以12），那么，结果是最初的数字。例如，8%12返回8。换句话说，模除操作不会改变中午之前的小时数。

第9～11行考虑到模除操作输出的两种可能。如果hour是12（正午）或0（午夜），那么模除操作返回0。在这种情况下，hour只要设置为12，表示12 p.m.或12 a.m.。最后，第12行将重新格式化的hour和一个空格以及“am”或“pm”组合起来，这样，结果就显示为“6 am”或“6 pm”。

填充一位数

正如前面所讨论的，当分钟数或秒钟值小于10的时候，我们最终可能得到较为奇怪的输出，例如，7：3：2 p.m。要将这种输出修改为更为常见的7：03：02 p.m.，我们需要在单个数位的前面添加一个0。使用基本的条件语句，这很容易做到：

1 var minutes=now.getMinutes（）；

2 if（minutes＜10）{

3 minutes=’0’+minutes；

4}

第1行获取当前时间的minutes，在这个例子中可能是33或3。第2行直接检查这个数值是否小于10，如果是，意味着分钟数是一个一位数，在其前面需要一个0。第3行有点技巧，因为你通常不会在一个数字前面加一个0，0+2等于2，而不是等于02。然而，我们可以以这种方式组合字符串，因此，’0’+minutes意味着将字符串’0’和变量minutes中的值组合起来。正如14.3.1节所介绍的，当我们将一个字符串和一个数字相加的时候，JavaScript解释器会将数字转换为一个字符串，因此，我们最终得到了诸如’08’的一个字符串。

我们可以将这些部分组合到一起，以创建一个简单的函数，来按照类似7：32：04 p.m.或4：02：34 a.m.，甚至是7：23 p.m.这样省略掉秒钟的时间格式，来输出时间：

function printTime（secs）{

var sep=’：’；//seperator character

var hours, minutes, seconds, time；

var now=new Date（）；

hours=now.getHours（）；

if（hours＜12）{

meridiem='am'；

}else{

meridiem='pm'；

}

hours=hours%12；

if（hours==0）{

hours=12；

}

time=hours；

minutes=now.getMinutes（）；

if（minutes＜10）{

minutes=’0’+minutes；

}

time+=sep+minutes；

if（secs）{

seconds=now.getSeconds（）；

if（seconds＜10）{

seconds=’0’+seconds；

}

time+=sep+seconds；

}

return time+’’+meridiem；

}

你将会发现，这个函数位于教程chapter14文件夹中的printTime.js文件中。你可以通过在浏览器中打开文件time.html（同一文件夹下），来看看这个函数的应用。要使用这个函数，要么将printTime.js文件添加到一个Web页面，或者将函数复制到一个Web页面或另一个外部JavaScript文件。要获取时间，只要像这样调用该函数：printTime（），或者，如果你想要秒钟也显示出来，这样调用printTime（true）。该函数将以相应的格式返回包含了当前时间的一个字符串。

14.4.4　创建今天以外的一个日期

到目前为止，我们看到了如何使用Date（）来捕获访问者的计算机上的当前日期和时间。但是，如果想要创建表示下一个感恩节或下一个元旦的一个Date对象，该怎么办呢？JavaScript允许我们以几种方法来创建表示今天之外的一个日期。如果你想要在两个日期之间进行计算，那么，你可能想要这么做，例如，计算“到新年还有多少天”（参见14.4.3节）。当使用Date（）方法的时候，你可以指定未来或过去的一个日期和时间。基本的格式如下：

new Date（year, month, day, hour, minutes, seconds, milliseconds）；

例如，要创建一个日期表示2012年元旦正午，可以这样编写代码：

var ny2012=new Date（2012，0，1，12，0，0，0）；

这段代码可以解释为，“创建一个新的Date对象表示2012年1月1日中午12点0分0秒0毫秒”。你必须至少提供年份和日期，到那时，如果不需要指定一个具体时间的话，可以省略掉毫秒、秒和分钟等。例如，只是创建一个日期对象表示2012年1月1日，我们可以这样写：

var ny2012=new Date（2012，0，1）；

注意：记住，正如14.4.1节所述，JavaScript使用0表示1月，使用1表示2月，依此类推。

创建距离今天一周的一个日期

正如14.3.3节所介绍的，JavaScript解释器实际上把日期当做表示从1970年1月1日经过的毫秒数的数值。创建一个日期的另一种方式，是传递一个值，表示到该日期所经过的毫秒数：

new Date（milliseconds）；

因此，创建表示2012年1月1日的一个日期的另一种方法是：

var ny2012=new Date（1325404800000）；

当然，我们是人类而不是计算器，因此，你可能不会这样来考虑日期。然而，当你要创建距离某个日期一定时间的一个新的日期的时候，毫秒表示可能很方便。例如，当使用JavaScript设置一个cookie的时候，我们需要指定从访问者的浏览器删除该cookie的一个日期。为了确保这个日期在一星期以后消失，我们需要指定距离今天一周的一个日期：

要创建从现在开始一周以后的一个日期，可以像下面这样做：

var now=new Date（）；//today

var nowMS=now.getTime（）；//get#milliseconds for today

var week=1000*60*60*24*7；//milliseconds in one week

var oneWeekFromNow=new Date（nowMS+week）；

第1行代码将当前日期和时间存储到一个名为now的变量中。接下来，getTime（）方法提取了从1970年1月1日到今天所经过的毫秒数。第3行计算了一周中的总的毫秒数（1000毫秒×60秒×60分钟×24小时×7天）。最后，这段代码通过将一周的毫秒数和今天相加，得到了一个新的日期。

14.5　综合应用

到目前为止，我们在本书中已经见到JavaScript可以完成的很多任务：表单验证、图像翻转、照片集，像标签页面板和折叠面板这样的用户界面改进等。但是，你可能会问，如何将这些组合到一起用于自己的站点呢？毕竟，一旦我们开始使用JavaScript，可能想要使用它来改进站点的每个页面。下面是关于如何在站点上使用多个脚本的一些技巧。

使用外部JavaScript文件

正如1.2节所介绍的，外部JavaScript文件是共享Web页面中相同的JavaScript代码的一种高效方式。外部文件使得更改JavaScript更为容易，如果需要扩展（或修改）JavaScript代码，只需要编辑一个文件。此外，当下载一个外部JavaScript文件的时候，它存储在浏览器的缓存中，因此，它不需要再次下载，这使得Web页面让访问者觉得具有更好的响应性而且载入更快。

在jQuery这样的JavaScript库的例子中，外部JavaScript文件是必需的，毕竟，如果把真正的jQuery JavaScript代码放入到每个页面中，我们的Web页面将会毫无必要地变大并且难以维护。此外，jQuery插件作为外部文件提供，如果要使用它们的话，需要将其链接到一个Web页面。链接到一个外部JavaScript文件很容易，如下所示：

＜script src="js/ui.tabs.js"＞＜/script＞

把自己的JavaScript代码放入到外部JavaScript文件中，这也可以使代码更具复用性，并且站点会给人更快的感觉，但是，只有当我们真正在Web页面中共享这些代码的时候才会这样。例如，对于9.4节所创建的表单验证脚本来说，把用于创建验证规则和错误消息的代码放入到一个外部文件中是没有意义的，因为所有这些规则和错误消息都是特定于页面上的表单元素的，并且，在拥有不同表单字段的一个表单上就无法工作了。在这个例子中，最好只是在Web页面自身中使用JavaScript验证表单。

然而，我们在9.4节所学习的Validation插件可以用于任何表单，因此，将它放入到一个单独的文件中是有意义的。对于将要在多个页面中使用的任何代码的操作都是一样的。例如，在9.3.1节，我们学习了如何使用JavaScript来聚焦到表单上的第一个字段，这是我们可能想要对每个表单做的事情。同样，9.2.3节的“常见问题：阻止多次提交”部分介绍了防止访问者多次单击提交按钮（从而多次提交表单数据）所需的JavaScript，这也是对于任何表单页面都有用的。因此，我们可能想要把这两个脚本组合到一个名字类似于forms.js的单独的外部文件中。JavaScript代码可能如下所示：

1$（document）.ready（function（）{

2//focus first text field

3$（"：text"）[0].focus（）；

4

5//disable submit button on submit

6$（'form'）.submit（function（）{

7 var subButton=$（this）.find（'：submit'）；

8 subButton.attr（'disabled'，true）；

9 subButton.val（'……sending information……'）；

10}）；

11}）；//end ready

注意，由于这段代码依赖于jQuery，因此必须将其包含到$（document）.ready（）函数中（从第1～10行）。实际上，每个依赖于jQuery的外部文件都必须从上面的第1行开始而到第10行结束。

注意：jQuery可以处理多个$（document）.ready（）函数而没有任何问题。例如，我们可以有几个外部JavaScript文件来对页面做各种事情，并且每个文件都可以拥有一个$（document）.ready（）函数，并且我们可以在只是出现在该页面上的＜script＞中包含一个$（document）.ready（）函数。这对jQuery来说没问题。

在多个页面中使用同样的脚本，需要你自己做一些规划。例如，第3行把光标放在一个页面上的第一个文本字段中。在大多数情况下，这是有意义的，我们希望聚焦到第一个字段上，以便访问者可以开始填充表单。然而，如果这个页面有多个表单，这段代码可能不会像我们预期的那样工作。

例如，如果在页面的顶部有一个查找框并且还有一个单独的表单用来提交一个产品订单，第3行中的代码将会把焦点放置到“Search”框中，而不是订单表单的第一个文本字段中。在这个例子中，我们需要进一步考虑问题，并且使用一种方法来确保当页面载入的时候正确的文本字段得到焦点。这里有两种可能的解决方案：

·为页面载入时想要聚焦的字段添加一个类名。例如，假设想要给聚焦的字段添加类名focus，代码如下所示：

＜input type="text"class="focus"name="firstName"＞

然后，可以使用如下这行JavaScript来确保聚焦到该字段：

$（'.focus'）.focus（）；

要使用这段代码，我们只需要确保给每个表单页面上的一个文本字段添加了focus类，并且确保把包含这段代码的外部JavaScript文件链接这些表单页面中的每一个。

·可以通过给＜form＞标签自身添加一个类名来达到同样的效果，只需要使用如下的JavaScript：

$（'.focus：text'）[0].focus（）；

这行代码自动地聚焦到类名为focus的一个表单的第一个文本字段。这种方法的好处是，第一个文本字段总是会获得焦点，因此，如果重新组织表单（例如，在开始的位置添加一些更多的文本字段），我们知道获得焦点的是第一个文本字段而不是页面下面的某个其他字段（类名为focus）。

一旦开始使用JavaScript，我们可能最终会在所有（或者几乎所有）的Web页面上使用几段脚本。例如，我们可能会有一些翻滚图像（参见7.1节），并且使用JavaScript来确保到站点之外的链接在一个新窗口中打开（参见8.2节）。在这种情况下，创建带有要在站点中共享的所有脚本的一个外部JavaScript文件是有用的，我们可以把这个文件命名为site_scripts.js或者直接叫做site.js。

注意：jQuery有一种内置的方法来保证我们不会产生不期望的JavaScript错误。如果试图在某些不存在的内容上执行一个操作，JavaScript通常会产生一个错误，例如，如果试图从没有一个文本字段的页面来选取该文本字段。幸运的是，jQuery会忽略这类错误。

14.6　编写更高效的JavaScript

编程包括很多工作。程序员总是寻求做事情更快并且使用更少的代码的方法。然而，有很多技巧和方法，下面的技术对于使用JavaScript和jQuery特别有用。

14.6.1　优先使用变量

程序员要学习的重要的一课，就是如何从脚本中提取细节，从而使脚本更为灵活和容易更新。例如，假设当访问者单击一段文本的时候，我们想要将其颜色修改为橙色。我们可以通过jQuery使用css（）函数（参见6.1节）来做到这点，如下所示：

$（'p'）.click（function（）{

$（this）.css（'color'，’#F60'）；

}）；

在这个例子中，橙色（#F60）是直接编码到这个步骤中。假设我们对其他的步骤引用同样的代码（可能是当访问者把鼠标光标移动到一个表格单元格上的时候添加一个背景颜色）。我们可能也要试图在那些步骤中编写为#F60。更好的一种方法是在脚本的开始把颜色放入一个变量中，然后在整个脚本中使用该变量。

1$（document）.ready（function（）{

2 var hColor=’#F60’；

3$（'p'）.click（function（）{

4$（this）.css（'color'，hColor）；

5}）；

6$（'td'）.hover（

7 function（）{

8$（this）.css（'backgroundColor'，hColor）；

9}，

10 function（）{

11$（this）.css（'backgroundColor'，'transparent'）；

12}

13）；

14}）；//end ready（）

在这个例子中，变量hColor现在存储了一个十六进制的颜色值，这个变量既用于＜p＞标签的click事件，也用于＜td＞标签的hover事件。如果随后我们确定不使用橙色，可以修改存储在变量中的值，例如，var hColor=’#F33’，现在，脚本将使用另一种颜色。

我们可以通过解耦＜p＞标签和＜td＞使用的颜色之间的关联，来使得上面的代码更加灵活。当前，它们都设置为相同的颜色，但是，如果想要使得最终能够为每个标签指定不同的颜色，可以为代码添加另一个变量：

1$（document）.ready（function（）{

2 var pColor=’#F60’；

3 var tdColor=pColor；

4$（'p'）.click（function（）{

5$（this）.css（'color'，pColor）；

6}）；

7$（'td'）.hover（

8 function（）{

9$（this）.css（'backgroundColor'，tdColor）；

10}，

11 function（）{

12$（this）.css（'backgroundColor'，'transparent'）；

13}

14）；

15}）；//end ready（）

现在，click和hover事件使用的还是同样的颜色#F60（因为在代码的第3行，变量tdColor的值设置为pColor的值）。然而，如果随后确定想要表格单元格具有不同的颜色，只要把第3行代码修改为如下所示：

var tdColor=’#FF3’；

在编写一个JavaScript程序的时候，找出在代码中显式命名的值并且将其转换为变量。可能的候选者是颜色、字体、宽度、高度、时间（例如1000毫秒）、文件名（例如图像文件）、消息文本（例如警告消息或确认消息），以及文件的路径（例如一个链接或一幅图像的路径）。例如：

var highlightColor=’#33A'；

var upArrow='ua.png'；

var downArrow='da.png'；

var imagePath=’/images/’；

var delay=1000；

把这些变量的定义放在脚本的开始处（或者，如果使用jQuery的话，直接放在.ready（）函数中）。

提示：把计划在页面上显示的文本放置到一个变量中，这特别有用。例如，像“Please supply a valid email address”这样的错误消息，或者像“Thank you for supplying your mailing information”这样的确认消息，都可以是变量。当这些消息作为变量在脚本的开始处组织到一起的时候，随后很容易编辑它们（并且，如果需要接触国际性用户的时候，可以很容易翻译它们）。

14.6.2　三元操作符

根据某种条件来设置一个变量的值，这是一种常见的编程任务。例如，假设想要设置一个变量，其中包含了用户的登录状态的文本。在脚本中，有一个名为login的变量，它包含了一个布尔值，如果用户登录了，这个值为true，否则，这个值为false。下面是针对该情况创建一个新变量的一种方法：

var status；

if（login）{

status='Logged in'；

}else{

status='Not logged in'；

}

在这个例子中，一条基本的条件语句（参见3.1.1节）根据用户是否登录来设置名为status的一个变量的值。JavaScript提供了这种常见过程的一个快捷方式，叫做三元操作符。三元操作符提供了一行命令创建一个简单的条件语句的方法。三元操作符的基本格式如下：

（condition）?A：B

根据这个条件的结果，要么返回A（如果条件为true），要么返回B（如果条件为false）。?位于true结果的前面，而：位于false的结果的前面。例如，上面的代码可以重新写为如下的样子：

var status=（login）?'Logged in'：'Not logged in'；

曾经的6行代码变成了一行简单的代码。图14-6示意了这段代码是如何工作的。

图　14-6　三元操作符允许我们编写只有一行的条件语句。在这个例子中，1是条件。如果条件为true，将返回紧跟在?后面的代码（2）；如果条件为false，将返回紧跟在：后的代码（3）

三元操作符只是一个简写方式，我们不一定必须使用它，并且，一些程序员认为它太过精简而不好理解，并且宁愿使用易于阅读的if/else语句。此外，三元操作符最好的用处是根据一个条件来设置一个变量的值。它并不是适用于所有类型的条件语句；例如，对于根据一个特定条件有多行代码执行的多行语句来说，就不能使用三元操作符。但是，即便不使用三元操作符，认识它们是如何工作的也可以帮助我们理解其他人的程序，因为我们可能会经常碰到它。

14.6.3　switch语句

编写条件语句的方法不止一种。尽管根据一个条件的结果给一个变量赋值的时候三元操作符很好用，但是switch语句是根据一个单个变量的值编写一系列if/else语句的一种更为紧凑的方法。

例如，假设我们要求站点的访问者在一个表单字段中输入他们喜爱的颜色，然后，根据他们提交的颜色而显示一条不同的信息。如下是使用典型的条件语句编写的代码：

if（favoriteColor=='blue'）{

message='Blue is a cool color.’；

}else if（favoriteColor=='red'）{

message='Red is a warm color.’；

}else if（favoriteColor=='green'）{

message='Green is the color of the leaves.’；

}else{

message='What kind of favorite color is that?’；

}

注意，这段代码中有那么多的favoriteColor=='some value'。实际上，'favoriteColor==’在仅仅9行代码中出现了3次。如果我们所做的只是重复地测试一个变量的值，那么，switch语句提供了一种更为优雅（并且容易阅读）的解决方案。一条switch语句的基本结构如图14-2所示。

图　14-7　switch语句是根据一个变量的值来执行不同操作的一种紧凑方式。不要忘掉了每个case末尾的break；语句，这条break；语句使得我们能够退出switch语句

switch语句的第1行从关键字switch开始，接着是圆括号中的一个变量名，后面跟着一个开始的花括号。基本上，这段代码是说“让我们获取这个变量的值，看看它是否匹配几个其他值中的一个”。每次测试叫做一个case，并且一条switch语句有一个或多个case。例如，在图14-2中，有3个case，编号为1～3。一个case的基本结构如下所示：

case value1：

//do something

break；

关键字case表示这是一个case的开始，后面跟着某个值，然后是一个冒号。这行代码是较长的if（variable=='value1'）的简写方式。这个值可能是一个数字、字符串或者布尔值（或者包含了一个数值、字符串或布尔值的变量），因此，如果想要测试该变量是否等于37，那么，这个case如下所示：

case 37：

//do something

break；

要测试该变量为true或false，可以这样编写代码：

case true：

//do something

break；

在第1行代码之后，我们添加了如果变量匹配了测试的case值时想要执行的语句。最后，添加了一条break；语句。这个步骤很重要，break；语句退出了switch语句。如果漏掉了break；语句，JavaScript解释器将会跳到下一个测试case并看它是否匹配。特别是，如果我们在一条switch语句中使用了最后的default关键字（参见图14-7中的4），漏掉break；语句可能会导致问题。如果没有测试case为真，就使用default语句，因此，它等同于一个条件语句中的最后的else子句。如果我们在前面的一个测试case中漏掉了break；语句，并且有一个case为true, JavaScript解释器仍然会运行default语句中列出的所有代码。

下面是如何使用switch语句来编写本小节前面给出的if/else if代码：

switch（favoriteColor）{

case'blue'：

message='Blue is a cool color.’；

break；

case'red'：

message='Red is a warm color.’；

break；

case'green'：

message='Green is the color of the leaves.’；

break；

default：

message='What kind of favorite color is that?’；

}

这段代码等同于前面的if/else if代码，但是它更加紧凑而且容易阅读。

实际上，如果想要对几个值运行同样的代码，也可以把多条case语句紧挨着放在一起（并且故意不包括default关键字）。例如：

switch（favoriteColor）{

case'navy'：

case'blue'：

case'indigo'：

message='Blue is a cool color.’；

break；

case'red'：

message='Red is a warm color.’；

break；

case'green'：

message='Green is the color of the leaves.’；

break；

default：

message='What kind of favorite color is that?’；

}

这类似于在一条if/else语句中使用（favoriteColor=='navy'||favoriteColor=='blue'||favoriteColor=='indigo'）。

14.7　创建快速载入的JavaScript

一旦我们开始为脚本使用外部JavaScript文件，访问者就会开始感觉到站点变快了。借助于浏览器的缓存，一旦为站点的页面下载了外部JavaScript文件，它们就不必再为不同的页面再次下载。然而，仍然有另一种方式使得站点下载更快，即压缩外部的JavaScript文件。

注意：通过SSL（secure socket layer，安全套接层）发布的文件不会缓存。因此，如果人们使用https：//作为协议访问站点的页面（例如，https：//www.oreilly.com），然后，他们下载的任何文件，包括外部JavaScript文件，都必须在每次需要的时候再次下载。

为了让脚本更容易理解，程序员通常插入空格、回车符和注释来说明脚本做什么。这些对于程序员来说都是重要的附加内容，但是，对于Web浏览器来说则不是必需的，它们可以很好地理解没有回车符、制表符、无关的空格或注释的JavaScript。使用一个压缩程序，我们可以减小JavaScript代码所占用的空间。本书所推荐的jQuery版本是精简版，其大小差不多是未压缩版本的一半。

还有几个程序，其目的是为了使得JavaScript代码更小。Douglas Crockford的JSMin（http：//crockford.com/javascript/jsmin.html）就是一个例子，Dean Edward的Packer（http：//dean.edwards.name/packer）也是一个例子。然而，本章使用的压缩器和Yahoo所使用的相同，因为它可以让文件节省很大的空间而不会修改代码（一些压缩器直接重新编写你的代码，而在某些情况下，这会使你的脚本无法工作）。

Yahoo的JavaScript压缩器YUI Compressor位于http：//developer.yahoo.com/yui/compressor。在本书的第3版中，我们给出了只有计算机黑客喜欢的说明：他们喜欢下载一个JAR文件（Java），并且使用可怕的命令行。幸运的是，一些友好的有事业心的人已经制作了一个在线版本，我们将想要压缩的JavaScript代码粘贴进去，或者甚至在计算机上选取一个JavaScript文件并将其上传到Web站点，就可以使用它了。

1.打开一个Web浏览器并且访问http：//www.refresh-sf.com/yui/。

这是Online YUI Compressor的站点。

2.单击文件链接。

可以只是从文本编辑器复制JavaScript代码并将其粘贴到站点主页的大文本框中，然后就可以跳转到步骤4。

3.单击Choose File按钮并在计算机上找到外部JavaScript文件。

这个文件必须只包含JavaScript。例如，你不能选择其中也包含JavaScript程序的一个HTML文件。

4.选择“Redirect to gzipped output”选项（就在Compress按钮的上方）。

这个选项允许你将最小化的代码下载到一个新的压缩文件中。这将是新的、压缩过的、外部JavaScript文件，可以将其保存到你的站点。

5.单击Compress按钮。

该Web站点处理你的代码，并且将压缩后的文件下载到你的计算机上。你可以重新命名该文件（因为它总是保存为min.js），并且将其放到你的站点上使用。在你压缩一个文件之后，Online YUI Compressor提供了很好的一份报告，列出了最初的文件大小，新的压缩后的文件大小，以及压缩百分比。

警告：确保在使用了Online YUI Compressor之后，最初的JavaScript文件仍然保留在手边，因为新的、压缩后的版本可能是不可读的，并且如果你想要对最初的编码做出修改，你是无法编辑它的。

第15章　除错和调试

每个人都会犯错误，但是在JavaScript中犯错可能会导致程序无法正确运行，或者根本就不运行。当我们初次开始使用JavaScript的时候，可能会犯很多的错误。试图搞清楚为什么一个脚本没有按照应该的方式工作，这可能令人感到沮丧，但是，它也是编程的一部分。幸运的是，通过一些经验和实践，我们将能够搞清楚为什么会发生错误以及如何修正错误。

本章介绍一些最常见的编程错误，并且，更重要的是，教你如何识别脚本中的错误，用程序员的话来说，就是调试错误。此外，本章中的教程还将带你一步一步地调试一个带有错误的脚本。

15.1　常见JavaScript编程错误

程序出错的方式有无数种，从简单的输入错误到可能不时会出现的更为隐秘的错误。然而，还是有很多错误经常会困扰初级的（甚至是高级的）JavaScript程序员。浏览本节给出的列表，并且在编程的时候记住它们。你可能会发现，知道这些常见的错误会使得更容易识别和修正自己的程序中的错误。

15.1.1　没有结束符号

你可能已经注意到了，JavaScript中充满无数的圆括号、花括号、分号、引号和其他的标点符号。由于计算机严格的本性，漏掉一个单个的标点符号可能导致程序陷入绝境。最为常见的错误之一是仅仅忘掉了包含结束的标点符号。例如，alert（'hello'；将会导致一个错误，因为结束的标点符号漏掉了，正确形式是alert（'hello'）；。

漏掉了结束的标点符号可能引起一个语法错误（参见本小节“快速熟悉：错误类型”部分的介绍）。这种“语法错误”使得脚本根本无法运行。当我们测试脚本运行的时候，浏览器允许我们知道是否犯了语法错误；但是，令人混淆的是，它们对问题的描述也不同。在Firefox错误控制台中（参见1.6.1节），我们得到“missing）after argument list”这样的错误消息；而Internet Explorer（参见1.6.2节）则把这个错误报告为“Expected'）’”；而Safari错误控制台（参见1.6.3节）则给出一条没有多大帮助的消息“SyntaxError：ParseError.”。正如本书1.6节开始处所介绍，Firefox常常提供最容易理解的错误消息，因此，在试图搞清楚脚本为什么不工作的时候，最好从使用该浏览器开始（如图15-1所示）。

图　15-1　Firefox的错误控制台列出了浏览器遇到的所有JavaScript错误。我们可以在Windows上选择WebDeveloper→Error Console（Ctrl+Shift+J），或者在Macs上选择Tools→WebDeveloper→Error Console（æ-Shift-J）来显示控制台。既然控制台列出了它在所有页面上遇到的错误，我们希望经常通过单击Clear按钮（如图中圆圈所示）来清除列表

alert（'hello'；中的语法错误确实容易找到。然而，当我们有了一个嵌套的圆括号的时候，很容易漏掉结束的圆括号并且很难一眼就看到错误。例如：

if（（x＞0）＆＆（y＜10）{

//do something

}

在这个例子中，条件语句最终的结束圆括号漏掉了，也就是紧接着（y＜10）后面的一个圆括号。第1行代码应该是if（（x＞0）and（y＜10））{。另外，Firefox提供了对问题的最为清晰的说明：“missing）after condition.”。表15-1给出了Firefox的错误控制台语法错误消息的列表。

当我们忘了包含第2个引号的时候，也会遇到一个语法错误。例如，alert（'hello）；会产生一个错误，因为最终的引号漏掉了，应该是alert（'hello'）；。在Firefox中，如果忘记包含两个引号，将会得到一个“unterminated string literal”错误，而Internet Explorer则报告一个“unterminated string constant”错误；Safari再次产生没有多大用处的错误消息：“SyntaxError：Parse Error”。

花括号也是成对出现的，并且，我们将在条件语句中使用它们（参见3.1.1节），在循环中（参见3.3节）中使用它们，当创建JavaScript对象直接量的时候（参见4.8.3节）以及创建JSON（参见11.4节）的时候使用它们：

if（score==0）{

alert（'game over'）；

在这个例子中，结束的花括号漏掉了，并且脚本会产生一个语法错误。

避免漏掉结束的标点符号的一种方法是，总是在添加其他程序之前添加结合的标点符号。例如，假设想要最终得到如下的代码：

if（（name=='bob'）＆＆（score==0））{

alert（'You lose（but at least you have a great name）'）；

}

首先是输入外围的两个元素，为条件创建一个基本的框架，如下所示：

if（）{}

此时，还没有太多代码，但是，很容易看到是否错误地遗漏了任何标点。接下来，添加更多的代码，一点一点地添加，直到程序全都编写完毕。在创建一个复杂的JavaScript对象直接量的时候，例如，用来设置Validation插件的对象直接量（参见本书9.4节）；或者创建一个如11.4节所讨论的JSON对象的时候，也可以使用这种方法。从基本的结构开始：

var options={

}；

然后，添加更多的结构：

var options={

rules：{

}，

messages：{

}

}；

最终的对象是：

var options={

rules：{

name：'required'，

email：'email'

}，

messages：{

name：'Please type your name'，

email：'Please type your e-mail address.’}

}；

这种方法允许我们通过几个步骤来检查自己的工作，并且使得识别标点符号方面的任何错误容易了很多。

表15-1：Firefox的错误控制台（参见1.6.1节的介绍）对于语法错误的消息给出了最为清晰的描述。当脚本不工作的时候，在Firefox中预览并查看错误控制台。这里给出一些最为常见的错误消息及其含义

快速熟悉错误类型

在编写JavaScript程序的时候，会遇到3种基本的错误类型。一些这样的错误直接而明显，而其他的一些错误总是在脚本完成并运行的时候才会露出它们丑陋的苗头。

语法错误。语法错误基本上是使得JavaScript解释器举手投降并说“我放弃”的错误。任何涉及漏掉结束的圆括号、花括号或引号的错误都会产生语法错误。在Web浏览器读取脚本的时候，会立即遇到语法错误，因此，脚本没有机会运行。语法错误的错误消息总是出现在Web浏览器的错误控制台中。

运行时错误。当浏览器成功地读取一段脚本的代码并且JavaScript解释器解释它之后，它可能仍然会遇到错误。即便程序的语法是正确的，在程序运行的时候，其他的问题可能会出现，这些问题叫做运行时错误。例如，假设在脚本开始处定义了一个名为message的变量，在脚本的后面，我们给图像添加了一个单击函数，以便在单击该图像的时候出现一个警告框。假设这个例子的警告代码如下所示：alert（MESSAGE）；。这条语句的语法没有问题，但是它调用了变量MESSAGE而不是小写的message。正如本书15.1.4节所提到的，JavaScript是区分大小写的，因此，MESSAGE和message引用两个不同的变量。当访问者单击图像的时候，JavaScript解释器查找变量MESSAGE（这个变量并不存在）并且产生一个运行时错误。

当试图访问页面上不存在的一个元素或者一个浏览器还没有读入到其内存中的一个元素的时候，会产生另一种常见的运行时错误。参见5.4.1节对于jQuery的$（document）.ready（）函数的介绍，了解关于这一问题的更多细节。

逻辑错误。有时候，即便脚本看上去运行了，它也不会产生我们想要的结果。例如，我们可能有一条if/else语句（参见3.1.1节），当一个条件为true的时候执行步骤A，而当条件为false的时候执行步骤B。遗憾的是，程序不会到达步骤B，即便我们确定条件为false。当我们不正确地使用相等操作符的时候（参见2.5.1节），可能会发生这种错误。从JavaScript解释器的观点来看，一切都是技术错误，但是，我们已经在程序的逻辑中犯了一个错误，这导致脚本不能按计划执行。

逻辑错误的另一个例子是无限循环，这是永远运行的一段代码，通常会引起程序挂起并且有时候甚至会导致Web浏览器崩溃。下面是无限循环的一个例子：

for（var i=1；i＞0；i++）{

//this will run forever

}

简而言之，只要测试条件（i＞0）为true，这个循环就将运行。由于i从一个值1开始（var i=1），并且每次它迭代循环的时候，i都会增加1（i++），i的值将总是大于1。换句话说，循环不会停止（如果需要回顾关于for循环的知识，请参见3.3.3节）。

逻辑错误属于最难发现的错误。然而，使用15.2节所介绍的调试技术，我们应该能够发现所会遇到的任何问题。

15.1.2　引号

引号通常困扰着初级程序员。引号用来创建字符串，以及用作页面上的消息或作为程序中的变量的其他字符（参见2.3.3节）。JavaScript和其他的编程语言一样，允许使用双引号或者单引号来创建字符串。因此：

var name="Jane"；

等同于

var name='Jane'；

正如前面一节所介绍的，我们必须包含开始的引号和结束的引号，否则，最终将在Firefox中得到一个“Unterminated string literal”错误（并且所有其他的浏览器也会放弃运行你的脚本）。此外，正如2.3.3节所介绍的，对于每一对引号必须使用同样类型的引号，换句话说，都是单引号或者都是双引号。因此，var name='Jane"将会产生一个错误。

可能产生的另一个常见的问题是在一个字符串中使用引号。例如，很容易产生如下的错误：

var message='There's an error in here.’；

注意There's中的单引号。JavaScript解释器把这个引号看做结束引号，因此，它实际上会这样看待这行代码：var message='There'，并且这行代码剩下的部分被看做一个错误。在Firefox错误控制台中，我们将得到消息“Missing；before statement”，因为Firefox认为第二个引号是一条简单的JavaScript语句的结尾，而后面的部分是另一条语句。

我们可以用两种方法来解决这个问题。首先，可以混用并匹配单引号和双引号。换句话说，用双引号来包含带有单引号的一个字符串，或者可以用单引号来包含带有双引号的一个字符串。例如，可以用这种方法来修正上面的错误：

var message="There's no error in here."；

或者，如果字符串包含双引号：

var message='He said，"There is no problem here."’；

另一种方法是对字符串中的引号进行转义。2.3.3节中的“常见问题：把引号放入字符串”部分详细介绍了转义引号，而这里只是一个回顾，要转义一个字符，在其前面放置一个反斜杠，如下所示：

var message='There\'s no error here.’；

JavaScript解释器把\’看做一个单引号字符而不是看做用来开始或结束字符串的符号。

15.1.3　使用保留字

正如表2-1所示，JavaScript语言有很多单词是保留了作为其专用的。这些单词包括用在该语言的语法中的单词，例如，if、do、for和while，以及用作浏览器对象的一部分的单词，例如alert、location、window和document等。这些单词不能用作变量名。

例如，如下的代码产生一个语法错误：

var if="This won't work."；

因为if用来创建条件语句，例如在if（x==0）中，我们不能使用它作为变量名。然而，如果我们使用那些作为浏览器对象模型（参见表2-1）的一部分的单词作为变量名，一些浏览器不会产生错误。例如，引用HTML文档的document。例如，看看下面的代码：

var document='Something strange is happening here.’；

alert（document）；

Firefox、Safari和Opera都不会产生一个错误，而是弹出一个警告框，其中带有文本“[object HTMLDocument]”，它引用的不是HTML文档自身。换句话说，这些浏览器不会允许你使用一个字符串覆盖文档对象。Chrome和IE 9产生一条错误消息，并且不会显示弹出的警告框。

15.1.4　条件语句中的单个等号

条件语句（参见3.1.1节）为程序提供了一种方式，根据一个变量的值、页面上一个元素的状态或者脚本中的某些其他的条件，来以不同的方式响应。例如，如果一幅图片是隐藏的话，条件语句可以显示它；而如果它是显示的，就隐藏它。然而，只有特定的条件为true或false的时候，条件语句才有意义。遗憾的是，很容易创建那些总是为true的条件语句：

if（score=0）{

alert（'game over'）；

}

这段代码用来检查存储在变量score中的值，如果这个值是0，那么，带有消息“game over”的警告框将出现。然而，在这个例子中，警告消息将总是出现，不管在条件语句之前存储在score中的值是什么。这是因为一个单个的等号是赋值操作符，因此，score=0把值0存储到score中。JavaScript解释器把一个赋值操作看做true，因此，不仅上面的代码总是弹出警告框，它还总是把score中的值重写为0。要避免这种错误，确保在测试两个值是否相等的时候使用两个等号符号：

if（score==0）{

alert（'game over'）；

}

15.1.5　区分大小写

别忘了，JavaScript是区分大小写的，这意味着，JavaScript解释器不仅记住变量名、函数名、方法名和关键字中的字母，而且记住了字母是大写还是小写。因此，alert（'hi'）和ALERT（'hi'）对于JavaScript解释器来说是不同的。首先，alert（'hi'）调用浏览器内置的alert（）命令，而第二个ALERT（'hi'）尝试调用一个名为ALERT（）的用户定义函数。

如果使用一个冗长的DOM选择方法getElementsByTagName（）或getElementById（），很容易遇到这个问题，因为它们既使用了大写也使用了小写字母。同样，如果在变量和函数名中既使用了大写字母也使用了小写字母，可能会不断地遇到这个问题。

如果看到一条“x is not defined”的错误消息（其中x是变量、函数或方法的名字），可能是错误匹配大小写的问题。

15.1.6　外部JavaScript文件的路径不正确

另一种常见的错误是没有正确链接到一个外部JavaScript文件。1.2节介绍了如何把一个外部JavaScript文件添加到一个Web页面。基本上，我们使用＜script＞标签的src属性来指向文件。因此，在HTML页面中，可以给文档的＜head＞部分添加一个＜script＞标签，如下所示：

＜script src="site_js.js"＞＜/script＞

src属性就像一个链接的herf属性一样工作，它定义了到JavaScript文件的路径。正如1.2节中的“快速熟悉：URL类型”部分所介绍的，有3种方式可以指向一个文件：绝对链接（http：//www.site.com/site_js.js）、根相对链接（/site_js.js）和文档相对链接（site_js.js）。

文档相对路径描述了Web浏览器如何从当前页面（Web页面）到一个特定的文件。文档相对链接经常使用，因为它允许我们在自己的计算机上测试Web页面和JavaScript文件。如果使用根相对链接，我们将需要在自己的计算机上设置一个Web服务器才能测试页面（或者将页面移动到Web服务器上去测试）。

我们可以在1.2节中的“快速熟悉：URL类型”部分阅读到有关链接路径如何工作的更多信息。但是，简而言之，如果我们发现一个脚本不能工作并且使用了外部JavaScript文件，那么，仔细检查以确保已经为JavaScript文件指定了正确的路径。

提示：如果使用jQuery库并且在Firefox错误控制台中得到了“$is not defined”的错误，我们可能没有正确地链接到jquery.js文件（参见4.3节了解更多信息）。

15.1.7　外部JavaScript文件中的不正确路径

当在一个外部JavaScript文件中使用文档相对路径的时候，和文件路径相关的另一个问题就会发生。例如，我们要创建了一个脚本来在页面上显示图像（就像一个幻灯片或者只是一个“随机显示当天图像”的脚本）。如果这个脚本使用了文档相对链接指向该图像，而我们把该脚本放入一个外部JavaScript文件的话，将会遇到麻烦。原因是：当一个外部JavaScript文件载入Web页面时，它的文档相对路径参照的是Web页面自身的位置。因此，包含在JavaScript文件中的任何文档相对的路径都必须相对于Web页面而不是相对于JavaScript文件。

这里有一个简单的例子可以说明这个问题。图15-2给出了一个非常简单的Web站点的结构。其中有两个Web页面（page.html和about.html）、4个目录（libs、images、pages和about）和一个外部JavaScript文件（images目录下的photp.jpg）。假设site_js.js文件引用了photo.jpg文件，可能是预载入该图像（参见7.1.2节）或者将其动态地显示于Web页面上。

从site_js.js文件的角度来看，到photo.jpg文件的一个文档相对路径是../images/photo.jpg（图15-2中的#1）。这个路径告诉浏览器退出libs目录（../），进入到images目录（images/）并且选择photo.jpg文件。然而，从page.html文件的角度，photo.jpg文件的路径只是images/photo.jpg（图15-2中的#2）。换句话说，同一张照片的路径对于两个文件来说是不同的。

如果我们想要在page.html文件中使用site_js.js脚本，那么，必须在site_js.js file文件中使用#2路径来引用photo.jpg的位置（也就是说，指定一个相对于page.html的路径）。同样，我们不能在位于站点的另一个目录下的一个Web页面中使用site_js.js，因为对于该文件来说，相对路径将有所不同（图15-2中的#3）。

有几种方法可以解决这个问题。首先，我们可以避免这种情况，不在JavaScript文件中列出到其他文件的路径。如果这么做了，可以使用根相对路径（参见1.2节），它对于站点的任何页面来说都是相同的。此外，可以在每个Web页面中定义到该文件的路径。例如，可以链接到外部JavaScript文件（参见1.2节），然后，在每个Web页面中定义变量来保存从当前页面到正确的文件的文档相对路径。

图　15-2　文档相对路径取决于路径开始的文件的位置，以及目标文件的位置。例如，从site_js.js文件到photo.jpg文件的文档相对路径（#1）是../images/photo.jpg；而从page.html文件到同一个文件的路径是images/photo.jpg（#2）；并且，从about.html到同一个文件的路径是../../images/photo.jpg（#3）

最后，我们可以使用类似于7.4节的幻灯片所使用的一种方法。路径来自Web页面并且嵌入页面上的链接中，JavaScript将该路径从HTML中提取出来。只要这些路径在HTML中有效，它们也会在脚本中有效。

15.1.8　消失的变量和函数

我们可能偶尔遇到一个“x is not defined”错误，其中x是一个变量的名字或者我们试图调用的一个函数的名字。问题可能只是我们错误地拼写了变量或函数的名字，或者使用了错误的大小写。然而，如果我们查看自己的代码，可以清楚地看到该变量或函数在脚本中定义了，那么，我们可能遇到了一个“作用域”问题。变量和函数的作用域在3.4.4节详细介绍过，简而言之，如果一个变量定义于一个函数中，那么，只能供该函数使用（或者供定义于该函数内部的其他函数使用）。下面是一个简单的例子：

1 function sayName（name）{

2

var message='Your name is'+name；

3}

4 sayName（）；

5 alert（message）；//error：message is not defined

变量message定义于函数sayName（）中，因此，它只能存在于该函数中。在该函数之外，message是不存在的，因此，当脚本试图在该函数之外访问这个变量的时候（第5行），将会产生一个错误。

当使用jQuery的时候，我们可能也会遇到这种错误。在5.4节，我们了解到了jQuery的$（document）.ready（）函数的重要性。函数中的任何内容只能在HTML已经载入之后运行。如果在$（document）.ready（）函数中定义了变量或函数，而试图在该函数之外访问它们，将会遇到问题，如下所示：

$（document）.ready（function（）{

var msg='hello'；

}）；

alert（msg）；//error：msg is not defined

因此，使用jQuery的时候，确保把所有的程序都放在$（document）.ready（）函数之中：

$（document）.ready（function（）{

var msg='hello'；

alert（msg）；//msg is available

}）；

快速熟悉

减少错误的编程技巧

处理程序中的错误和bug的最好的办法就是试图尽可能早地预防它们。如果等到编写一个300行的脚本之后并且就要在一个Web浏览器中测试它之前，那么确实很难找到错误的诱因。减少错误的两个最为重要的技巧是：

构建小段的脚本。你现在可能已经知道了，JavaScript程序很难阅读，因为其中充满了）、if、else和函数等内容。不要试图一次性编写一个完整的脚本（除非你真的很棒，脚本很短，或者你很走运）。在编程过程中，有很多种犯错误的方法，但是，分小段地编写脚本可以较好地避免一些错误。

例如，假设我们想要把在“Comments”框中输入的字母的数目紧接着该文本框显示。换句话说，当一个访问者在该字段输入的时候，当前输入的字母的总数目会在该文本框后面显示（一些站点会这么做，它们会限制访问者在一个字段输入的字母数，例如，最多300个字母）。这项任务使用JavaScript确实很容易完成，但是它包含了几个步骤：响应keydown事

件（当访问者在字段输入一个字母的时候），读取字段的值，计算字段中字符的数目，然后在页面上显示数目。我们可以尝试一次性编写这个脚本，但是也可以先为步骤1（响应一个keydown事件）编写代码，然后立即在Web浏览器中测试（使用alert（）命令或者下一节所介绍的Firebug的console.log（）函数，这可以帮助我们看到一个keydown事件的结果）。如果它有效，我们可以继续第二个步骤，测试它；以此类推。

随着我们获得更多的经验，可能不需要测试这样的小步骤。我们可以一次编写几个步骤，然后测试代码段。

经常测试。我们还应该经常在Web浏览器中测试脚本。至少在完成程序的每一部分之后进行测试，就像上一点中建议的那样。此外，我们应该在不同的浏览器中测试脚本，尤其是IE 6、7和8，Firefox 2和3，Safari 2和3，以及我们认为站点的访问者可能使用的任何其他的浏览器。

15.2　使用Firebug调试

如果还没有使用过Firebug，你可能会错过了对于Web设计师来说最好的工具之一。它是免费的，易于安装和使用，并且可以帮助我们改进HTML、CSS和JavaScript。Firebug是Firefox的一个扩展，添加了一套有用的诊断工具，允许我们为HTML、CSS以及（对于本书来说更为重要的）JavaScript程序挑毛病。

15.2.1　安装并打开Firebug

可以从www.getfirebug.com找到这个扩展。按照如下步骤安装它。

1.使用Firefox访问https：//addons.mozilla.org//firefox/addon/firebug，单击“Add to Firefox”按钮。

为了防止你偶然安装一个恶意扩展，Firefox会阻止安装该扩展的第一次尝试。然后会出现一个软件安装窗口，警告你可能会安装恶意软件。不要担心，Firebug完全是安全的。

2.单击Install Now按钮。

Firefox安装该扩展程序，但是，只有当重新启动Firefox的时候，它才会工作。将会出现一个小窗口，详细说明一切。

3.单击Restart按钮。

好了，Firebug已经安装好并且可以使用了，但是首先你需要打开它。

4.在Windows上，单击Firefox→Web Developer→Firebug→Open Firebug或者按下F12键，在Macs上，单击Tools→Web Developer→Firebug→Enable Fire-bug。

现在，你可以开始使用Firebug来帮助你调试脚本了。

15.2.2　用Firebug查看错误

Firebug提供了查看错误的一种方法，该方法比Firefox的内置错误控制台更容易使用而且更为有用。使用Firebug，当我们载入带有JavaScript错误的一个Web页面，将会在浏览器的右上角看到统计的错误列表（如图15-3所示）。单击Firebug图标来打开Firebug控制台，它列出了所有的JavaScript错误。

控制台中列出的错误和Firefox的错误控制台中列出的错误相同（如图15-1所示），但是，Firebug只是列出我们当前查看的页面的错误（不像错误控制台列出Firefox曾经访问过的所有的页面上的错误）。由于Firebug提供了这样一种容易的方法来访问错误信息，我们可能会发现，一旦熟悉了Firebug，就会完全忽略Firefox错误控制台。

图　15-3　Firebug控制台列出了Firefox在当前页面上遇到的所有JavaScript错误。要查看发生错误的具体的代码行，单击错误下的代码片段（如图中圆圈所示）。然后，Firebug从控制台标签页切换到Script标签页，并且突出显示错误出现的代码行

15.2.3　使用console.log（）记录脚本过程

一旦脚本开始运行，它就像是一种黑盒子。我们并不能真正知道脚本中发生了什么，并且只能看到最终结果，例如，页面上的消息、一个弹出窗口等。我们不能总是确切地判断一个循环是否正确工作，或者在任何时候都及时地看到一个特定变量的值。

JavaScript程序员很长一段时间以来使用alert（）方法（参见1.3节）来弹出带有一个变量的当前值的一个窗口。例如，如果想要知道当一个循环运行的时候变量elementName中存储的是什么值，使用alert（elementName）；。这是查看脚本的“黑盒子”中的内容的一种方式。然而，警告框确实太有干扰性了。我们必须单击它以关闭，并且在一个可能要运行20次的循环中，就会有太多的弹出警告框要关闭。Firebug提供了查看程序的一种更好的方法。Firebug控制台不仅列出错误（参见上一节的介绍），而且可以用来输出来自程序的消息。console.log（）的作用类似于document.write（）函数（参见1.4节的介绍），但是，console.log（）把消息输出到控制台，而不是把消息输出到Web页面。

提示：当前所有的浏览器都支持console.log（）方法。因此，你可以参考第1章介绍的内容在Chrome、Safari、Internet Explorer和Opera的控制台中使用它。

例如，可以使用如下代码把变量elementName的当前值显示到控制台：

console.log（elementName）；

和alert（）方法不同，这种方法不会干扰程序流程，我们只是在控制台中看到这条消息。

要让日志消息更容易理解，可以包含带有附加文本的一个字符串。例如，如果有一个名为name的变量，并且我们想要确定在程序中的某个时刻该变量中存储的是什么值，可以像下面这样使用console.log（）函数：

console.log（name）；

但是，如果想要在name的前面加一条消息，可以这样编写：

console.log（'User name：%s'，name）；

换句话说，首先给log（）函数传递一个字符串，后面跟着一个逗号以及要显示其中的值的变量名。特殊的%s是表示“用变量的值替换我”的一种方式。换句话说，变量的值替换了%s，因此，最终在控制台中得到这样的一条消息：“User name：Bob”。

可以给消息添加多个变量，只要为每个变量包含一个%s就可以了。例如，如果有两个变量：name和score，并且想要用一条定制日志消息来显示它们，可以这样编写：

console.log（'%s has a score of%s'，name, score）；

可以对数字值使用%s，但是Firebug提供了两种其他的符号：%d和%f，分别表示整数（例如，1、2和10）和浮点数（例如，1.22和2.4444）。使用这两个数值符号中的一个，意味着数值将用不同的颜色显示，这只是将它们和其他文本区分开的一种方式。

例如，可以重新编写上面的代码，从而把存储在变量score中的数值也显示出来：

console.log（'%s has a score of%d'，name, score）；

log（）函数只是一种方法，可以为我们提供一些自己所开发的脚本的功能的相关信息。一旦程序完成并且投入使用，我们应该从脚本中删除所有的console.log（）代码。没有安装Firebug的Web浏览器（也包括Internet Explorer、Safari和Opera），在遇到log（）函数的时候将会产生错误。

15.2.4　教程：使用Firebug控制台

在本教程中，我们将学习如何使用console.log（）函数来查看在程序中发生了什么。

我们将创建一个脚本，用来显示在一个表单上的文本框中输入的字符的数目。

注意：参见1.3节中的“注意”部分以获取如何下载教程文件的信息。

为了开始本教程，首先按照15.2.1节的说明在Firefox中安装Firebug（别忘了，Firebug只能够和Firefox Web浏览器一起工作）。

1.在文本编辑器中打开文件console.html。

这个脚本使用jQuery库。外部jquery.js文件已经添加到了页面，并且开始的和结束的＜script＞标签已经设置好了。我们从添加jQuery的$（document）.ready（）函数开始。

2.在页面顶部的＜script＞标签中添加如下粗体所示的代码：

＜script＞

$（document）.ready（function（）{

}）；//end ready

＜/script＞

我们在4.10节学习了$（document）.ready（）函数的基本知识，它使得浏览器在开始运行任何JavaScript之前载入所有的页面代码。首先使用Firebug log（）函数来直接显示一条脚本已经执行了.ready（）函数的消息。

3.向脚本添加如下粗体所示的代码：

＜script＞

$（document）.ready（function（）{

console.log（'READY'）；

}）；//end ready

＜/script＞

console. log（）在我们将其放置在脚本中的地方运行。换句话说，当页面的HTML载入之后（这就是ready（）函数所等待的事情），Firebug把“READY”显示到Firebug控制台。添加ready（）函数确实是常见且基本的动作，因此，我们可能不会总是在这里添加一个console.log（）函数，但是对于这个教程，我们将添加一个console.log（）函数来看看它是怎么工作的。实际上，我们将向这个页面添加很多日志消息，以便能够通过练习掌握console.log（）函数。

4.保存这个文件，并且在Firefox中打开。如果Firebug没有启用，单击Firefox工具栏右上角的Firebug图标。

也可以单击Tools→Web Developer→Firebug→Open Firebug来查看Firebug控制台。

单词READY应该出现在控制台中（如图15-4中的圆圈所示）。每次访问者在一个表单的文本字段中输入一个字符，我们创建的这段脚本就会显示输入的字符的数目。为了实现这一点，我们将给该文本框添加一个keyup事件（参见5.1.4节）。在这个脚本的每个步骤中，我们还将添加一个console.log（）函数，来帮助我们看清楚发生了什么。

图　15-4　如果Firefox浏览器工具栏的右上角的Firebug图标左边出现了一个数字，那么表示有一个JavaScript错误（可能只是一个输入错误）。打开Firebug（单击Firebug图标），并且控制台将告诉你这个错误

5.在步骤3中添加代码的后面添加如下的代码：

$（'#comments'）.keyup（function（）{

console.log（'Event：keyup'）；

}）；//end keyup

这个页面上的＜textarea＞标签有一个ID为comments，因此，我们可以使用jQuery（$（'#comments'））选择该元素并且给keyup事件添加一个函数（参见5.2节回顾关于添加事件的内容）。在这个例子中，console.log（）函数只是把一条状态消息显示到Firebug控制台，以便当每次触发keyup事件的时候告诉你。这个函数是查看一个事件函数是否真正运行或者某些事情是否阻止了事件发生的一种容易的方式。

保存页面，在Firefox中载入它并在文本框中输入几个字符。确保Firebug的控制台是打开的，我们应该看到了几行'Event：keyup'（每行对应我们输入的一个字符）。现在，keyup事件已经工作了，我们可能还想获取文本框中的内容并将其存储到一个变量中。为了确保获取了所需的信息，可以把该变量的内容显示到控制台。

6.在步骤5中输入代码中添加如下的第3行和第4行：

1$（'#comments'）.keyup（function（）{2

console.log（'Event：keyup'）；3 var text=$（this）.val（）；

4 console.log（'Contents of comments：%s'，text）；

5}）；//end keyup

第3行获取了文本框的值并将其存储到一个名为text的变量中（参见9.1.2节了解从一个字段提取值的更多信息）。第4行把消息写到了控制台。在这个例子中，它组合了字符串'Contents of comments：’和文本框中当前存储的值。当一个程序没有正确工作的时候，一个非常常见的诊断步骤是，显示脚本中变量的值来确保该变量包含了我们期望它拥有的信息。

7.保存这个文件，在Firefox中重新载入它，并且在评论文本框中输入一些文本。

现在，每次向字段中输入一个字母，控制台应该显示评论文本框中的内容。到目前为止，我们应该已经掌握了控制台，但是，我们还要添加一条消息，然后完成这个脚本。

8.编辑keyup事件函数，添加如下的第5行和第6行：

1$（'#comments'）.keyup（function（）{2

console.log（'Event：keyup'）；3

var text=$（this）.val（）；4

console.log（'Contents of comments%s'，text）；5 var chars=text.length；

6 console.log（'Number of characters：%d'，chars）；

7}）；//end keyup

第5行计算了存储在text变量中的字符的数目（参见14.1.1节了解关于length属性的更多内容），并且将其存储在变量chars中。为了确保脚本正确地计算了字符数，使用log（）函数（第6行）向控制台显示一条消息。既然变量chars保存了一个数字，可以使用%d符号来显示一个整数值。

还剩下最后一件事情：完成脚本以使其显示输入的字符数目，以便访问者可以看到。

9.在keyup事件函数的末尾添加最后一行，完成后的页面脚本如下所示：

1＜script＞

2$（document）.ready（function（）{

3 console.log（'READY'）；

4$（'#comments'）.keyup（function（）{

5 console.log（'Event：keyup'）；

6 var text=$（this）.val（）；

7 console.log（'Contents of comments：%s'，text）；

8 var chars=text.length；

9 console.log（'Number of characters：%d'，chars）；

10$（'#count'）.text（chars+"characters"）；

11}）；//end keyup

12}）；//end ready

15＜/script＞

10.保存该文件，并且在Firefox中预览。

确保启用了Firebug，页面和控制台现在应该如图15-5所示。我们可以在教程目录的chapter15文件夹下看到本教程的一个完成后的版本，即complete_console.html。

注意：一旦我们有了一个功能正常的程序，应该从脚本中删除所有的console.log（）代码。在某些浏览器中，log（）函数将会产生错误。

15.2.5　更强大的调试

Firebug控制台是显示消息以便我们在程序运行时知道发生了什么的一种好办法。但是，有时候，一个程序运行得如此之快，以至于很难在每一步中看清楚发生了什么。我们需要一种方法使这一过程慢下来。幸运的是，Firebug包含了一个强大的JavaScript调试器。它使我们一行一行地步进执行一个脚本，以便可以看到在程序的每一步发生了什么。

注意：你将会发现在Chrome、Opera、Safari和Internet Explorer 9中JavaScript调试器具有相似的功能。

调试是修正一个不能正确工作的程序的过程，也就是除错。要真正理解一个程序是如何发挥功能（或者不能正确发挥功能），我们需要一步一步地查看程序是如何工作的。

要使用调试器，可以把某些代码行标记为断点。断点是JavaScript停止运行程序并开始等待的一个位置。然后，可以使用Firebug中的控制来使得程序一次运行一行。通过这种方式，我们可以看到在任何特定的代码行到底发生了什么。如下是基本的过程。

图　15-5　Firebug控制台是在程序运行时显示诊断信息的一种好办法。只要在一组中的第一条console.log（）消息的前面添加console.group（），在最后一条消息的后面添加console.groupEnd（），就可以把一系列的日志条目组织在一起。可以在www.getfirebug.com/console.html了解关于控制台函数的更多内容

1.在Firefox中打开一个页面。

我们需要按照15.2.1节的介绍来安装和打开Firebug。

2.打开Firebug。

单击Firebug图标（在Firefox工具栏右上角位置）。或者，按下F12键（仅适用于Windows操作系统）或者单击Firefox→Web Developer→Firebug→Open Firebug（在Windows中）或者单击Tools→Web Developer→Firebug→Enable Firebug（在Mac中）。

提示：如果不喜欢We b页面直接堆叠在Fi re b u g上的难看的样子，选择Fi re fox→We b Developer→Firebug→“Open Firebug in New Window”（在Windows中）或者Tools→Web Developer→Firebug→“Open Firebug in New Window”（在Mac中）

3.单击Script标签页（如图15-6所示）。

这个Script标签页列出了我们想要调试的文件的源代码。在写入一个Web页面中的脚本的情况下，我们看到了整个Web页面的源代码（包括HTML）。对于一个外部JavaScript文件来说，我们只是看到该文件中的JavaScript。

图　15-6　Firebug调试器允许我们设置断点（脚本停止并开始等待的行），控制脚本的执行，并且查看Watch列表中的变量。在调试器执行脚本的时候，当前行（将要运行的行）的左边有一个黄色的箭头（如圆圈所示）

4.从source菜单中选择带有我们想要调试的脚本的文件（如图15-6所示）。

把脚本放入不同的文件是很常见的：Web页面自身，或者一个或多个外部JavaScript文件。如果页面使用来自多个文件的脚本，我们需要选择包含了想要调试的脚本的文件。

5.添加断点。

要添加断点，单击该行代码的行号的左边。一个红色的符号出现，表示这是一个断点。

注意：给只包含注释的一行添加代码是没有效果的，调试器不会在该行停止。只能给包含真正的JavaScript代码的行添加断点。

6.重新载入Web页面。

因为必须在Firefox中查看Web页面，并且为此要打开Firebug并添加断点，我们想要调试的JavaScript可能已经运行了（在我们添加任何断点之前）。在这种情况下，需要重新载入页面，以便可以再次开始JavaScript。

如果在响应事件的一个函数中添加了一个断点（例如，我们想要调试当单击一个按钮或者把鼠标光标移到一个链接上的时候所运行的代码），那么，我们需要触发该事件，如单击按钮或者把鼠标光标移到链接上，或者通过其他的方式来触发，从而到达该断点并且开始调试过程。

在脚本开始运行之后，只要到达一个断点，脚本就会停止。程序及时冻结，等待执行断点后面的第1行。

7.使用Firebug的控件来步进地执行程序。

Firebug提供了4个控件（如图15-6所示），它们告诉程序在断点停止了之后该如何运行。我们可以阅读本小节中“用调试器控制脚本”部分对这些控件的介绍。

8.在Watch列表中监控程序条件（如图15-6所示）。

步进执行程序的目的是为了查看脚本内在任何特定的行发生了什么。Watch列表提供了关于程序条件的基本信息，并且允许我们添加要查看的条件变量。例如，如果想要跟踪在脚本运行时候变量score的值，我们可以在Watch列表中做到这一点。我们将在15.2.5节学习如何使用Watch列表。

9.在文本编辑器中修正脚本。

在步进脚本的过程中，很有可能找到脚本中哪里出错，例如，为什么一个特定变量值没有变化，或者为什了一个条件语句不会得到false。使用这些信息，我们可以跳转到文本编辑器中并修改脚本（在15.3节的教程中，我们将运行一个修改脚本的示例）。

10.在Firefox中测试页面，并且，如果必要的话，重复上述的步骤继续调试脚本。

用调试器控制脚本

一旦给脚本添加了断点并且重新载入页面，我们就准备好了一行一行地步进执行脚本。如果在页面载入的时候运行的脚本的某个部分添加了一个断点，脚本将会在断点停止；如果只是在一个事件（例如单击一个链接）后面紧接着的一行添加了一个断点，我们需要触发该事件，然后才能到达该断点。

当调试器在断点停止了程序，它不会运行那一行代码；它在运行该行代码之前就停下来。然后，我们可以单击调试器的4个按钮之一来控制调试器接下来做什么（如图15-6所示）。

·Play。Play按钮直接开始脚本的运行。在JavaScript解释器遇到另一个断点之前，或者直到脚本运行完成之前，脚本不会再次停止。如果有另一个断点，脚本再次停止下来并等待你单击调试器控件的4个按钮之一。

如果只是想要运行程序或者跳到下一个断点，使用Play按钮。

·Step Over。这个有用的选项运行当前的代码行，然后在脚本的下一行停止。它的名字叫做Step Over，因为如果当前代码行包含对一个函数的调用，它不会进入该函数。它跳过该函数并且在函数后的下一行代码停止。如果已经知道调用的函数其功能没有缺陷，这个选项是很好用的。例如，如果脚本调用一个jQuery函数，我们将跳过对该函数的调用，否则，会花很多时间看到一行一行的、令人惊讶的jQuery程序。大多数时候，我们将选择Step Over选项，除非在代码行中，调用的是我们自己创建的函数，那么，使用下面提到的Step Into选项来看看在函数中发生了什么。

·Step Into。Step Into使得调试器进入一个函数调用。也就是说，如果我们在包含了函数调用的一行上，调试器进入该函数并且在函数的第1行停下。当不确定主脚本中或者我们自己编写的一个函数中是否有问题，这个选项是一种可以采用的办法。

如果你确保将要调用的函数能够工作，例如，这个函数你此前已经用过很多次了，那么，可以直接跳过这个选项。当你在调试包含了一个jQuery选择器或命令的一行代码的时候，你也想要使用Step Over而不是Step Into。例如，$（'#button'）是选择页面上的一个元素的一种jQuery方式。然而它也是jQuery库的一个函数，因此，当你遇到一个jQuery函数的时候，如果单击了Step Into按钮，将会陷入jQuery库的复杂世界（并且，如果发生这种情况，你将会知道这种情况，因为脚本标签页会变为显示出jQuery文件的所有JavaScript代码）。

当使用调试器的时候，你会发现自己迷失在函数之中，或者迷失在jQuery这样的JavaScript库中，你可以使用下面描述的控制方法来逃脱出来。

·Step Out。Step Out按钮使得调试器走出一个函数调用。我们通常在Step Into之后使用它。当我们这么做的时候，函数正常运行，但是不会像是在常规调试模式中那样在函数中的每一行都停止。当我们单击了这个按钮，调试器返回到最初调用函数的那一行然后停下来。

查看脚本

尽管调试器顶端的按钮允许我们控制脚本如何执行，调试器的整个要点还是看看脚本中发生了什么。这就是为什么使用Watch列表（如图15-7所示）。Watch列表提供了当前指定的代码行环境中的变量和函数的一个列表。这意味着，如果在函数中放置一个断点，将会看到函数中定义的所有变量的一个列表；如果在脚本的主体中放置一个断点，将会看到在那里定义的所有变量的一个列表。我们还将看到Watch列表列出了我们所创建的所有函数。

图　15-7　Firebug的Watch列表在程序运行的时候显示了不同的变量的值。可以给该列表添加自己的表达式，该列表在窗口的顶部显示为灰色条纹

可以把这个Watch列表看做一种连续的console.log（）命令。它在一行特定的代码运行的时候，显示出一个特定变量或表达式的值。

Watch列表提供了对程序的有价值的查看，提供了一种冻结的效果，以便我们可以找到脚本中哪里发生了一个错误。例如，如果知道一个特定的变量存储了一个数值，我们可以步进地执行脚本看看变量初次创建的时候存储到其中的是什么值，并且看到在程序运行的时候是如何修改该值的。在单击了Step Over或Step Into按钮之后，如果看到变量值改变为我们意料之外的内容，那么，可能就找到了引入错误的一行代码。

15.3　调试教程

在本教程中，我们将看到使用Firebug来调试一个充满了各类错误（语法错误、运行时错误和逻辑错误）的文件。这个页面只是一个简单的测验程序，它提出了3个问题并且显示出测试结果（在任何浏览器中打开chapter15目录下的complete_debugger.html文件，来看看这个页面如何像预期的那样工作）。

注意：参见1.3节中的“注意”部分以获取如何下载教程文件的信息。

可以使用带有“New watch expression……”的黄色工具栏来添加自己的变量和函数。只要单击一下黄色工具栏，就会弹出一个文本框。输入要跟踪的变量的名字，甚至是想要执行的一条JavaScript语句。例如，由于调试器不会跟踪for循环（参见本书3.3.3节）中的一个计数器变量，我们可以添加这个变量，并且在循环一步一步执行的过程中，可以看到每次迭代循环的时候这个计数器是如何改变的。

为了完成这个教程，我们需要运行Firefox Web浏览器，安装并打开Firebug扩展，参见15.2.1节的介绍。

1.启动Firefox并打开chapter15目录下的文件debugger.html。

在浏览器窗口的右上端，Firebug显示这里有一个错误（如图15-8所示）。你必须打开Firebug控制台，以弄清楚发生了什么错误。

2.单击Firefox右上角的Firebug图标以打开Firebug，并且打开控制台。

我们可以看到如图15-8所示的错误消息“Missing]after element list”。方括号用来创建数组（参见本书2.8.1节），因此，这条消息看上去是因为一个数组的结束方括号漏掉了。这个遗漏是一个语法错误（参见15.1.1节的“快速熟悉：错误类型”部分），因为它显示了代码中的一个“语法错误”（就像是一个句子漏掉了句号）。注意错误消息的右边，Firebug告诉我们这个错误发生在第15行。

图　15-8　Firebug控制台用于跟踪那些令一个脚本轰然倒塌的语法错误和运行时错误

3.启动文本编辑器并且打开文件debugger.html。找到第15行（该行上是一个单个的；）。在；前面输入一个结束方括号，如下所示：

]；

这个方括号结束了一个嵌套数组（参见3.5节），该数组包含了测试的所有问题和答案。

4.保存文件，返回到Firefox并重新载入页面。

还有另一个错误！这次，错误控制台显示“$is not defined”并且指向包含jQuery$（document）.ready（）函数的第10行。当Firefox报告某些内容“not defined”（没有定义），这意味着代码引用了某些不存在的内容，这可能是还没有创建的一个变量或函数的名字。或者可能只是代码中的一个输入错误。在这个例子中，代码看上去很好。错误出现在页面的前面，在这段代码中：

＜script src="_js/jquery-1.6.3.min.js"＞＜/script＞

当操作外部脚本的时候，一个常见的错误是偶尔输错了到脚本的路径。在这个例子中，jquery-1.6.3min.js文件位于名为js的一个文件夹中，而该文件夹在页面所在的文件夹之外。这里的这段代码假设js文件应该和这个Web页面位于同一文件夹下，由于Firefox不能找到jquery-1.6.3.min.js文件（其中定义了jQuery的$（）函数），因此，脚本产生一个错误。

5.把＜script＞标签修改为：

＜script src="../_js/jquery-1.6.3.min.js"＞＜/script＞

../表示js文件夹位于页面的文件夹之外，并且路径现在正确地指向了该jQuery文件。这个程序还有什么错呢？

6.保存文件，返回到Firefox并重新载入页面。

没有错误，看上去这个页面已经修改好了，不是吗？

7.单击Start Quiz按钮。

哦，又是一个错误。这次，控制台报告“askQuestions is not defined”并且指向脚本末尾的第70行附近。因为这个错误只是在程序运行的时候出现，它是一个运行时错误（参见本章“快速熟悉：错误类型”部分）。这个问题出现在脚本的末尾，在下面的条件语句中：

if（quiz.length＞0）{

askQuestions（）；

}else{

giveResults（）；

}

现在为止，如果我们发现某些内容没有定义的话，常常只是因为一个简单的输入错误。在这个例子中，askQuestions（）是对一个函数的调用，因此，现在花点时间来检查代码并且尝试找到这个函数。

找到了吗？尽管没有askQuestions（）函数，我们应该注意到，有一个askQuestion（）（最后没有s）。

8.返回到文本编辑器，然后在第70行删除askQuestions（）中最后的s（靠近脚本的末尾）。保存这个文件，在Firefox中重新载入它，然后，再次单击“Start Quiz”按钮。

现在，出现了带有5个多选项的测试题。遗憾的是，最后一个选项有一个“undefined”标签（label）。看上去像是一个错误。然而，Firebug控制台是空的，因此，技术上讲，这不是JavaScript错误。程序逻辑一定出现了某种错误。要探究这个问题的根本，我们需要使用Firebug调试器。

9.在Firebug中，单击Script标签页，并且从Script标签页下面的源代码菜单中选择debugger.html（如图15-9所示）。

这个Script标签页使我们能够访问页面的JavaScript。如果页面包含了JavaScript，并且我们链接到了其他的外部JavaScript文件，源代码菜单允许我们选择要调试哪一段JavaScript代码。

由于“undefined”单选按钮似乎位置不对，因此，创建该单选按钮的代码是开始查找这个bug的好地方。如果已经编写了这段脚本，我们应该能够知道到哪里去查看代码。然而，如果我们只是处理有错误的代码，可能必须找到这段脚本。

在这个例子中，单选按钮在一个名为buildAnswers（）的函数内创建，该函数的作用是构建用单选按钮表示的一系列的多选项。这个函数接收一个数组，其中包含了一个列表，带有每个单选按钮的值。当执行完该函数，它返回包含了单选按钮的HTML的一个字符串。因此，这个函数是开始调试的好地方。

10.在Firebug的Script标签中，向下滚动直到看到第47行。单击第46行的左边来插入一个断点（如图15-9中的圆圈所示）。

在第46行的左边出现了一个红点。这个红点便是一个断点，或者说是代码中JavaScript停止运行该脚本的某个点。换句话说，当这段脚本再次运行，JavaScript解释器会在运行到这一行的时候停下来，从而我们能够一行一行地步进执行代码，看看背后到底发生了什么。

调试器还允许我们在程序运行的时候查看变量的值，这很像我们在本书15.2.3节所使用的console.log（）函数。接下来，我们将告诉Firebug想要跟踪哪个变量。

图　15-9　在Firebug中，我们可以调试当前页面使用的任何脚本。源代码菜单允许我们选择嵌入到当前Web页面中的JavaScript，或者是来自任何添加的外部JavaScript文件的代码

这个步骤给Watch列表添加了变量i。这个变量在循环中用做一个计数器来记录循环运行了多少次（参见3.3.3节了解关于for循环的更多内容）。在脚本运行的时候，我们将能够看到这个值是如何变化。接下来，我们将添加要查看的另一个变量。

12.再次单击“New Watch Expression”栏，然后输入answers.length，然后按下回车键。

不要担心Firebug此时显示的值（它可能会显示“answers is not defined”）。直到真正地运行调试器并且运行到该函数的时候，才能够跟踪该变量的值。现在来看一下脚本的内部。

13.单击Firefox的Reload按钮或者按下Ctrl+R（æ-R）。当页面重新载入的时候，单击Web页面上的“Start Quiz”按钮。

这个脚本开始了，并且第一个问题显示到了Web页面上。但是，当创建单选按钮的时候，调试器停止在了第46行（参见图15-10的上图）。注意，在Watch标签中，i的值没有定义。这是因为在该行执行之前，断点就停止了该程序。换句话说，循环还没有开始，并且i变量还没有创建。

然而，answers.length的值设置为4。数组answers是传递给函数的一个数组，其中包含了答案（我们可以在Watch列表中看到数组元素列在列表下方）。数组的length属性表示数组中项目的数目，在这个例子中是4，因此，当该函数完成的时候，我们应该得到4个单选按钮。

14.单击Step Over按钮（如图15-10所示）。

11.在Firebug窗口的右边单击“New Watch Expression”栏，输入i，然后按下回车键。

这个按钮把我们带入到程序的下一行。现在，我们可以看到i设置为0。我们将继续单击执行该循环。

图　15-10　当我们使用Firebug步进执行程序的时候，行号右边的红色圆圈表示一个断点，而黄色箭头表示JavaScript解释器当前停止的行号。单击Step Over（或Step Into）来运行该代码行，并且停止在下一行

15.单击Step Over按钮，直到我们在Watch列表中看到i的值改变为5（如图15-10的下图所示）。

尽管answers数组只有4个项，我们可以看到for循环实际运行了5次（i的值）。因此，有趣的是该循环是如何停止的。别忘了，在for循环中，for语句中间的语句是一个条件，只有该条件为true，循环才能运行（参见3.3.3节）。在这个例子中，条件是i＜=answers.length；。换句话说，该循环从包含0的i开始，并且只要i小于或等于answers数组中的项目数，就继续运行。换句话说，在循环执行之前，i将等于0、1、2、3和4，这等于运行了5次。然而，由于answers数组只有4项，第5次通过循环的时候，再没有可供显示的答案了，因此会显示“undefined”，因为answers数组中没有第5项了。

16.返回文本编辑器，将第46行的for循环修改为如下所示：

for（i=0；i＜answers.length；i++）{

现在，循环只是运行answers数组中的项目数那么多次，为每个可能的答案创建一个单选按钮。

17.保存文件，并在Firefox中预览。

可以通过在Firebug Script窗口中单击断点的红点来关闭断点，从而看到完成后的页面不受干扰地执行。

文件complete_debugger.html包含了本教程的完整版本。正如你所看到的，在一个程序中查找bug可能花费很多工作。但是，像Firebug这样的一个调试工具，使得我们看到程序内部的“机构”并且找到哪里出错变得更为容易。

附录A　JavaScript资源

本书提供了足够的信息和实用的技术，帮助你开始自己的JavaScript职业生涯。但是，没有一本书可以解答你所有的JavaScript问题。当学习JavaScript编程的时候，有太多的东西需要学习，这个附录提供一些资源供你进一步研究和学习。

A.1参考

有时候，我们读书的时候需要一本词典。当编写JavaScript程序的时候，最好有各种关键字、术语、方法和其他各类JavaScript语法的一个完整参考。可以在网上或者图书中找到这样的参考。

Web站点

·Mozilla Developer Center Core JavaScript Reference（http：//developer. mozilla.org/en/docs/Core_JavaScript_1.5_Guide）提供了对JavaScript的完整参考。它非常详细，但是，由于它以技术人员为受众，有时候会难以理解。

·来自DevGuru（www.devguru.com/technologies/javascript）的JavaScript Quick Reference是列出了JavaScript关键字和方法的一个单独页面。单击一个条目，将显示说明关键字和方法的页面。

·Google Doctype（http：//code. google.com/doctype）介绍了JavaScript、DOM和CSS，并且告诉我们每个浏览器支持哪种功能。这是Web开发者的百科全书。

·如果你只是针对Internet Explorer进行开发，那么来自Microsoft的MSDN JavaScript Language Reference（http：//msdn.microsoft.com/en-us/library/d1et7k7c（VS.85）.aspx）是一个优秀的资源。如果要开发供全世界使用的Web站点，那么可以略过这个资源，它只提供针对IE的信息。

图书

·《JavaScript权威指南》（David Flanagan著，O'Reilly出版）是JavaScript最全面的纸版的百科全书。它很厚且篇幅很大，但是它拥有全面理解JavaScript所需的所有细节。

A.2 JavaScript基础

JavaScript并不是很容易学习，并且多使用一些资料来了解Web编程的林林总总的知识也没什么坏处。下面的资源帮助你了解JavaScript语言的基础知识（JavaScript语言有时候相当难）。

文章和PPT

·Simon Willison的A（Re）-Introduction to JavaScript（www.slideshare.net/simon/a-reintroduction-to-javascript）。这个有趣的PPT提供了对JavaScript的全面概览。

Web站点

·The W3 Schools JavaScript教程（www.w3schools.com/js）是全面的（但可能不总是彻底讲解清楚的）教程，覆盖了JavaScript编程的主要方面。

·网站howtocreate.co.uk（www.howtocreate.co.uk/tutorials/javascript/introduction）的An Introduction to JavaScript详尽地介绍了JavaScript，而且是免费的。当然，如果你在使用jQuery时，可能不需要这个站点上的很多信息，因为它主要介绍了选择和操作DOM元素的传统方法。

图书

·Michael Morrison的《Head First JavaScript》（O'Reilly出版）是对JavaScript编程的生动、非常清晰的介绍。它提供了有关JavaScript如何工作以及如何用它编程的很多信息，但是，并没有提供太多直接有用的Web页面示例。

A.3 jQuery

本书的大部分内容介绍了jQuery这个JavaScript库，但是，关于这个强大的、节省时间的、有趣的程序库，还是有很多内容需要学习。

文章和PPT

·Marc Grabanski的jQuery Essentials（www.slideshare.net/1Marc/jquery-essentials）是一份有趣的且完整的PPT，介绍了jQuery的基础知识。强烈推荐。

·jQuery Tutorials for Designers（www. webdesignerwall.com/tutorials/jquery-tutorials-for-designers）这是来自知名的Web设计博客Web-Designer Wall的文章，其中介绍了我们可以用jQuery完成的漂亮的、简单的事情。

·jQuery Cheatsheet（http：//woorkup. com/2011/05/12/jquery-visual-cheat-sheet-1-6/），这是一个可以下载的PDF文件，它将所有的jQuery函数在一页中列了出来。

Web站点

·jQuery. com，精彩的jQuery JavaScript库的主页，提供了讨论论坛、文档、插件和下载。

·jQuery的文档通过一个用户生成的Wiki（http：//docs.jquery.com）来提供。在这个站点上，任何人都可以自由地添加或编辑对jQuery的众多功能的介绍，但是，有一组核心的人处理大多数的文档。这是关于jQuery的完整信息的首选来源。

·jQuery for Designers（http：//jqueryfordesigners. com）是一个站点，其中包括介绍创建有趣的效果、有用的界面以及使用jQuery对Web站点的常规改善的文章和视频教程。

·Learning jQuery（http：//www. learningjquery.com）提供了来自一些jQuery的卓越开发者的信息。

·Script Junkie（http：//msdn. microsoft.com/en-us/scriptjunkie）是针对Web开发者的微软站点。它包括大量的有关HTML、CSS和JavaScript（着重于jQuery）的文章。

图书

·《jQuery in Action》（Bear Bibeault和Yehuda Katz著，Manning出版），通过很多示例程序全面介绍了jQuery。它假设读者有一些JavaScript和编程知识。

·《jQuery Cookbook》（O'Reilly）介绍了你作为一名程序员可能面对的最常见的任务和问题的“解决方法”。由众多的作者编写，其中的很多人了解jQuery背后的思路。

·《jQuery：Novice to Ninja》（Earle Casteldine和Craig Sharkie著，Sitepoint出版），提供了使用jQuery的众多介绍，具有很多动手的示例，展示了如何处理常见的用户界面需求。

A.4 Ajax

Ajax把Web浏览器、JavaScript和服务器端编程组合到了一起，得到了3倍的乐趣（以及3倍的头疼）。幸运的是，有很多资源可以帮助学习如何使用Ajax。

Web站点

·Ajaxian（www. ajaxian.com）是了解关于Ajax、JavaScript框架以及有用的Web服务的新消息的一个很好的资源。这个站点面向专业的JavaScript程序员，但是也提供了新闻、报道，并且经常宣传那些以创新的方式使用了Ajax的站点。

图书

·Brett McLaughlin编写的《Head Rush Ajax》（O'Reilly出版），对于JavaScript和服务器端编程新手来说可能是最好的Ajax入门读物。它有趣的编写方法和配图排版，使得Ajax的所有基本概念和技术都易于理解。

A.5高级JavaScript

没错，JavaScript甚至比本书所带给你的印象要复杂很多。一旦开始精通JavaScript编程，你可能想要加强自己对于这一复杂语言的理解。

文章和PPT

·Chris Heilman的Show Love to the Object Literal（www.wait-till-i.com/2006/02/16/show-love-to-the-object-literal）是一篇简短的博客文章，它很好地介绍了JavaScript对象直接量的用法。

·Chris Pels的Do Object Oriented Programming with JavaScript（http：//msdn.microsoft.com/en-us/scriptjunkie/ff698282.aspx）提供了一个简短的视频来介绍这个复杂的主题。这是开始学习JavaScript面向对象编程的不错去处。

·Sorting a JavaScript array using array. sort（）（www.javascriptkit.com/javatutors/arraysort.shtml）对于如何排序数组的内容提供了有用的信息，包括随机化数组的快速方法（就像是洗牌）。

Web站点

·Eloquent JavaScript（http：//eloquentjavascript. net）是一个JavaScript教程站点。它组织良好，具有创新的教学方法。尽管它被认为是初学者的JavaScript教程站点，编写教程的作者就好像是在和一群计算机科学家对话，因此，如果你刚刚接触JavaScript或编程，这里不是开始学习的最好的地方。

·Christian Heilmann创建的Unobtrusive JavaScript（www.onlinetools.org/articles/unobtrusivejavascript/index.html）是一个小型的站点，它专门介绍无干扰的JavaScript概念，具体来说，就是如何让一个站点可供所有人访问（即便这些人的浏览器不支持JavaScript）。

·Douglas Crockfords站点的JavaScript部分（http：//javascript.crockford.com）提供了有关JavaScript的很多（复杂的）信息。这个站点上有很多的信息，其中的一些需要具有计算机科学专业水准才能够理解。

·Yahoo的JavaScript Developer Center（http：//developer.yahoo.com/javascript）所拥有的关于JavaScript的信息比任何其他关于Web的站点都多。很多信息都针对Yahoo自己的JavaScript库YUI以及Yahoo所提供的很多Web服务（例如Yahoo Maps）进行了调整。

·Mozilla Developers站点的JavaScript部分（https：//developer.mozilla.org/en/javascript）包含了大量的JavaScript信息，包括本附录开始提到的JavaScript参考，还有一个详细的指南（https：//developer.mozilla.org/en/JavaScript/Guide），它介绍了JavaScript的不同版本以及应用JavaScript概念的具体示例。

图书

·《JavaScript Patterns》（Stoyan Stefanov著，O'Reilly出版）。当你真的想要将JavaScript编程水准向前推进的时候，本书提供了编程“模式”，帮助你解决常见的任务，包括如何最好地使用对象直接量、JSON和数组。大量的编程不太适合初学者。

·Douglas Crockford编写的《JavaScript：The Good Parts》（O'Reilly出版）介绍了JavaScript最有用的部分，即如何避免不好的编程技术。作为Yahoo的高级JavaScript架构师，Douglas绝对知道自己在讲些什么。本书很简短而精炼，但是包含了关于如何很好地使用JavaScript的很多智慧。

A.6 CSS

如果你在学习本书，可能已经很熟悉CSS了。JavaScript真的可以利用CSS的格式化功能的优点，不仅能控制元素的外观，而且可以让它们在屏幕上活动。如果需要和回顾CSS知识，这里有一些有帮助的资源。

Web站点

·来自WestCiv的The Complete CSS Guide（www.westciv.com/style_master/academy/css_tutorial）介绍了层叠样式表的几乎所有部分，在这里，我们不会学习很多不同的技术，而是学习什么是CSS以及如何创建样式和样式表的基础知识。

·The SitePoint CSS Reference（http：//reference. sitepoint.com/css）是另一个在线CSS参考，容易使用并带有搜索引擎。

·Selectutorial（http：//css. maxdesign.com.au/selectutorial）是学习CSS选择器语法的好办法。由于jQuery主要基于使用CSS选择器的思想来操作页面的HTML，很好地理解这一概念是很值得的。

图书

·《CSS：The Missing Manual》（David Sawyer McFarland编写，O'Reilly出版）是关于层叠样式表的一本全面的、教程驱动的图书。它包括对CSS的深入介绍、真实的示例以及确保CSS在跨浏览器世界能够工作的除错技巧。

·《CSS：The Definitive Guide》（Eric Meyer编写，O'Reilly出版）。名如其书，本书如此详细地介绍了CSS，以至于如果你一直坐着阅读它的话，头脑一定会很累。

·《Stunning CSS3》（Zoe Mickley Gillenwater著，New Riders出版）是一本精心编写的图书，介绍了新的CSS 3标准的诸多方面，并且展示了高级的基于CSS 3的设计，以及高效使用CSS 3的众多技术。