JavaScript面向对象编程指南

978-7-115-30904-4
作者: 【加】Stoyan Stefanov
译者: 凌杰
编辑: 陈冀康

图书目录:

详情

本书介绍了JavaScript编程的众多泛型之一,面向对象编程,帮助读者了解到JavaScript编写更多不同功能的应用程序的技巧。本书也有专门一章介绍Web浏览器应用环境编程。本书不需要读者有任何JavaScript知识,适合初学者。

图书摘要

JavaScript面向对象编程指南

〔加〕Stoyan Stefanov 著

凌杰 译

人民邮电出版社

北京

JavaScript是一种具有高度表达能力的、基于原型特性的、非常灵活的面向对象编程语言。本书着重介绍 JavaScript 在面向对象方面的特性,展示如何构建强健的、可维护的、功能强大的应用程序及程序库。

全书包括8章和3个附录,依次介绍了JavaScript的发展历史、基础性话题(变量、数据类型、数组、循环以及条件表达式)、函数、对象、原型、继承的实现、BOM和DOM等。附录部分包括了学习JavaScript编程常用的参考资源。尤其值得一提的是,本书作者是JavaScript设计模式方面的专家,他在本书第8章中介绍了几种常用的JavaScript编程模式,这也为他后续的另一本重要著作《JavaScript Patterns》奠定了基础。

本书全面地覆盖了JavaScript语言的OO特性,同时兼顾基础知识,对初学者来说,是难得的JavaScript佳作。阅读本书不需要读者具备任何的JavaScript基础知识及项目经验,通过学习本书,读者将会在面试有关JavaScript程序设计的职位时游刃有余。

相对于Perl、Python等动态脚本语言来说,JavaScript确实是一门饱受误解的语言。对于译者这种从20世纪90年代末走过来的C++程序员来说,尤其如此。在那个年代,提起JavaScript总是会让人联想起各种花哨的小玩意儿、令人讨厌的恶作剧、浏览器之间的恶斗(例如IE与Netscape)等令人不太愉快的场景。总而言之,我长期以来对JavaScript的评价基本上是比较负面的,认为那不过是一个旁门左道的、不务正业的玩具罢了。

但随着AJAX技术在21世纪最初10年里的爆炸性发展,人们突然意识到JavaScript原来也可以给Web页面带来如此令人惊叹的用户体验,如果说在这之前,大部分Web页面还都只不过是一些被动供人阅读的电子刊物的话,如今它们已经真正成为了一个“实用程序”,可以具有即时交互能力,可以接受个性化定制,等等。总之,您在桌面程序中能做到的,它基本都已经做到了。这的确出乎许多人的预料,包括我。

人们不禁要问了,似乎AJAX的核心XMLHttpRequest对象并不是一个新鲜事物呀,为什么直到最近才发现这种用法呢?这其中的原因是多样的。例如,如果没有宽带产业的发展,即便是发送JSON这种轻量级数据所带来的延时成本也是不可想象的。但无论如何,译者认为其中最重要的原因应该还是语言本身的标准化。如果没有ECMA-262这份标准文档,各大浏览器在客户端的表现完全不一致,我们就没有办法对Web应用实行MVC模式,以实现行为、外观、内容三者的分离。因为那样的话,开发成本显然是不能接受的。

而一旦我们在程序设计中使用了 MVC 模式,就意味着我们的设计思路已经进入面向对象的领域。面向对象思维在程序设计方法学上是具有划时代意义的。它标志着我们代码的重用目标由实现转向了接口,从而形成了模块化编程的趋势。这有利于我们随时更换不同的实现模块,以使得我们的代码应对变化的能力更强,也更容易维护。因而,各种成本的降低加速了这种编程方式的商业化。

JavaScript的面向对象特性不同于其他任何一种语言,从某种意义来说,它面向“对象”的程度比任何一种语言都要彻底(因为它差不多除了“对象”什么也没有)。这使得它更为灵活,更为动态化,也更具可挖掘性。现在,是时候重新学习 JavaScript 这门语言以及其面向对象特性了。希望这本书给你带去有用的信息。

翻译一本书从来都不是一个人的功劳,在此感谢人民邮电出版社的陈冀康编辑和微软亚洲研究院徐宁工程师在本书翻译过程中给予我的帮助与鼓励。特别是徐宁工程师还承担了本书第8章的初稿翻译工作,感谢他的付出。希望这本书也能给你们带来快乐。

凌杰

2012年11月5日于新安江畔

这本书旨在介绍 JavaScript 语言,这是一种具有高度表达能力的、基于原型特性的、非常灵活的面向对象程序设计语言。只要我们摒弃之前设计师所做的那种类似翻滚按钮的玩具思路,这种有趣的、独特的语言就会重新焕发活力,并且远胜往昔,今天的 Web 2.0世界中的 AJAX、胖客户端程序设计、丰富的仿桌面型网络应用程序、缩放式地图以及基于Web的邮件客户端基本上都依赖于JavaScript所带来的高交互性用户体验。如果说之前没有什么机会能让我们对 JavaScript 这种语言产生足够的关注,那么现在是时候该坐下来好好学习(或复习)它了。

另外,这本书并不会假设读者具备任何的 JavaScript 基础知识及项目经验。你完全可以从零开始来学习这门语言。

本书所涵盖的内容

第1章简单阐述了 JavaScript这门语言的历史、现状及未来。另外,我们还对面向对象程序设计中的一般性基础概念做了一些介绍,并详细说明了该语言调试环境(Firebug)的安装、设置及应用示范。

第2章讨论语言中的一些基础性话题,包括变量、数据类型、数组、循环以及条件表达式。

第3章讨论的是函数。JavaScript中有许多功能都需要通过函数来完成。在这一章中,我们将系统地学习关于函数的一切内容。另外,我们还需要了解变量作用域以及内建函数的相关内容。其中有一个叫做闭包的概念非常有趣,但也很不容易理解,在该章末尾,我们会重点介绍。

第4章介绍的是对象。在这一章中,我们学习了如何使用对象的属性与方法,以及创建对象的各种方法。另外,我们还会带你预览JavaScript中的内建对象,例如Math、Date等(更详细的内容则放在附录C中)。

第5章将介绍JavaScript中有关原型的所有重要概念。

第6章旨在突破传统的“JavaScript思维”,将讨论如何在JavaScript中实现继承。

第 7章介绍的是浏览器。在这一章中,我们将会了解到有关 BOM(Browser Object Model)和DOM(Document Object Model)的知识,并进一步了解与浏览器事件和AJAX相关的内容。

第 8 章归纳了几种专用于 JavaScript 的编程模式,以及若干个与语言无关但适用于JavaScript 的设计模式。这些模式大部分都选自 GoF 那本《设计模式》中介绍的通用软件设计模式。

附录A列出的是JavaScript中所有的保留字。

附录B是一份JavaScript中内建函数的参考指南,并附简单的使用范例。

附录C是一份JavaScript中内建对象的参考指南,它提供了详细的对象方法与属性介绍和使用示例。

附录D是一份正则表达式模式的参考指南。

一些约定

在这本书中,读者会发现几种不同样式的文本,它们各自代表了不同类型的信息。下面,我们将通过一些文本实例来解释一下这些样式各自所代表的含义。

对于一段文本中的代码,我们将以如下形式来表现:“对于 key/value 对,我们将以冒号分割的方式来表示,具体格式形如 key : value。”

而对于代码块文本,我们将采用如下格式:

var book = {

 name: 'Catch-22',

 published: 1961,

 author: {

  firstname: 'Joseph',

  lastname: 'Heller'

 }

};

当需要提醒你注意代码中的某一部分时,我们会将相关行或项目的字体加粗,例如:

function TwoDShape(){}

// take care of inheritance

TwoDShape.prototype = Shape.prototype;

TwoDShape.prototype.constructor = TwoDShape;

另外,加粗字体还经常用于强调新的条件或重要词汇,例如,我们屏幕上、菜单中以及对话框中会看到的单词。这时候,我们经常会这样表述“通过单击Next按钮,我们就转到下一个屏幕”。

这种形式表达的是一些需要读者警惕的或需要重点关注的内容。

这种形式所提供的是一些提示或小技巧。

读者反馈

我们始终欢迎任何来自读者的反馈信息。它能让我们了解您对于这本书的看法——无论是喜欢还是不喜欢。这些反馈对于我们的选题开发来说都是至关重要的。

对于一般的反馈,您只需简单地给 feedback@packtpub.com 发一份电子邮件,并在邮件的标题中注明这本书的书名即可。

如果希望我们出版某一本您需要的书,您可以通过 www.packtpub.com 网站,或者suggest@packtpub.com发送电子邮件以告诉我们这本书的标题。

如果您对某一话题有专长,并且有兴趣写(或奉献)一本这方面的书,请参考我们的作者指南:www.packtpub.com/authors。

客户支持

您一直都是Packt图书的主人,我们将会尽一切努力来帮助您获取最好的图书资讯。

勘误表

尽管我们已经尽了最大的努力来确保书中内容的正确性,但错误始终是存在的。如果您在我们的书中发现了错误——无论是关于文字的还是代码的——只要您能告诉我们,我们都将不胜感激。因为这样可以大大减少其他读者在阅读方面所遇到的困难。因此,当您发现错误时,只需要访问 http://www.packtpub.com/support,选择相应的书名,然后单击“let us know”链接并输入相关错误的详细信息即可。一旦您提供的信息获得了确认,相关的内容就会出现在这本书的勘误表中。我们出版社所有现存的勘误表都可以在http://www.packtpub.com/support中通过选择所要查询的书来获取。

疑问

如果您对本书有任何疑问,也可以通过 questions@packtpub.com 跟我们联系,我们会竭尽所能地帮您解决问题。

Stoyan Stefanov:雅虎公司的 Web开发人员、Zend认证工程师。他经常会在其博客(www.phpied.com)与一些相关会议中就JavaScript、PHP等Web开发话题发表独到见解。他还独自运营着其他一些网站,其中包括JSPatterns.com——一个用于探讨JavaScript模式的网站。除此之外,Stoyan 还是雅虎性能优化工具的项目领导人,同时参与了多个开源项目,例如Firebug和PEAR。

Stoyan是一位“世界公民”,出生并成长于保加利亚,但却是一个加拿大公民,现居住于美国加利福尼亚州的洛杉矶。在短暂的线下时间里,他喜欢弹吉他,与家人一起去圣莫尼卡海滩或待在游泳池边。

Dan Wellman和他的妻子及三个孩子住在他的家乡,英格兰南海岸的南安普顿。白天,他隐蔽身份在一家麻雀虽小五脏俱全的电子商务产品代理机构工作。到了晚上,他为了真理、正义,以及更少的恶意JavaScript脚本,与黑暗势力进行战斗。

他在撰写计算机相关的文章、教程和评论方面已经有大约五年的经验,可以说是一个“吃键盘饭”的专家。

Douglas Crockford是一家美国公共教育系统的产品经理,一个已登记的选民,一辆小汽车的主人。他同时也是 JavaScript领域中最权威的专家,《JavaScript: The Good Parts》一书的作者。他开发过办公自动化系统,在雅达利公司研究过游戏和音乐,在卢卡斯电影公司担任过技术总监,并且担任过派拉蒙的新媒体部门的总监。

他曾是电子社区Communities.com的创始人和CEO。他曾是State软件公司的创始人和CTO,并在工作期间创造了JSON,一种数据交换的标准。他现在是雅虎公司的架构师。

Gamaiel Zavala先生是雅虎公司加利福尼亚圣莫尼卡分部的前端工程师。他热衷于编写各种类型的代码,并着力于全局性地理解:如何从协议和数据包开始,最终构成一整套前端用户体验。在极客生活之外,他也非常享受与他可爱的妻子和孩子的家庭生活。

Jayme Cousins先生一获得地理学专业的毕业文凭,就开始创建一个商业网站。他的项目涉及销售细分市场的空间分析软件,为他的城市的报纸通宵准备在线内容,在地图上打印路名,油漆房屋,以及为成年人教授大学技术教程。他现在和他的妻子 Heather 及刚诞生的宝宝 Alan 一起,躲在加拿大安大略省南部的伦敦市幸福地生活。Jayme 曾审过 Packt出版公司出版的《Learning Mambo》一书。他喜欢把技术应用于真实世界的应用程序,为真实世界的人们提供服务,并且经常感到自己的天职是为创业者将艰涩难懂的技术翻译为浅显的语言。

Jayme现在通过他自己的 In House Logic(www.inhouselogic.omc)提供网站开发、咨询及技术培训。

Julie London是一个拥有超过八年搭建企业级网站应用程序的资深软件工程师。她曾经做过很多年的Flash开发,不过现在专注于其他的客户端技术,例如CSS、JavaScript和XSL。她目前在雅虎洛杉矶分部担任前端工程师。

Nicholoas C. Zakas担任雅虎首页的主力前端工程师,YUI(Yahoo! UI Library,雅虎界面库)的代码贡献者,以及雅虎公司 JavaScript技术的讲师。他也是《Professional JavaScript for Web Developers》和《Professional Ajax》这两本书的作者,以及 JavaScript方面众多在线技术文章的作者。

Nicholas 从一个小型软件公司的网络管理员开始了他的职业生涯。在完全转型为软件工程师之前,他还担任过用户界面设计师及原型设计师。他在2006年从马萨诸塞州搬到了硅谷并加入了雅虎。现在可以通过他的网站:www.nczonline.net联系到他。

Nicole Sullivan是住在加利福尼亚的一位CSS性能调优的领军人物。追溯到 2000年,她开始踏入这个领域的原因,是她未婚夫(现在是一个W3C员工)告诉她,由于她的网站不能通过CSS验证导致自己夜不能寐。她认为自己最好搞清楚他所说的“验证器”究竟是什么。从那时起,她就萌发了对CSS标准的热爱。

她开始搭建符合508法案的网站。随着她对于性能调优和大型网站的偏爱,她转到了在线销售业务领域,为很多家知名欧洲及世界品牌搭建CSS架构解决方案,例如SFR、Club Med、法国国家铁路公司、法国邮政、FNAC、雅高酒店集团、雷诺等。

Nicole 现在在雅虎公司异常处理小组工作。她的职责涉及研究和传播性能方面的最佳实践,以及创建如YSlow这样能帮助其他前端工程师创建更好的站点。

Philip Tellis是一个在雅虎公司工作的懒散的极客。他喜欢让计算机为他工作;如果计算机做不到,他就会自己改写程序。

在写代码之外,Philip会沿着硅谷骑车,以及做饭……当然不是同时做。

Ross Harmes在加利福尼亚的旧金山担任Flickr的前端工程师。他也是《Pro JavaScript Design Patterns》一书的作者。他的一些技术文章和在线项目,例如YUI针对TextMate的附件包,发表在www.techfoolery.com上。

Tenni Theurer在 2006年年初作为一个技术布道师加入了雅虎异常处理小组。她之后以一个经理的身份接管并发展了一个工程师小组,在帮助雅虎的产品获得更快的访问速度及更好的用户体验方面做出了杰出的贡献。Tenni现在是雅虎搜索分发组的资深产品经理。Tenni曾在多个会议上发表演讲,包括Web 2.0博览会、Ajax体验、富Web体验、Ajax世界、BlogHer、WITI及CSDN-DrDobbs等。她是雅虎开发者网络及雅虎用户界面博客的特邀客座博主。在加入雅虎之前,Tenni在IBM的普适计算组研究企业级移动解决方案,直接为高端用户解决大规模实施上的问题。

Wayne Shea是一位在雅虎工作的软件工程师。他在雅虎的项目包括研究如何改进移动网站的性能,以及开发可扩展的高性能Web服务。在加入雅虎之前,他在Openwave和Access公司致力于为手机创建移动浏览器。

Yavor Paunov是保加利亚索菲亚技术大学和加拿大蒙特利尔协和大学的共同协作项目的产品经理。他经历过从两人小组的创业项目到跨国公司的各种项目。工作之外,Yavor还喜欢听现场音乐会,以及和他迷人的喜欢咬鞋子的可卡犬一起散步。

众所周知,时下所流行的这些Web应用,例如Yahoo! Maps、Google Maps、Yahoo! Mail、My Yahoo!、Gmail、Digg以及YouTube等都有一些明显的共同特征,即:它们都是Web2.0时代的应用程序,都有非常丰富的人性化交互界面,而这往往意味着大量的JavaScript应用。事实上, JavaScript最初也只不过是一种内嵌于HTML语句中的单行式脚本语言。但如今已经今非昔比了,对于它今天所拥有的面向对象特性来说,无论是在可重用性方面,还是在可扩展性方面都已经足以支持我们去实现任何网站项目中的行为逻辑了。毕竟,对于今天的标准来说,任何一个符合规范的Web页面都应该包含以下三个要素:内容(HTML)、外观(CSS)和行为(JavaScript)。

通常来说,JavaScript程序的运行必须要依赖于某个宿主环境。其中最常见的当然就是我们的Web浏览器了,但请注意,浏览器并不是JavaScript代码唯一的宿主环境。事实上,我们可以利用JavaScript来创建各种类型的插件工具、应用扩展以及其他形式的组件。总之,学习JavaScript语言是一件一举多得的事情,我们可以通过学习这种语言,来编写各种不同的应用程序。

这本书将着重于介绍 JavaScript 语言本身,特别是其中的面向对象特性。我们会从零开始讲解这些内容,也就是说,读这本书无需具备任何的程序设计基础。另外,除了有一章内容是专门为Web浏览器环境而写的以外,本书其余部分介绍的都是JavaScript的一般特性,适用于任何支持该语言的执行环境。

现在,让我们进入第 1 章的学习吧。首先,我们需要先来了解一下 JavaScript 背后的发展历程,而后我们才能逐步引入面向对象编程方面的基本概念。

起初,Web站点事实上只不过是一个静态的HTML文档集,这些文档之间仅依靠一些简单的超链接(hyperlinks)绑定在一起。但很快,随着Web业务的快速普及和增长,网站管理者越来越希望自己所创建的Web页面能处理更多的事情。例如,他们希望网站具有更丰富的用户交互能力,以便能完成一些简单的任务(比如验证表单之类),加强与服务器端的信息交互。那时候,他们有两种选择:Java applets(后来被证明失败了)和LiveScript。其中,LiveScript就是1995年由Netscape公司开发的程序设计语言。Netscape 2.0之后,它正式被更名为JavaScript。

不久,这种对Web页面中静态元素进行扩展的方式就在业界大放异彩,令其他的浏览器厂商也纷纷效仿,推出了自己的类似产品。例如,Microsoft公司随后就发布了支持JScript的 Internet Explorer(IE)3.0。该语言在 JavaScript的基础上引入了一些 IE独有的特性。最终,为了使语言的实现更趋向于标准化,一个叫做ECMAScript(欧洲计算机制造商协会)的组织应运而生了。这才有了我们今天所看到的这份被叫做ECMA-262的标准文档。目前在业界广为流行的JavaScript也只是遵守该标准的一种实现而已。

无论结果是好是坏,JavaScript在随后爆发的第一次浏览器大战(大约是在1996年到2001年间)中得到了迅速的普及。那时正值互联网发展的第一波繁荣期,主要由Netscape和Microsoft这两大浏览器厂商在争夺市场份额。在此过程中,他们不断地往各自的浏览器中添加新的特性和各种版本的 JavaScript 实现。但他们彼此之间又缺乏共同遵守的标准,这给 JavaScript 的开发带来大量的负面影响,也给开发人员带来巨大的痛苦。因为在这种情况下,我们通常只能针对某一个具体的浏览器来编写脚本。如果我们把在这个浏览器上开发的脚本拿到其他浏览器上测试,就会发现它们完全不能工作。与此同时,由于浏览器厂商都在忙于继续增加新的浏览器特性,以至于根本没能及时更新相应的工具,这导致了开发工具的严重滞后。

尽管浏览器厂商引入的不兼容性使Web开发人员感到难以忍受,但这还只是问题的一个方面。而另一方面的问题则出在开发人员自己身上,他们在自己的Web页面中使用了太多的新特性,总是迫不及待地想引入浏览器提供的每一项新功能,以“加强”自己的页面。例如状态栏中的动画、闪烁的颜色、闪烁的文本、会摇晃的浏览器窗口、屏幕上的雪花效果、能跟踪对象的鼠标光标等,而这往往都是以牺牲实用性为代价的。这种滥用现象极大地损坏了JavaScript在业界的名声,以至于那些“真正的程序员”(这里特指那些具有更成熟的编程语言背景的开发人员,例如Java或C/C++程序员)对JavaScript根本不屑一顾,或者仅仅把它当做一种用于前端设计的玩具。

出于上述原因,JavaScript 语言在一些 Web 项目中遭到了强烈抵制。某些项目甚至完全拒绝在客户端上进行任何的程序设计,转而只信任他们自己可以掌控的服务器端。确实,在当时的情况下,也没有什么理由值得我们花费双倍的时间来为这些不同的浏览器设计项目,然后再花更多的时间去调试它们。

这种情况一直持续到第一次浏览器大战结束。但在随后的几年中,Web开发领域在一系列历史进程的推动下,终于发生了一些非常积极的变化。

◆ Microsoft公司赢得了战争,但在之后的五年中(这或多或少算得上一个互联网时代了),他们停止了继续向 Internet Explorer和 JScript中添加新特性的动作,这给了其他浏览器充分的时间,使它们能够在功能上逐步完成对IE的追赶和超越。

◆ Web 标准在移动开发领域的重要性在开发人员和浏览器厂商那里得到一致的认可。这是很自然的,毕竟对于开发人员来说,谁也不想因为不同的浏览器而花费双倍(甚至更多)的开发时间,这促使各方都越来越倾向于遵守统一的开发标准。尽管目前,我们离建立一个完全统一的标准化环境还有很长的路要走,但目标已经很明确了,相信终会有实现的那一天的。

◆ 开发人员和技术本身也日趋成熟了,更多的人开始将注意力转移到东西本身的可用性上,并以此为基础,逐步加强在技术和功能方面的开发力度。

在这种健康环境的影响下,开发人员开始谋求一种更好的新型开发模式,以取代这些现有的开发方式。而随着类似Gmail和Google Maps这样的应用程序的相继出现,客户端的程序设计也开始逐渐变得丰富起来。显然,如今的 JavaScript 已经成为一种成熟的、在某些方面独一无二的、具有强大原型体系的面向对象语言。关于这点,最好的例子莫过于是对XMLHttpRequest对象的重新发现和推广,该对象起初不过是一个IE-only特性,但如今已经得到绝大多数浏览器的支持。通过XMLHttpRequest对象,JavaScript可以用HTTP请求的形式从服务器上获得所需的新鲜内容,实现了页面的局部更新。这样一来,我们就不必每次都刷新整个页面。随着 XMLHttpRequest 对象的广泛应用,一种类桌面式的 Web应用程序模式诞生了,我们称之为AJAX的应用程序。

关于 JavaScript 语言,最有意思的是它必须要在一个宿主环境中运行。其中受欢迎的宿主环境当然就是浏览器了,但这并不是我们唯一的选择。JavaScript完全可以运行在服务器端、桌面以及富媒体环境中。如今,我们可以使用JavaScript来实现以下功能:

◆ 创建具有强大而丰富的Web应用程序(这种应用程序往往运行于Web浏览器中,例如Gmail)。

◆ 编写类似 ASP 这样的服务器端脚本,或者使用 Rhino(这是一种用 Java 实现的JavaScript引擎)这样的框架进行编程。

◆ 创建某些富媒体式的应用程序(如Flash、Flex),这其中用到的ActionScript就是一种基于ECMAScript标准的脚本语言。

◆ 编写Windows桌面自动化管理脚本任务,我们可以使用Windows自带的脚本宿主环境。

◆ 为一些桌面应用程序编写扩展或插件,例如Firefox、Dreamweaver、Fiddler。

◆ 创建一些桌面型Web应用程序,这些应用程序往往会使用离线型数据库来存储信息,例如Google Gears。

◆ 创建Yahoo! Widgets、Mac Dashboard这样的小工具或某些桌面型Adobe Air应用程序。

当然,这里列出的也远远不是该语言应用的全部。JavaScript 应用的确发端于 Web 页面,但如今,几乎可以说它们已经无所不在了。

对于未来的情况,我们在这里只能做一些猜测。但几乎可以肯定地说,JavaScript语言必将还会有它的一席之地。毕竟,在过去相当长的一段时间里,JavaScript在被严重低估、始终未得到充分利用(或者被错误地滥用了)的情况下,依然几乎每天都能有很多新的、有趣的JavaScript应用被开发出来。尽管它们都是一些简单的、内嵌于HTML标签中(例如onclick事件)的单行式代码。而如今的开发人员所面对的商业开发往往要复杂得多,这需要良好的设计和规划,以及合适的应用扩展和程序库。JavaScript必将在其中得到真正的用武之地,开发人员无疑会更加重视它独有的面向对象特性,以获取越来越多的便利。

一旦JavaScript成为未来招聘中的“必需项”,您在这方面的知识储备就会成为能否成功应聘某些 Web 开发职位的决定性因素。例如,我们在面试时常会被问到这样的问题:“JavaScript是一种面向对象语言吗?如果是,您在JavaScript中是如何实现继承的呢?”读了这本书之后,您就会对这样的面试有充分的准备,并有可能凭借一些连面试官自己都不知道的知识而打动他们。

在我们深入学习 JavaScript 之前,首先要了解一下“面向对象”的具体含义,以及这种程序设计风格的主要特征。下面我们列出了一系列在面向对象程序设计(OOP)中最常用到的概念:

◆ 对象、方法、属性

◆ 类

◆ 封装

◆ 聚合

◆ 重用与继承

◆ 多态

现在,让我们来进行逐一阐述。

既然这种程序设计风格叫做面向对象,那么它的重点就在于对象。而所谓的对象,实质上是指“事物”(包括人和物)在程序设计语言中的表现形式。这里的“事物”可以是任何东西(如某个客观存在的对象,或者某些较为抽象的概念)。例如,对于猫这种常见对象来说,我们可以看到它们具有某些明确的特征(如颜色、名字、体型等),能执行某些动作(如喵喵叫、睡觉、躲起来、逃跑等)。在OOP语义中,这些对象特征就叫做属性,而那些动作就称之为方法。

此外,我们还有一个口语方面的类比

注释:这里应该特指英文环境,在中文这种形而上的语言环境中,这种类比或许并不是太合适。——译者注

◆ 对象往往是用名词来表示的(如book、person)

◆ 方法一般都是些动词(如read、run)

◆ 属性值则往往是一些形容词

我们可以来试一下。例如,在“The black cat sleeps on my head”这个句子中,“the cat”(名词)就是一个对象,“black”(形容词)则是一个颜色属性值,而“sleep”(动词)则代表一个动作,也就是OOP语义中的方法。甚至,为了进一步证明这种类比的合理性,我们也可以将句子中的“on my head”看做动作“sleep”的一个限定条件,因此,它也可以被当做传递给sleep方法的一个参数。

在现实生活中,相似的对象之间往往都有一些共同的组成特征。例如蜂鸟和老鹰都具有鸟类的特征,因此它们可以被统称为鸟类。在OOP中,类实际上就是对象的设计蓝图或者制作配方。“对象”这个词,我们有时候也叫做“实例”,所以我们可以说老鹰是鸟类的一个实例。我们能基于相同的类创建出许多不同的对象。因为类更多的是一种模板,而对象就是在这些模板的基础上被创建出来的。

注释:至少在中文环境中,老鹰更像是鸟类的一个子类。希望读者在理解对象与类的关系时,不要过分依赖这种类比。——译者注

但是我们要明白,JavaScript与C++或Java 这种传统的面向对象语言不同,它实际上压根儿没有类。该语言的一切都是基于对象的,其所依靠的是一套原型系统(这里的原型(prototype)实际上也是一种对象,我们稍后再来详细讨论这个问题)。在传统的面向对象语言中,我们一般会这样描述自己的做法:“我基于Person类创建了一个叫做Bob的新对象。”,而在这种基于原型的面向对象语言中,我们则会这样描述:“我将现有的 Person 对象扩展成了一个叫做Bob的新对象。”

封装则是另一个OOP相关的概念,它主要用于阐述对象中所包含(或封装)的内容,它们通常由两部分组成:

◆ 相关的数据(用于存储属性)。

◆ 基于这些数据所能做的事(所能调用的方法)。

但除此之外,封装这个术语中还包含了一层隐藏信息的概念,这完全是另一方面的问题。因此,我们在理解这个概念时,必须要留意它在具体的OOP语境中的含义。

以一个MP3播放器为例。如果假设这是一个对象,那么作为该对象的用户,我们无疑需要一些类似于像按钮、显示屏这样的工作接口。这些接口能帮助我们使用该对象(如播放歌曲之类)。至于它们内部是如何工作的,我们并不清楚,而且多数情况下也不会在乎这些。换句话说,这些接口的实现对我们来说是不可见的。同样的,在OOP中也是如此。当我们在代码中调用一个对象的方法时,无论该对象是来自我们自己的实现还是某个第三方库,我们都不需要知道该方法是如何工作的。在编译型语言中,我们甚至都无法查看这些对象的工作代码。而由于 JavaScript 是一种解释型语言,源代码是可以查看的。但至少在这个概念上它们是一致的,即我们只需要知道所操作对象的接口,而不必去关心它的具体实现。

关于信息隐藏,还有另一方面内容,即方法与属性的可见性。在某些语言中,我们能通过 public、private、protected 这些关键字来限定方法和属性的可见性。这种限定分类定义了对象用户所能访问的层次。例如,private 方法只有其所在对象内部的代码才有权访问,而public方法则是任何人都能访问的。在JavaScript中,尽管所有的方法和属性都是public的,但是我们将会看到,该语言还是提供了一些隐藏数据的方法,以保护程序的隐密性。

所谓聚合,有时候也叫做组合,实际上是指我们将几个现有对象合并成一个新对象的过程。总之,这个概念所强调的就是这种将多个对象合而为一的能力。通过聚合这种强有力的方法,我们可以将一个问题分解成多个更小的问题。这样一来,问题就会显得更易于管理(便于我们各个击破)。当一个问题域的复杂程度令我们难以接受时,我们就可以考虑将它分解成若干子问题区,并且必要的话,这些问题区还可以再继续分解成更小的分区。这样做有利于我们从几个不同的抽象层次来考虑这个问题。例如,个人电脑是一个非常复杂的对象,我们不可能知道它启动时所发生的全部事情。但如果我们将这个问题的抽象级别降低到一定的程度,只关注它几个组件对象的初始化工作,例如监视器对象、鼠标对象、键盘对象等,我们就很容易深入了解这些子对象情况,然后再将这些部分的结果合并起来,之前那个复杂问题就迎刃而解了。

我们还可以找到其他类似情况,例如Book是由一个或多个author对象、publisher对象、若干chapter对象以及一组table对象等合并(聚合)而成的对象。

通过继承这种方式,我们可以非常优雅地实现对现有代码的重用。例如,我们有一个叫做Person的一般性对象,其中包含一些姓名、出生日期之类的属性,以及一些功能性函数,如步行、谈话、睡觉、吃饭等。然后,当我们发现自己需要一个Programmer对象时,当然,这时候你可以再将Person对象中所有的方法与属性重新实现一遍,但除此之外还有一种更聪明的做法,即我们可以让Programmer继承自Person,这样就省去了我们不少工作。因为Programmer对象只需要实现属于它自己的那部分特殊功能(例如“编写代码”),而其余部分只需重用Person的实现即可。

在传统的OOP环境中,继承通常指的是类与类之间的关系,但由于JavaScript中不存在类,因此继承只能发生在对象之间。

当一个对象继承自另一个对象时,通常会往其中加入新的方法,以扩展被继承的老对象。我们通常将这一过程称之为“B继承自A”或者“B扩展自A”。另外对于新对象来说,它也可以根据自己的需要,从继承而来那组方法中选择几个来重新定义。这样做并不会改变对象的接口,因为方法的名字是相同的,只不过当我们调用新对象时,该方法的行为与之前不同了。我们将这种重定义继承方法的过程叫做覆写。

在之前的例子中,我们的Programmer对象继承了上一级对象Person的所有方法。这意味着这两个对象都实现了“talk”等方法。现在,我们的代码中有一个叫做Bob的变量,即便是在我们不知道它是一个 Person 对象还是一个 Programmer 对象情况下,也依然可以直接调用该对象的“talk”方法,而不必担心这会影响代码的正常工作。类似这种不同对象通过相同的方法调用来实现各自行为的能力,我们就称之为多态。

如果您在面向对象程序设计方面是一个新手,或者您不能确定自己是否真的理解了上面这些概念,请不必太担心。以后我们还会通过一些代码来为您具体分析它们。尽管这些概念说起来好像很复杂、很高级,但一旦进入真正的实践,事情往往就要简单得多。

话虽如此,但还是先让我们再来复习一下这些概念吧(见表1-1)。

表1-1

(续表)

在这本书中,我们在代码编写方面更强调的是“自己动手”,因为在作者的理念中,学好一门编程语言最好的途径就是编写代码。因此,这里没有任何的代码可以供您下载,以便直接复制/粘贴。正好相反,您必须亲自输入代码,并观察它们是如何工作的,需要做哪些调整,然后周而复始地摆弄它们。因而,当您在尝试这些代码示例时,我们建议您使用Firebug控制台这样的工具。下面就让我们来看看这些工具是如何使用的。

获取所需的工具

作为开发人员来说,他们的机器上大多都应该已经安装了 Firefox,并将它作为日常浏览器来使用。如果您还没有安装该Web浏览器,那么现在可以安装一个了。它完全免费,适用于任何平台——Windows、Linux 或者 Mac 都可以,您可以到 http://www.mozilla.com/firefox/中去下载。

Firefox是一款扩展性很强的浏览器,如图1-1所示,它拥有大量可用的扩展插件(几乎全是用JavaScript实现的)。Firebug就是其中比较受欢迎的一款扩展组件——事实上,该组件也是 Web 开发中不可或缺的工具,它提供了大量的、可用性很强的特性。您可以在http://www.getfirebug.com/中下载它,安装完成之后,我们可以在任何网页下按F12(Windows下)或者单击Firefox窗口右上角的小虫子按钮来启动Firebug 界面。而这个界面就是我们接下来的重点——控制台。

图1-1

我们可以直接在 Firebug 控制台中输入代码,然后只要按 Enter 键,代码就会如期执行。而代码返回值就紧接着会在控制台中被打印出来。而且,这些代码会在当前所载入的页面环境中进行,例如,我们输入document.location.href就会得到当前页面的URL。

此外,该控制台还具有一套自动完成功能,其工作方式与我们平时所用的操作系统命令行类似。举个例子,如果我们在其中输入docu,然后按Tab键,docu就会被自动补全为document。这时候如果再输入一个“.”(点操作符),我们就可以通过重复按Tab键的方式来遍历document对象中所有可调用的方法和属性。

通过上下箭头键,我们可以随时从相关的列表找回已经执行过的命令,并在控制台重新执行它们。

通常情况下,控制台只能提供单行输入,但我们可以用分号分割的方式来执行多个JavaScript语句。如果还需更多的空格和语句行的话,我们也可以通过单击输入行上方状态栏右边的那个向上箭头键,来打开控制台的多命令行模式。如图1-2所示:

在图1-2中,我们通过一些代码将Google主页的logo换成了自己的图片。如您所见,我们可以在任何页面中测试我们的JavaScript代码。

此外,我们还应该设置一下Firefox中的配置选项,以便使控制台中的JavaScript警告等级更为严格。这将会有助于我们写出更高质量的代码,尽管警告并不等同于错误,但我们也应该尽量避免在编写代码时出现警告信息。例如,使用未声明变量并不是一个错误,但也不是一个好主意。因此JavaScript引擎应该会产生一个警告信息。如果我们将警告等级设置为“严格”,该信息就会在控制台中显示出来(见图1-2)。下面,就让我们来设置一下:

图1-2

1.在Firefox地址栏中输入about:config。

2.在过滤器的搜索栏中输入strict,并按Enter键。

3.双击javascript.options.strict选项所在的行,将其值改为true。

在这一章中,我们介绍了 JavaScript 这门语言的发展历程和现状。然后,我们还对面向对象程序设计的概念进行了一些基本论述。接着,我们向您详细阐述了为什么JavaScript不是传统的面向对象语言,而是一套独特的原型系统。最后,我们还介绍了本书的训练环境——Firebug控制台的设置与运用。现在,您已经为下一步深入学习JavaScript语言,掌握其面向对象特性打下了一定的基础。本章末尾,我们列出了一些与本章内容相关的资料链接,以供您需要时参考。

◆ 在YUI视频网站(http://developer.yahoo.com/yui/theater/)上,有DouglasCrockford先生的几个讲座是非常值得一看的。例如“TheoryoftheDOM”这一讲的第一部分就是关于浏览器历史的。而“The JavaScript Programming Language”这一讲的第一部分谈的就是JavaScript的发展史(当然也包括一些其他事情)。

注释:DouglasCrockford是Web开发领域最知名的技术权威之一,ECMAJavaScript2.0标准化委员会委员。被JavaScript之父Brendan Eich称为JavaScript的大宗师(Yoda)。曾任Yahoo!资深JavaScript架构师,现任PayPal高级JavaScript架构师。——译者注

◆ 关于 OOP 的概念,我们可以参考 wikipedia 上的相关文章:http://en.wikipedia.org/wiki/Object-oriented_programming 以及 sun 公司所提供的Java文档(这里谈的是传统意义上的OOP):http://java.sun.com/ docs/books/ tutorial/java/concepts/index.html。

◆ 关于 JavaScript 在今天的应用情况,我们或许可以从以下几个实例中获得一些启示:Yahoo! Widgets (http://widgets.yahoo.com/)、Google Maps(http://maps. google.com ),以及各个版本的 JavaScript 可视化语言处理器(http:// ejohn.org/blog/ processingjs/)。

在深入学习 JavaScript 的面向对象特性之前,我们首先要了解一些基础性知识。在这一章中,我们将会从以下几个方面入手。

◆ JavaScript中的基本数据类型,例如字符串和数字等。

◆ 数组。

◆ 常用操作符,例如+、-、delete、typeof等。

◆ 控制流语句,例如循环和if-else条件表达式等。

通常,变量都是用来存储数据的。当我们编写程序时,用变量来表示实际数据显然要方便些。尤其是当我们需要多次使用某个数字(例如 3.141592653589793)时,使用变量pi显然要比直接写数字值方便得多。而且,它们之所以能被称为“变量”,主要是它们所存储的数据在初始化之后仍然是可以改变的。正因为如此,我们在编写代码时往往会用变量来代表程序中的未知数据,例如某个计算的结果值。

变量的使用通常可分为两个步骤。

◆ 声明变量。

◆ 初始化变量,即给它一个初始值。

为了声明变量,我们需要使用var语句。像这样:

var a;

var thisIsAVariable;

var _and_this_too;

var mix12three;

变量名可以由任何数字、字符及下划线组合而成。但要记住它不能以数字开头,像下面这样是不被允许的:

var 2three4five;

所谓的变量初始化,实际上指的是变量首次(或者最初)被赋值的时机。它可以有以下两种选择。

◆ 先声明变量,然后再初始化。

◆ 声明变量与初始化同步进行。

下面是后一种写法的例子:

var a = 1;

这样,我们就声明了一个名为a、值为1的变量。

另外,我们也可以在单个 var 语句中同时声明(并初始化)多个变量,只要将它们分别用逗号分开即可,例如:

var v1, v2, v3 = 'hello', v4 = 4, v5;

区分大小写

在JavaScript语言中,变量名是区分大小写的。为了证明这一点,我们可以在Firebug控制台中测试下列语句(每输入一行按一次Enter键):

var case_matters = 'lower';

var CASE_MATTERS = 'upper';

case_matters

CASE_MATTERS

为了减少按键的次数,在输入第三行时,我们可以先键入ca然后按Tab键,控制台会自动将其补全为case_matters。最后一行也是如此,我们只需先输入CA然后直接按Tab即可。输入完成之后,最终结果如图2-1所示:

图2-1

为方便起见,以后我们将用代码形式来代替截图。上面的例子可以表示如下:

>>> var case_matters = 'lower';

>>> var CASE_MATTERS = 'upper';

>>> case_matters

"lower"

>>> CASE_MATTERS

"upper"

如您所见,三个连续大于号(>>>)之后的内容是我们输入的代码,而其余部分则是控制台输出的结果。需要强调的是,当您测试类似的代码时,应该根据实验的实际情况来调整相关代码。这才能有助于您更好地理解语言的工作方式。

所谓操作符,通常指的是能对一两个输入执行某种操作,并返回结果的符号。为了更清晰地表达该术语的含义,我们先来看一个具体的示例:

>>> 1 + 2

3

这段代码中所包含的信息主要有以下几点。

◆ +是一个操作符。

◆ 该操作是一次加法运算。

◆ 输入值为1和2(输入值也叫做操作数)。

◆ 结果值为3。

这里的1和2都是直接参与加法运算的。现在,我们改用变量来表示它们,同时再另外声明一个变量来存储运算结果。具体如下:

>>> var a = 1;

>>> var b = 2;

>>> a + 1

2

>>> b + 2

4

>>> a + b

3

>>> var c = a + b;

>>> c

3

在表2-1中,我们列出了一些基本的算术运算符。

表2-1

(续表)

事实上,当我们输入var a = 1;这样的语句时,所执行的也是一种操作。这种操作叫做纯赋值,因而=也被称为纯赋值操作符。

此外,JavaScript中还有一组由算术运算和赋值操作组合而成的操作符。我们叫它复合操作符。这些操作符能让我们的代码显得更为紧凑。下面来看几个示例:

>>> var a = 5;

>>> a += 3;

8

在该例中,a += 3;实际上就相当于a = a + 3;的缩写形式。

>>> a -= 3;

5

同理,这里的a -= 3;等同于a = a - 3;。

以此类推:

>>> a *= 2;

10

>>> a /= 5;

2

>>> a %= 2;

0

除了我们已经提到的算术运算与赋值操作以外,JavaScript 中还有其他各种类型的操作符。我们将会在后面的章节中陆续看到。

我们在程序中所使用的任何值都是有类型的。在 JavaScript 中,主要包含以下几大基本数据类型。

1.数字——包括浮点数与整数,例如1、100、3.14。

2.字符串——一序列由任意数量字符组成的序列,例如"a"、"one"、"one 2 three"。

3.布尔值——true或false。

4 . undefined——当我们试图访问一个不存在的变量时,就会得到一个特殊值:undefined。除此之外,使用一个未初始化的变量也会如此。因为 JavaScript 会自动将变量在初始化之前的值设定为undefined。

5.null——这是另一种只包含一个值的特殊数据类型。所谓的null值,通常是指没有值、空值,不代表任何东西。null与undefined最大的不同在于,被赋予null的变量通常被认为是已经定义了的,只不过它不代表任何东西。关于这一点,我们稍后会通过一些具体的示例来解释。

任何不属于上述五种基本类型的值都会被认为是一个对象。甚至有时候我们也会将null视为对象,这会使人有些尴尬——这是一个不代表任何东西的对象(东西)。我们将会在第4章中深入阐述对象的概念,现在我们只需要记住一点,JavaScript中的数据类型主要分为以下两个部分。

◆ 基本类型(上面列出的五种类型)。

◆ 非基本类型(即对象)。

如果我们想知道某个变量或值的数据类型,可以调用一种叫做typeof的特殊操作符。该操作符会返回一个代表数据类型的字符串,它的值包括:“number”、“string”、“boolean”、“undefined”、“object”和“function”。在接下来的几节中,我们将会逐一展示对五种基本数据类型使用typeof操作符时的情况。

最简单的数字类型当然就是整数了。如果我们将一个变量赋值为1,并对其调用typeof操作符,控制台就会返回字符串“number”,请看下面的代码。此外要注意的是,当您第二次设置变量值时,就不需要再使用var语句了。

>>> var n = 1;

>>> typeof n;

"number"

>>> n = 1234;

>>> typeof n;

"number"

当然,这也同样适用于浮点数(即含小数部分的数字):

>>> var n2 = 1.23;

>>>typeof n;

"number"

注释:此处原文为typeofn;,但根据上下文判断应属笔误,故更正为typeofn2;。——译者注

除了对变量赋值以外,我们也可以直接对一个数值调用typeof。例如:

>>> typeof 123;

"number"

2.3.2.1 八进制与十六进制

当一个数字以0开头时,就表示这是一个八进制数。例如,八进制数0377所代表的就是十进制数255。

>>> var n3 = 0377;

>>> typeof n3;

"number"

>>> n3;

255

如您所见,例子中最后一行所输出的就是该八进制数的十进制表示形式。如果您对八进制数还不太熟悉,那么十六进制您一定不会感到陌生,毕竟,CSS 样式表中的颜色值使用的都是十六进制。

在CSS中,我们定义颜色的方式有以下两种。

◆使用十进制数分别指定R(红)、G(绿)、B(蓝)的值,取值范围都为0~255。例如rgb(0,0,0)代表黑色、rgb(255,0,0)代表红色(红值达到最大值,而绿和蓝都为0值)。

注释:三原色模式(RGBcolormodel)是一种加色模型,是用三种原色──红色、绿色和蓝色的色光以不同的比例相加,以产生多种多样的色光。——译者注

◆ 使用十六进制数,两个数位代表一种色值,依次是 R、G、B。例如#000000 代表黑色、#ff0000代表红色,因为十六进制的ff就等于255。

在JavaScript中,我们会用0x前缀来表示一个十六进制值(简称为hex)。

>>> var n4 = 0x00;

>>> typeof n4;

"number"

>>> n4;

0

>>> var n5 = 0xff;

>>> typeof n5;

"number"

>>> n5;

255

2.3.2.2 指数表示法

一个数字可以表示成 1e1(或者 1e+1、1E1、1E+1)这样的指数形式,意思是在数字1后面加1个0,也就是10。同理,2e+3的意思是在数字2后面加3个0,也就是2000。

>>> 1e1

10

>>> 1e+1

10

>>> 2e+3

2000

>>> typeof 2e+3;

"number"

此外,我们也可以将 2e+3 理解为将数字 2 的小数点向右移三位。依照同理,2e-3也就能被理解为是将数字2的小数点左移三位。

>>> 2e-3

0.002

>>> 123.456E-3

0.123456

>>> typeof 2e-3

"number"

2.3.2.3 Infinity

在JavaScript中,还有一种叫做Infinity的特殊值。它所代表的是超出了JavaScript处理范围的数值。但 Infinity 依然是一个数字,我们可以在控制台使用 typeof 来测试Infinity。当我们输入1e308时,一切正常,但一旦将后面的308改成309就出界了。经实践证明,JavaScript所能处理的最大值是1.7976931348623157e+308,而最小值为5e-324。

>>> Infinity

Infinity

>>> typeof Infinity

"number"

>>> 1e309

Infinity

>>> 1e308

1e+308

另外,任何数除0也为infinity:

>>> var a = 6 / 0;

>>> a

Infinity

Infinity 表示的是最大数(或者比最大数还要大的数),那么最小数该如何表示呢?答案是在Infinity之前加一个负号:

>>> var i = -Infinity;

>>> i

-Infinity

>>> typeof i

"number"

但这是不是意味着我们可以得到双倍的 Infinity 呢?——毕竟我们可以从 0 加到Infinity,也可以从0减到-Infinity。好吧,这只是个玩笑。事实上这是不可能的,因为即便将正负 Infinity相加,我们也不会得到 0,而是会得到一个叫做NaN(Not A Number的缩写,即不是数字)的东西。

>>> Infinity - Infinity

NaN

>>> -Infinity + Infinity

NaN

而且,Infinity与其他的任何操作数执行任何算术运算的结果,都是Infinity。

>>> Infinity - 20

Infinity

>>> -Infinity * 3

-Infinity

>>> Infinity / 2

Infinity

>>> Infinity - 99999999999999999

Infinity

2.3.2.4 NaN

还记得之前见过的那个NaN吗?尽管该值的名字叫做“不是数字”,但事实上它依然属于数字,只不过是一种特殊的数字罢了。

>>> typeof NaN

"number"

>>> var a = NaN;

>>> a

NaN

如果我们在对一个假定的数字执行某个操作时失败了,就会得到一个NaN。例如,当我们试图将 10与字符"f"相乘时,其结果就会为 NaN,因为"f"显然是不支持乘法运算的。

>>> var a = 10 * "f";

>>> a

NaN

而且,NaN是具有传染性的,只要我们的算术运算中存在一个NaN,整个运算就会失败。

>>> 1 + 2 + NaN

NaN

字符串通常指的是一组用于表示文本的字符序列。在 JavaScript 中,一对双引号或单引号之间的任何值都会被视为一个字符串。也就是说,1是一个数字的话,"1"就是一个字符串了。在一个字符串上,typeof操作符会返回“string”。

>>> var s = "some characters";

>>> typeof s;

"string"

>>> var s = 'some characters and numbers 123 5.87';

>>> typeof s;

"string"

字符串中可以包含数字,例如:

>>> var s = '1';

>>> typeof s;

"string"

如果引号之间没有任何东西,它所表示的依然是一个字符串(即空字符串):

>>> var s = ""; typeof s;

"string"

之前,当我们在两个数字之间使用加号时,所执行的是加法运算。但在字符串中,这是一个字符串拼接操作,它返回的是两个字符串拼接之后的结果。例如:

>>> var s1 = "one"; var s2 = "two"; var s = s1 + s2; s;

"onetwo"

>>> typeof s;

"string"

像+这样的双功能操作符可能会带来一些错误。因此,我们如果想执行拼接操作的话,最好确保其所有的操作数都是字符串。同样的,在执行数字相加时,我们也要确保其所有的操作数都是数字。至于如何做到这一点,我们将会在后续章节中详细讨论。

2.3.3.1 字符串转换

当我们将一个数字字符串用于算术运算中的操作数时,该字符串会在运算中被当做数字类型来使用。(由于加法操作符的歧义性,这条规则不适用于加法运算。)

>>> var s = '1'; s = 3 * s; typeof s;

"number"

>>> s

3

>>> var s = '1'; s++; typeof s;

"number"

>>> s

2

于是,将数字字符串转换为数字就有了一种偷懒的方法:只需将该字符串与1相乘即可。(当然,更好的选择是调用parseInt函数,关于这点,我们将会在下一章中介绍。)

>>> var s = "100"; typeof s;

"string"

>>> s = s * 1;

100

>>> typeof s;

"number"

如果转换操作失败了,我们就会得到一个NaN值。

>>> var d = '101 dalmatians';

>>> d * 1

NaN

此外,将其他类型转换为字符串也有一种偷懒的方法,只需要将其与空字符串连接即可:

>>> var n = 1;

>>> typeof n;

"number"

>>> n = "" + n;

"1"

>>> typeof n;

"string"

2.3.3.2 特殊字符串

在表2-2中,我们列出了一些具有特殊含义的字符串。

表2-2

(续表)

除此之外,还有一些很少被使用的特殊字符,例如:\b(退格符)、\v(纵向制表符)、\f(换页符)等。

布尔类型中只有两种值:true和false。它们可用于引号以外的任何地方。

>>> var b = true; typeof b;

"boolean"

>>> var b = false; typeof b;

"boolean"

如果true或false在引号内,它就是一个字符串。

>>> var b = "true"; typeof b;

"string"

2.3.4.1 逻辑运算符

在JavaScript中,主要有三种逻辑运算符,它们都属于布尔运算。分别是:

◆ !——逻辑非(取反)。

◆ &&——逻辑与。

◆ ||——逻辑或。

在 JavaScript 中,如果我们想描述一些日常生活中非真即假的事物,就可以考虑使用逻辑非运算符:

>>> var b = !true;

>>> b;

false

如果在同一个值上执行两次逻辑非运算,其结果就等于原值

注释:从上下文来看,此处应该特指布尔值。——译者注

>>> var b = !!true;

>>> b;

true

如果在一个非布尔值上执行逻辑运算,该值会在计算期间被转换为布尔值:

>>> var b = "one";

>>> !b;

false

如您所见,上例中的字符串"one"是先被转换为布尔值 true 然后再取反的,结果为false。如果我们对它取反两次,结果就会为true。例如:

>>> var b = "one";

>>> !!b;

true

使用双重取反操作可以很容易地将任何值转换为等效的布尔值。虽然这种方法很少被用到,但从另一个角度也说明了将其他类型的值转换为布尔值的重要性。而事实上,除了下面所列出特定值以外(它们将被转换为false),其余大部分值在转换为布尔值时

都为true。

◆ 空字符串""

◆ null

◆ undefined

◆ 数字0

◆ 数字NaN

◆ 布尔值false

这6个值有时也会被我们统称为falsy,而其他值则被称为truthy(包括字符串"0"、""、"false")。

接下来,让我们来看看另外两个操作符——逻辑与和逻辑或的使用示例。当我们使用逻辑与操作符时,当且仅当该操作所有操作数为true时,它才为true。而逻辑或操作则只需要至少一个操作数为true即可为true。

>>> var b1 = true; var b2 = false;

>>> b1 || b2

true

>>> b1 && b2

false

在表2-3中,我们列出了所有可能的情况及其相应结果。

表2-3

当然,我们也能连续执行若干个逻辑操作。例如:

>>> true && true && false && true

false

>>> false || true || false

true

我们还可以在同一个表达式中混合使用&&和||。不过在这种情况下,我们最好用括号来明确一下操作顺序。例如:

>>> false && false || true && true

true

>>> false && (false || true) && true

false

2.3.4.2 操作符优先级

您可能会想知道,为什么上例中的第一个表达式(false && false || true &&true)结果为true。答案在于操作符优先级。这看上去有点像数学,例如:

>>> 1 + 2 * 3

7

由于乘法运算的优先级高于加法,所以该表达式会先计算2 * 3,这就相当于我们输入的表达式是:

>>> 1 + (2 * 3)

7

逻辑运算符也一样,!的优先级最高,因此在没有括号限定的情况下它将会被最先执行。然后,接下来的优先顺序是先&&后||。也就是说:

>>> false && false || true && true

true

与下面表达式等效:

>>> (false && false) || (true && true)

true

最佳方法:

尽量使用括号,而不是依靠操作符优先级来设定代码的执行顺序,这样我们的代码才能有更好的可读性。

2.3.4.3 惰性求值

如果在一个连续的逻辑操作中,操作结果在最后一个操作完成之前就已经明确了的话,那么该操作往往就不必再继续执行了,因为这已经不会对最终结果产生任何影响。例如,在下面这种情况中:

>>> true || false || true || false || true

true

在这里,所有的逻辑或运算符优先级都是相同的,只要其中任何一个操作数为true,该表达式的结果就为 true。因而当第一个操作数被求值之后,无论后面的值是什么,结果都已经被确定了。于是我们可以允许 JavaScript 引擎偷个懒(好吧,这也是为了提高效率),在不影响最终结果的情况下省略一些不必要的求值操作。为此,我们可以在控制台中做个实验:

>>> var b = 5;

>>> true || (b = 6)

true

>>> b

5

>>> true && (b = 6)

6

>>> b

6

除此之外,上面的例子还向我们显示了另一个有趣的事情——如果 JavaScript 引擎在一个逻辑表达式中遇到一个非布尔类型的操作数,那么该操作数的值就会成为该表达式所返回的结果。例如:

>>> true || "something"

true

>>> true && "something"

"something"

通常情况下,这种行为是应该尽量避免的,因为它会使我们的代码变得难以理解。但在某些时候这样做也是有用的。例如,当我们不能确定某个变量是否已经被定义时,就可以像下面这样,即如果变量mynumber已经被定义了,就保留其原有值,否则就将它初始化为10。

var mynumber = mynumber || 10;

这种做法简单而优雅,但是请注意,这也不是绝对安全的。如果这里的mynumber之前被初始化为0(或者是那6个falsy值中的任何一个),这段代码就不太可能如我们所愿了。

2.3.4.4 比较运算符

在 JavaScript 中,还有另外一组以布尔值为返回值类型的操作符,即比较操作符。下面让我们通过表2-4来了解一下它们以及相关的示例。

表2-4

(续表)

还有一件有趣的事情要提醒读者注意:NaN不等于任何东西,包括它自己。

>>> NaN == NaN

false

通常情况下,当我们试图访问某个不存在的或者未经赋值的变量时,就会得到一个undefined值。JavaScript会自动将声明时没有进行初始化的变量设为undefined。

当我们试图使用一个不存在的变量时,就会得到这样的错误信息:

>>> foo

foo is not defined

这时候,如果我们在该变量上调用typeof操作符,就会得到字符串“undefined”:

>>> typeof foo

"undefined"

如果我们声明一个变量时没有对其进行赋值,调用该变量时并不会出错,但typeof操作符依然会返回“undefined”。

>>> var somevar;

>>> somevar

>>> typeof somevar

"undefined"

而null 值就完全是另一回事了。它不能通过JavaScript来自动赋值,只能通过我们的代码来完成。

>>> var somevar = null

null

>>> somevar

null

>>> typeof somevar

"object"

尽管undefined和null之间的差别微乎其微,但有时候也很重要。例如,当我们对其分别执行某种算术运算时,结果就会截然不同:

>>> var i = 1 + undefined; i;

NaN

>>> var i = 1 + null; i;

1

这是因为null和undefined在被转换为其他基本类型时,方法存在一定的区别,下面我们给出一些可能的转换类型。

转换成数字:

>>> 1*undefined

NaN

>>> 1*null

0

转换成布尔值:

>>> !!undefined

false

>>> !!null

false

转换成字符串:

>>> "" + null

"null"

>>> "" + undefined

"undefined"

现在,让我们来快速汇总一下目前为止所讨论过的内容。

◆ JavaScript语言中有五大基本数据类型:

◆ 数字

◆ 字符串

◆ 布尔值

◆ undefined

◆ null

◆ 任何不属于基本类型的东西都属于对象。

◆ 数字类型可以存储的数据包括:正负整数、浮点数、十六进制数与八进制数、指数以及特殊数值NaN、Infinity、-Infinity。

◆ 字符串类型存储的是一对引号之间的所有字符。

◆ 布尔类型的值只有两个:true和false。

◆ null类型的值只有一个:null。

◆ undefined类型的值只有一个:undefined。

◆ 绝大部分值在转换为布尔类型时都为true,但以下6种falsy值除外:

◆ ""

◆ null

◆ undefined

◆ 0

◆ NaN

◆ false

现在,我们对 JavaScript 中的基本数据类型已经有了一定的了解,是时候将注意力转向更有趣的数据结构——数组了。

我们可以用一对不带任何内容的方括号来声明一个空数组变量,例如:

>>> var a = [];

>>> typeof a;

"object"

如您所见,typeof 在这里返回的是“object”。建议您先别管这个,待我们重点讨论对象时再回头来说明这个问题。

如果我们要定义一个带三个元素的数组,可以这样做:

>>> var a = [1,2,3];

只要在控制台中简单地输入数组名,就能打印出该数组中的所有内容:

>>> a

[1, 2, 3]

那么,究竟什么是数组呢?简而言之,它就是一个用于存储数据的列表。与一次只能存储一个数据值的变量不同,我们可以用数组来存储任意数量的元素值。现在的问题是,我们应该如何访问数组中的各个数据值呢?

通常,元素在数组中的索引位置是从0开始编号的。也就是说,数组首元素的索引值(或者说位置值)应该是0,第二个元素的索引值则是1,以此类推。表2-5中所显示的就是之前那个三元素数组实例中的具体情况。

表2-5

为了访问特定的数组元素,我们需要用一对方括号来指定元素的索引值。因此a[0]所访问的就是数组a的首元素,而a[1]则代表第二个元素,以此类推。

>>> a[0]

1

>>> a[1]

2

我们可以通过索引来更新数组中的元素。例如在下面的代码中,我们更新了第三个元素(索引值为2)的值,并将更新后的数组打印出来:

>>> a[2] = 'three';

"three"

>>> a

[1, 2, "three"]

另外,我们也可以通过索引一个之前不存在的位置,来为其添加更多的数组元素。

>>> a[3] = 'four';

"four"

>>> a

[1, 2, "three", "four"]

如果新元素被添加的位置与原数组末端之间存在一定的间隔,那么这之间的元素将会被自动设定为undefined值。例如:

>>> var a = [1,2,3];

>>> a[6] = 'new';

"new"

>>> a

[1, 2, 3, undefined, undefined, undefined, "new"]

为了删除特定的元素,我们需要用到delete操作符。该操作符虽然不能真正移除一个元素,但它能将其设定为undefined。元素被删除后,数组的长度并不会受到影响。

>>> var a = [1, 2, 3];

>>> delete a[1];

true

>>> a

[1, undefined, 3]

通常情况下,我们可以在数组中存放任何类型的值,包括另一个数组。

>>> var a = [1, "two", false, null, undefined];

>>> a

[1, "two", false, null, undefined]

>>> a[5] = [1,2,3]

[1, 2, 3]

>>> a

[1, "two", false, null, undefined, [1, 2, 3]]

让我们来看一个例子,在下面的代码中,我们定义了一个含有两个数组的数组:

>>> var a = [[1,2,3],[4,5,6]];

>>> a

[[1, 2, 3], [4, 5, 6]]

在该数组中,首元素a[0]本身也是一个数组。

>>> a[0]

[1, 2, 3]

如果想要访问内层数组中的特定元素,我们需要再加一组方括号。例如:

>>> a[0][0]

1

>>> a[1][2]

6

另外值得注意的是,我们通过这种访问数组方式来获取某个字符串中的特定字符。例如:

>>> var s = 'one';

>>> s[0]

"o"

>>> s[1]

"n"

>>> s[2]

"e"

除此之外,数组的使用方法还有很多(我们将会在第4章中详细介绍),现在先到此为止,请记住以下内容。

◆ 数组是一种数据存储形式。

◆ 数组元素是可以被索引的。

◆ 数组中的元素索引是从0开始的,并且按照每个元素的位置依次递增。

◆ 我们是通过方括号中的索引值来访问数组元素的。

◆ 数组能存储任何类型的数据,包括另一个数组。

条件表达式是一种简单而强大的控制形式,它能够帮助我们控制一小段代码的执行走向。而循环则是一种可以让我们重复执行某段代码的操作。接下来,我们将会学习以下内容。

◆ if条件表达式。

◆ switch语句。

◆ while、do-while、for,以及for-in循环。

首先,我们需要先了解一下什么是代码块,这在条件表达式和循环体中随处可见。

所谓的代码块,通常指的是被包括在0对或多对大括号中的那一段代码。

{

 var a = 1;

 var b = 3;

}

每个代码块中都可以内嵌另一个代码块,并且可以无限制地嵌套下去

注释:这里的“无限制”是纯语言意义上的,由于每一层嵌套都意味着内存中的一个独立栈,所以嵌套的深度必须考虑内存的实际使用情况。——译者注

{

 var a = 1;

 var b = 3;

 var c, d;

  {

    = a + b;

   {

    d = a - b;

   }

  }

 }

最佳实践:

◆ 尽量使用分号来作为每一行的结束。尽管这在语法上是可选的,但对于开发来说是一个很好的习惯。为了让代码获得最佳的可读性,我们在代码块中的表达式最好是一行一个,并用分号彼此隔开。

◆ 尽量对代码块中的所有代码使用缩进格式。有些人会用tab来做缩进,而有些则会使用四个或两个空格。这都无关紧要,只要保持前后一致就行。在上面那个例子中,我们在最外层用了两个空格的缩进,在首层嵌套中用了四个空格,而第二层则是六个空格。

◆ 尽量使用大括号。当代码块中只有一个表达式时,大括号实际上是可选的。但为了增加代码的可读性和可维护性,我们最好还是养成加大括号的习惯,即使这不是必需的。

现在,准备好开始学习循环和条件语句了吗?另外要提醒的是,接下来我们需要将Firebug控制台切换到多行模式。

2.6.1.1 if条件表达式

让我们先来看一个简单的if条件表达式:

var result = '';

if (a > 2) {

 result = 'a is greater than 2';

}

如您所见,该表达式通常主要由以下几个部分组成。

◆ if语句。

◆ 括号中的条件部分——判断“a是否大于2”。

◆ 当if条件满足时所要执行的代码块。

其中,条件部分(即括号内的部分)通常由某些返回布尔值的操作组成,主要有以下几种形式。

◆ 逻辑类操作,包括!、&&、||等。

◆ 比较类操作,包括===、!=、>等。

◆ 一个可以转换为布尔类型的值或变量。

◆ 以上几种形式的组合。

除此之外,if表达式中还有一个可选项,即如果条件部分的表达式返回false的话,我们也可以执行后面else子句中的代码块。例如:

if (a > 2) {

 result = 'a is greater than 2';

} else {

 result = 'a is NOT greater than 2';

}

而且,我们还可以在if和else之间插入无数个else if子句。例如:

if (a > 2 || a < -2) {

 result = 'a is not between -2 and 2';

} else if (a === 0 && b === 0) {

 result = 'both a and b are zeros';

} else if (a === b) {

 result = 'a and b are equal';

} else {

 result = 'I give up';

}

另外,我们也可以在当前的if代码块中再内嵌一个新的条件语句。

if (a === 1) {

 if (b === 2) {

  result = 'a is 1 and b is 2';

 } else {

  result = 'a is 1 but b is not 2';

 }

} else {

 result = 'a is not 1, no idea about b';

}

2.6.1.2 检查变量是否存在

if 表达式在检查一个变量是否存在时往往非常有用。其中,最懒的方法就是其条件部分中直接使用变量,例如if(somevar){....}。但这样做并不一定是最合适的。我们可以来测试一下。在下面这段代码中,我们将会检查程序中是否存在一个叫做 somevar 的变量,如果存在,就将变量result设置为yes。

>>> var result = '';

>>> if (somevar){result = 'yes';}

somevar is not defined

>>> result;

""

这段代码显然是可以工作的,因为最终的结果肯定不会是“yes”。但首先,这段代码会产生一个警告信息:“somevar is not defined”,作为一个 JavaScript高手,您肯定不希望自己的代码多此一举。其次,就算if(somevar)返回的是false,也并不意味着somevar就一定没有定义,它也可以是任何一种被初始化为falsy值(如false或0)的已声明变量。

所以在检查变量是否存在时,更好的选择是使用typeof。

>>> if (typeof somevar !== "undefined"){result = 'yes';}

>>> result;

""

在这种情况下,typeof返回的就是一个字符串,这样就可以与“undefined”进行直接比对。但需要注意的是,如果这里的 somevar 是一个已经声明但尚未赋值的变量,结果也是相同的。也就是说,我们实际上是在用typeof测试一个变量是否已经被初始化(或者说测试变量值是否为undefined)。

>>> var somevar;

>>> if (typeof somevar !== "undefined"){result = 'yes';}

>>> result;

""

>>> somevar = undefined;

>>> if (typeof somevar !== "undefined"){result = 'yes';}

>>> result;

""

也就是说,只有当一个变量被定义并初始化为 undefined 以外的值时,typeof 所返回的类型才不会等于“undefined”。

>>> somevar = 123;

>>> if (typeof somevar !== "undefined"){result = 'yes';}

>>> result;

"yes"

2.6.1.3 替代if表达式

如果我们所面对的条件表达式非常简单,就要考虑用其他形式来替代 if 表达式。例如下面这段代码:

var a = 1;

var result = '';

if (a === 1) {

 result = "a is one";

} else {

 result = "a is not one";

}

我们完全可以将其简化为:

var result = (a === 1) ? "a is one" : "a is not one";

但需要提醒的是,这种语法通常只用于一些非常简单的条件逻辑,千万不要滥用。因为这样做很容易使我们的代码变得难以理解。

另外,这里的?操作符也叫做三无运算符。

2.6.1.4 switch语句

当我们发现自己在 if 表达式中使用了太多的 else if 子句时,就应该要考虑用switch语句来替代if了。

var a = '1';

var result = '';

switch (a) {

 ase 1:

  result = 'Number 1';

  break;

 ase '1':

  result = 'String 1';

  break;

 default:

  result = 'I don\'t know';

  break;

 }

 result;

显然,这段代码的执行结果为“String 1”。现在,让我们来看看switch表达式主要由哪几部分组成:

◆ switch子句。

◆ 括号中的表达式,这里通常会是一个变量,但也可以是其他任何能返回某值的东西。

◆ 包含在大括号中的case序列块。

◆ 每个case语句后面有一个表达式,该表达式的结果将会与switch语句的表达式进行比对。如果比对的结果为true,则case语句中冒号之后的代码将会被执行。

◆ break语句是可选的,它实际上是case块的结束符,即当代码执行到break语句时,整个switch语句就执行完成了,否则就继续执行下一个case块,而这通常是应该避免的。

◆ default语句也是可选的,其后的代码块只有在上面所有的case表达式都不为true时才会被执行。

换句话说,整个switch语句的执行应该可以分为以下几个步骤。

1.对switch语句后面的括号部分进行求值,并记录结果。

2.移动到第一个case块,将它的值与步骤1的结果进行比对。

3.如果步骤2中的比对结果为true,则执行该case块中的代码。

4.在相关case块执行完成之后,如果遇到break语句就直接退出switch。

5.如果没有遇到break或步骤2中的比对结果为false,就继续下一个case块,然后重复步骤2到5中的操作。

6.如果依然还没有结束(也就是始终未能按照步骤4中的方式退出),就执行default语句后面的代码块。

最佳实践

◆ 对case进行缩进,并对后面的代码部分进行再次缩进。不要忘了break。

◆ 有时候,我们会希望故意省略一些break语句,当然,这种叫做贯穿(fall-through)的做法在实际应用中并不常见,因为它通常会被误认为是人为的遗漏。故而使用时往往需要在文档中加以说明。但从另一方面来说,如果我们真的有意让两个相邻的case语句共享同一段代码的话,这样做并没有什么不妥。只不过,这不能改变相关的规则,即如果执行代码是写在case语句之后的话,它依然应该以break结尾。另外在缩进方面,break是选择与case对齐还是与相关的代码块对齐,完全取决于个人喜好,只要保持风格的一致性即可。

尽量使用default 语句。因为这可以使我们在switch找不到任何匹配的情况下,也依然能做一些有意义的事情。

通过if-else和switch语句,我们可以在代码中采取不同的执行路径。当我们处于某种十字路口时,就可以根据某个具体的条件来选择自己的走向。然而,循环就完全是另一回事了,我们可以利用它使代码在返回主路径之前先去执行某些重复操作。至于重复的次数,则完全取决于我们设定在每次迭代之前(或之后)的条件值。

比如说,我们的程序通常都是在A点到B点之间运行,如果我们在这之间设置了一个条件 C,而这个条件的值将会决定我们是否要进入循环 L。那么一旦进入了循环,我们就必须在每次迭代完成之后对该条件进行重新求值,以判断是否要执行下一次迭代。总之,我们最终还是会回到通往B点的路径上来的。

当某循环的条件永为true时,它就成了一个无限循环。这意味着代码将会被“永远”困在循环中。这无疑是一个逻辑上的错误,我们必须对此加以防范。

在JavaScript中,循环主要有以下四种类型:

◆ while循环

◆ do-while循环

◆ for循环

◆ for-in循环

2.6.2.1 while循环

while循环是最为简单的一种循环,它们通常是这样的:

var i = 0;

while (i < 10) {

 i++;

}

while语句主要分为两个部分:小括号中的条件和大括号中的代码块。当且仅当条件值为true时,代码块才会被反复执行。

2.6.2.2 do-while循环

do-while循环实际上是while循环的一种轻微的变种。示例如下:

var i = 0;

do {

 i++;

} while (i < 10)

在这里,do语句后面先出现的是代码块,然后才是条件。条件出现在代码块之后,这意味着代码块无论如何都会被执行一次,然后再去对条件部分进行求值。

如果我们将上面两个示例中的i初始化为11而不是0的话,第一个例子(while循环)中,代码块将不会执行,i最终的值仍然是11,而第二个例子(do-while循环)中的代码块将会被执行一次,而i的值也会变为12。

2.6.2.3 for循环

for是使用得最为广泛的循环类型,也是我们最应该掌握的内容。实际上,这也只需要掌握一点点语法知识。

在条件C和代码块L的基础上,我们还需要增加以下两个部分的内容。

◆ 初始化部分——在进入循环之前所要执行的代码(即图中0所标志的内容)。

◆ 自增部分——每次迭代完成后所要执行的代码(即图中++所标志的内容)。

最常用的for循环模式主要包括以下内容。

◆ 在初始化部分中,我们会定义一个循环变量(通常命名为i),例如var i = 0;。

◆ 在条件部分中,我们会将i与循环边界值进行比对。例如i < 100。

◆ 在自增部分中,我们会将循环变量i自增1,如i++。

下面来看一个具体示例:

var punishment = '';

for (var i = 0; i < 100; i++) {

 punishment += 'I will never do this again, ';

}

实际上,这三个部分(初始化、循环条件、自增操作)都可以写成用逗号分割的多重表达式。例如,我们可以重写一遍上面的例子,在其初始化部分中增加一个punishment变量的定义。

for (var i = 0, punishment = ''; i < 100; i++) {

 punishment += 'I will never do this again, ';

}

那么,我们能不能把循环体中的内容移到自增部分中去呢?当然可以,尤其当其中只有一行内容时。只不过这样的循环看上去有点令人尴尬,因为它没有循环体了。

for (var i = 0, punishment = '';

 i < 100;

 i++, punishment += 'I will never do this again, ')

{

 // nothing here

}

事实上,这三部分也都是可选的,上面的例子也完全可以写成下面这样:

var i = 0, punishment = '';

for (;;) {

 punishment += 'I will never do this again, ';

 if (++i == 100) {

  break;

 }

}

尽管代码重写之后的工作方式与原来相同,但它显得更长了,可读性也更差了。我们完全可以用while循环来取代它。但for循环可以使代码更紧凑、更严谨。它的三个部分(初始化、循环条件、自增操作)泾渭分明,语法也更为纯粹。这些都有利于我们理清程序的逻辑,从而避免类似于无限循环这样的麻烦。

另外,for循环还可以彼此嵌套。下面,我们来看一个嵌套循环的具体示例。假设该例要打印一个10行10列的星号字符串,那么我们就可以用i来表示行数,j则表示列数,以构成一个“图形”:

var res = '\n';

for(var i = 0; i < 10; i++) {

 for(var j = 0; j < 10; j++) {

  res += '* ';

 }

 res+= '\n';

}

最终,该字符串输出如下:

另外,我们还可以用嵌套循环和取模运算画出一个雪花状的图形,代码如下:

var res = '\n', i, j;

 for(i = 1; i <= 7; i++) {

  for(j = 1; j <= 15; j++) {

    res += (i * j) % 8 ? ' ' : '*';

  }

  res+= '\n';

 }

2.6.2.4 for-in循环

for-in循环往往被用来遍历某个数组(或对象,这一点我们以后再讨论)中的元素。这似乎也是它唯一的用处,该循环不能用来替代for或while循环,执行某些一般性的重复操作。下面,我们来看一个for-in遍历数组元素的示例。当然,例子仅供参考。毕竟对于for-in循环来说,它最适用的场合依然是对象,以及用于常规for循环的数组。

在下面的示例中,我们将遍历数组中的所有元素,并打印出当前所在的索引位置(即键值)和元素值。

var a = [ 'a', 'b', 'c', 'x', 'y', 'z'];

var result = '\n';

for (var i in a) {

 result += 'index: ' + i + ', value: ' + a[i] + '\n';

}

结果如下:

"

index: 0, value: a

index: 1, value: b

index: 2, value: c

index: 3, value: x

index: 4, value: y

index: 5, value: z

"

现在,我们来看本章最后一个内容:注释。通过注释这种形式,我们可以将自己的一些想法放在JavaScript代码中。由于注释中的内容会被JavaScript引擎自动忽略掉,因此它们不会对程序产生任何影响。而当您几个月后重新考虑这段代码,或将其转让给其他人维护时,这些注释就会显得非常重要。

注释的形式主要有以下两种。

◆ 单行注释——以//开头并直至该行结束。

◆ 多行注释——以/*开头,并以*/结尾,其中可以包括一行或多行内容。但要记住,注释首尾符之间的任何代码都将会被忽略。

具体示例如下:

// beginning of line

var a = 1; // anywhere on the line

/* multi-line comment on a single line */

/*

 comment

 that spans

 several lines

*/

甚至,有些实用工具(例如 JSDoc)可以从我们的代码中提取相关的注释,并据此生成有意义的项目文档。

在这一章中,我们学习了编写一个 JavaScript 程序所需要的基本组件。现在,您应该已经掌握了以下几种基本数据类型。

◆ 数字

◆ 字符串

◆ 布尔值

◆ undefined

◆ null

你也已经了解了一些基本的操作符。

◆ 算术运算符:+ 、-、*、/、%。

◆ 自增(减)运算符:++、--。

◆ 赋值运算符:=、+=、-=、*=、/=、%=。

◆ 特殊操作符:typeof、delete。

◆ 逻辑运算符:&&、||、!。

◆ 比较运算符:==、===、!=、!==、<、>、>=、<=。

另外,我们还学习了如何使用数组来存储和访问数据。最后,我们还为您介绍了几种不同的控制程序流程的方法——条件(if-else 和 switch 语句)和循环(while、do-while、for、for-in语句)。

本章的信息量确实不小,因此我们建议您通过下面的练习巩固一下。在继续深入下一章的学习之前,我们需要给自己一些鼓励。

1.如果我们在控制台中执行下列语句,结果分别是什么?为什么?

◆ var a; typeof a;

◆ var s = '1s'; s++;

◆ !!"false"

◆ !!undefined

◆ typeof -Infinity

◆ 10 % "0"

◆ undefined == null

◆ false === ""

◆ typeof "2E+2"

◆ a = 3e+3; a++;

2.执行下面的语句后,v的值会是什么?

>>> var v = v || 10;

如果将 v分别设置为 100、0、null,或者卸载它(即 delete v),结果又将是什么?

3.编写一个打印乘法口诀表的脚本程序。提示:使用嵌套循环来实现。

图书在版编目(CIP)数据

JavaScript面向对象编程指南/(加)斯托扬(Stefanov,S)著;凌杰译.--北京:人民邮电出版社,2013.2

ISBN 978-7-115-30904-4

Ⅰ.①J… Ⅱ.①斯…②凌… Ⅲ.①JAVA语言—程序设计—指南 Ⅳ.①TP312-62

中国版本图书馆CIP数据核字(2013)第016139号

版权声明

Copyright © 2008 Packt Publishing. First published in the English language under the titleObject-Oriented JavaScript.

All rights reserved.

本书由美国 Packt Publishing公司授权人民邮电出版社出版。未经出版者书面许可,对本书的任何部分不得以任何方式或任何手段复制和传播。

版权所有,侵权必究。

JavaScript面向对象编程指南

◆著 [加]Stoyan Stefanov

译 凌杰

责任编辑 陈冀康

◆人民邮电出版社出版发行  北京市崇文区夕照寺街14号

邮编 100061  电子邮件 315@ptpress.com.cn

网址 http://www.ptpress.com.cn

三河市海波印务有限公司印刷

◆开本:800×1000 1/16

印张:20.75

字数:404千字  2013年3月第1版

印数:1-3000册  2013年3月河北第1次印刷

著作权合同登记号 图字:01-2012-4600号

ISBN 978-7-115-30904-4

定价:59.00元

读者服务热线:(010)67132692 印装质量热线:(010)67129223

反盗版热线:(010)67171154

广告经营许可证:京崇工商广字第0021号

相关图书

深入浅出Spring Boot 3.x
深入浅出Spring Boot 3.x
JavaScript核心原理:规范、逻辑与设计
JavaScript核心原理:规范、逻辑与设计
JavaScript入门经典(第7版)
JavaScript入门经典(第7版)
JavaScript函数式编程指南
JavaScript函数式编程指南
PHP、MySQL和JavaScript入门经典(第6版)
PHP、MySQL和JavaScript入门经典(第6版)
JavaScript学习指南(第3版)
JavaScript学习指南(第3版)

相关文章

相关课程