JavaScript核心技术
· 书名:JavaScript核心技术
· 作者:(美)帕瓦斯 著
· 来源:机械工业出版社
· 出版时间:2007年06月
· ISBN:9787111212973
· 定价:45元
图书版权归出版社和作者所有,CSDN & DearBook独家提供试读和连载
内容介绍:
它从最简单的地方入手,不仅讲述了JavaScript的基础知识,还讲述了JavsScript如何操作CSS、DOM等Ajax基础技术。而关于跨浏览器兼容问题的解决,则贯穿了本书的始终。本书对于各种浏览器、浏览器版本、JavaScript版本、DOM版本的介绍,有助于我们理解所遇到的各种新旧代码,使我们能够对这些代码做出正确的取舍。\r\n 本书还提供了一些使用JavaScript的最佳实践。无论是新手还是老手,这些如何正确使用JavaScript的经验都能帮助他们养成良好的编程习惯。本书还介绍了一些调试和开发J...
· 目录
第1章 JavaScript初探
· 1.4 JavaScript初探:“Hello World!”
第2章 JavaScript数据类型与变量
· 2.2 作用域
· 2.3 简单类型
· 2.5 习题
第3章 运算符和语句
· 3.2 简单语句
· 3.7 习题
1.1 规范和实现相互交织的历史
2 JavaScript是一种广泛使用的编程语言;它也是被误解最多的一种语言。近几年,它得到了飞速发展,大多数网站都以某种形式使用它。它基于构件的能力简化了那些日益复杂的程序库的创建工作。这些程序库在Web页面中所提供的效果,在过去大都需要安装一个外部应用程序才能实现。它也可以和服务器端应用程序紧密集成在一起,而这些服务器端应用程序往往使用各种各样语言开发并与多种数据库接口。正因为这些原因,JavaScript往往被视作一种轻量级的、不复杂的、不像一种“真正的编程语言”的编程语言。
3 在某些方面,JavaScript太容易使用了。对于它的批评者来说,它规范性久缺;它的面向对象的能力不是真正的面向对象(OO);它存在于一个简化环境之中,只是功能的一个子集;它不安全;它是宽松类型(loosely typed)的;它不能编译成字节或位。我想起在几年前阅读过的一个JavaScript的介绍,告诫我们不要被JavaScript的名字所迷惑:JavaScript和Java没有什么关系。毕竟,Java很难学。
4 那么,实际情况又怎样呢?JavaScript到底是一种有趣的、轻量级的、有用但却不被认真对待的小脚本语言呢?还是一种可信任的、可用来实现网站的最重要功能的、强大的编程语言呢?JavaScript的真实情况是:它是将两种语言合二为一的语言;也正是这一真实情况令人混淆。
5 第一种语言是内置在Web浏览器和其他应用程序中的、用户友好的、易于使用的脚本语言,它能提供表单验证之类的功能,还有下拉菜单、数据更新时的颜色渐变和即席编辑页面之类的、很酷的新玩意。因为它实现于一个特定的环境中,通常是一个带有一些表单的Web 浏览器中,这同时也是一个受保护的环境。JavaScript不需要具备管理文件、内存或很多其他编程语言的基础功能,这使得JavaScript更加精益,更加简单。无论你是否有JS的经历,是否受过JS训练,甚至不论你以前是否有过编程经验,都可以开始JS的编程。
6 而第二种语言是一种成熟的、功能完备的、限制严格的、基于对象的语言。它确实需要你对它有更深一层的理解。如果使用得当,在只对服务器端的应用做一点点修改甚至是不修改的情况下它就能帮助Web应用扩展规模(增加其用户数)。它能够简化网站的开发工作,同时又提高网站的成熟度,使一个好的站点在其访问者面前表现得更好。
7 如果使用不当,JavaScript也会产生站点的安全漏洞,尤其是在与其他功能(比如Web服务或数据库表单)组合使用的时候。它还会使页面变得无法使用、不可读和可访问性降低。
8 在本书中,将向你介绍上面所描述过的两种语言:有趣的脚本语言和强大的面向对象的编程语言。更重要的是,本书将向你展示如何正确地使用JavaScript。
9 1.1 规范和实现相互交织的历史
10 学习一种编程语言并不需要学习它的历史,但学习JavaScript却例外,因为它的历史反映在今天的Web页面中。
11 JavaScript是由Netscape公司发明的,最初开发它是为了进行LiveConnect服务器端的开发。Netscape当时想要一种能实现与服务器端构件接口的脚本语言,于是就创造了这一脚本语言,并起名叫“LiveScript”。后来,在和SUN(Java语言的所有者)合作之后,Netscape的工程师将LiveScript改名为JavaScript,尽管它过去和现在都与Java没有关系。著名的JavaScript权威Steven Champeon这样写道:
12 让我们回到1995年初。Netscape刚刚聘用了从MicroUnity Systems Engineering出来的Brendan Eich,让他负责设计和实现一种新语言。分配给他的任务是让新加入到Navigator中的Java支持能够更方便地被非Java 程序员的访问,Eich最终断定:宽松类型的脚本语言才适合这样的环境;也就是说,读者(也就是数千的Web设计师和开发人员)既不需要使用字节码编译器,也不需要具备面向对象的软件设计知识,就能够把页面元素(比如表单、框架或图片)结合在一起。
13 为了反映语言的动态本质,他所创造的语言命名为“LiveScript”。但是没多久(在Navigator 2.0 beta开发结束之前),它就被更名为JavaScript。一个市场驱动的、并使Web设计师们困扰了好几年的错误就这样出炉了,他们在邮件列表和Usenet上不断地把这两种语言弄混。1995年12月4号,Netscape和Sun联合宣布了这一新语言的诞生,并称其是HTML和Java二者的补充。
14 (摘自《JavaScript: How Did We Get Here?》,O’Reilly Network,2001 年4月)。
15 为了不在技术上落后,Microsoft通过发布IE浏览器和它自己的脚本语言VBScript (源自于微软的流行产品Visual Basic)来反击Netscape的进攻。后来,微软也发布
16 了自己的类JavaScript语言版本:JScript。
17 这场浏览器及语言上的竞争冲击到很多公司对JavaScript的早期接受,尤其是最难以克服的挑战是:对于日益增长的页面,如何维护其跨浏览器的兼容性,更别提在提及名字的时候会造成的那种混淆了。
18 为了避开兼容性问题,Netscape在1996年将JavaScript的规范提交给国际组织欧洲计算机制造商协会(European Computer Manufacturer’s Association,ECMA),并将其作为一个标准化的成果来重新发布。来自Sun、Microsoft、Netscape以及其他对JavaScript进行了投资的公司的工程师应邀参与了这一过程,其结果就是在1997年6月发布了ECMAScript规范的第1个版本:ECMA-262。从那时起,大多数公司都支持一个公认的JavaScript(或者说JScript 或ECMAScript)版本,至少是支持ECMA-262。
19 你可以从http://www.ecma-international. org/ publications/ standards/ Ecma-262.htm下载ECMA-262的PDF文件。它不是一本让人兴奋的读物,但却是一本很适合放在身边的参考书。
20 严格地说,ECMA-262的第2个版本只是一个维护版本。第3版,也就是当前的版本发布于1999年12月。
21 然而,如果随着ECMA-262的通过,这一混淆就结束了的话,那就不是JavaScript 了。使Web发生混乱的是关于新版本的ECMAScript(即ECMA-357)的讨论。然而,ECMA-357不是ECMAScript的新版本;它是一个叫做E4X的扩展。这一扩展的目的就是为ECMA-262增加原生XML能力。ECMA-357发表于2004年,而现在JavaScript 1.6已经部分实现了E4X。
22 重要的是要记住,有很多老版本的脚本语言至今仍然还在使用。发现老的JScript 或者最早版本的JavaScript并不稀奇。为了澄清脚本语言的各种版本及其相互关系,表1-1给出了JavaScript、Jscript和ECMAScript版本之间的大致关系,还指出了当前流行的Web浏览器支持它们的哪个版本。
23 表1-1:浏览器对脚本的支持
|
浏览器 |
脚本支持 |
文档URL |
|
Internet Explorer 6.x |
ECMA-262(v3) /JScript 5.6 |
http://msdn.microsoft.com/library/ default.asp?url=/library/en-us/script56/ html/1e9b3876-3d38-4fd8-8596-1bbfe 2330aa9.asp |
24 (续)
|
浏览器 |
脚本支持 |
文档URL |
|
Internet Explorer 7.x |
ECMA-262 (v3) /JScript 5.6 |
http://msdn.microsoft.com/ie/ |
|
(Windows XP) |
||
|
Opera 8 and 8.5 |
ECMA-262 (v3) / |
http://www.opera.com/docs/specs/ |
|
JavaScript 1.5 |
js/ecma/ |
|
|
Firefox 1.5 |
ECMA-262 (v3) |
JavaScript 1.5 核心参考文档: |
|
部分支持ECMA-357 (E4X) |
http://developer.mozilla.org/en/docs/ |
|
|
/JavaScript 1.6 |
Core_JavaScript_1.5_Reference/ |
|
|
JavaScript 1.6核心参考文档: |
||
|
http://developer.mozilla.org/en/docs/ |
||
|
New_in_JavaScript_1.6 |
||
|
Tiger上的Safari 2.x |
ECMA-262 (v3) |
http://developer.apple.com/document |
|
ation/AppleApplications/Conceptual/ |
||
|
SafariJSProgTopics/index.html |
||
|
Camino 1.0 |
ECMA-262 (v3) /JavaScript 1.5 |
http://www.caminobrowser.org/ |
|
Netscape 8.1 |
ECMA-262 (v3) /JavaScript 1.5 |
http://browser.netscape.com/ns8/ |
|
各种无线设备 |
各不相同 |
此站点包含了对于一些模拟器和测试 |
|
的浏览器 |
工具的参考:http://www.wireless- |
|
|
devnet.com/channels/printlinks.phtml? |
||
|
category=4 |
25 当你访问一个Web页面并想知道其如何实现某个特性时,你通常可以根据它们如何声明脚本块来断定它们使用了什么版本的JavaScript。另外,这些老语言的一些部分还在影响JS的更现代的版本。在本章的后面,我们将更近距离地考察这些脚本块,我们还将看到老浏览器的影响贯穿本书,但是,重要的是要知道老版本的JavaScript及其变种依然影响着今天的应用。
26 贯穿本书的始终,我都会交替使用JavaScript和JS这两种写法。另外,除非特别注明,本书中的例子都基于ECMA-262和JavaScript 1.5/1.6。
27 1.2 跨浏览器的不兼容性和其他常见的JavaScript传说
28 JavaScript语言要运行在多种环境和多种平台之上。它能用来开发Web页面(和其他的应用),这些页面将会运行于Mac OS X、Windows和Linux等操作系统中。它不需要任何专门的下载或安装,因为无论你选择使用什么浏览器,浏览器中都内置了JavaScript。
29 很多浏览器都实现了JavaScript的一个公共子集,这使得大部分代码在不同浏览器上都能很好地兼容。这会令人混淆:既然语言的实现类似,那么跨浏览器的不兼容问题又会出现在哪里呢?
30 大多数跨浏览器不兼容的问题都是由于浏览器所开放的底层的文档对象模型
31 (Document Object Model,DOM)不同,而不是因为语言本身。例如,一个JavaScript语言的对象可以是Date或String;无论在Safari还是Navigator中实现,它都还是Date或String。一个DOM的实例对象应该是一个document对象,它代表了浏览器中保存Web页面的那一部分。在不同的浏览器各自对JavaScript(或ECMAScript)的实现中,如何开放和操作这些DOM对象,这导致了浏览器之间的不兼容性。
32 混淆的另一个地方关系到:在Web页面中,哪些由JavaScript处理,哪些又是通过使用级联样式表(Cascading Style Sheet,CSS)来处理的。对于页面中的元素,JavaScript所能做的主要是创建元素、删除元素或者更改元素的属性。而这些属性中,有一些又可以通过CSS样式属性来定义。
33 CSS可以定义Web页面中的元素的外观,还可以定义元素的某些行为。它能够隐藏或显示元素、改变颜色或字体、移动、改变大小或设置元素的形状等等。每种浏览器对于CSS的实现可能有所不同,这也导致了一些跨浏览器不兼容的问题。然而,JavaScript所做的一切,就是改变一个元素的CSS样式属性。
34 ECMAScript一致声称所有内置的JavaScript对象是相同的,但是在各种浏览器之间还是存在一些小的差异。然而,对于大多数情
35 况来说,过去的跨浏览器问题都是因为DOM或CSS的不同。
36 1.3 你能用JavaScript来做什么
37 在得到广泛使用的早期,JavaScript用于一些简单的任务:验证表格内容或设置和检索cookie(cookie能在浏览器关闭时持久保存少量字节数的信息)。在20世纪90 年代的后期,随着动态HTML(Dynamic HTML,DHTML)的引入,JavaScript也通过下拉菜单之类提供更加动态的用户体验。
38 JavaScript最近的爆炸式流行,是因为它是Ajax(Asynchronous JavaScript and XML,异步JavaScript和XML)的一个关键部分。Ajax自称将重新构造Web应用程序与用户的交互方式。随着时间的推移,很多跨平台的问题都已解决了,并且语言也变得更加成熟,因此JavaScript也不再仅仅是一种脚本语言,它已成为了一种功能全面的编程语言。
39 那么你可以用JavaScript来做什么呢?对于初学者来说,可以使用JavaScript:
40 验证表单字段
41 在把内容提交给服务器之前,验证表单中的输入。这样既节省了时间又节约
42 了服务器的资源,还提供了及时的反馈。设置和检索Web cookie 将用户名、账户或偏好设置之类的信息持久地保存于一个受控且安全的环境中。这样,在用户下一次访问此站点时,就能节省用户的时间。动态地更改页面元素的外观通过突出显示不正确的表单条目来提供反馈;根据读者的请求,将某部分的字体变大。隐藏和显示元素根据用户的偏好设置或用户的操作,显示或隐藏页面的内容(比如表单元素)展开编辑框以及改变一个图片的显示大小。在页面上移动元素创建一个下拉菜单,或提供一个动画光标来突出页面元素。捕捉用户事件并相应地调整页面根据键盘或鼠标的动作,使页面的某一部分变得可编辑。滚动内容对于较大的图片或内容区域,提供一种用鼠标或键盘抓住元素的方式,并可以对其进行上下左右的滚动。在不离开本页面的情况下与服务器端应用程序进行连接这是Ajax的基础,可以用于填充选项列表、更新数据以及刷新显示,而这一切都不需要重新载入页面。这有助于减少与服务器的交互次数,而与服务器的交互在时间和资源方面的代价都很高。你能做什么呢?可能问你不能做什么更好一些。
43 1.4 JavaScript初探:“Hello World!”
44 JavsScript如此流行的原因之一是:把JavaScript加入到Web页面中比较简单。最少情况下,你所要做的事情,只是在页面中包含一个HTML的script标记,将type属性设为JavaScript语言,然后增加想要的JavaScript代码:
45 <script type="text/javascript">
...some JavaScript
</script>
46 script标记通常加在文档的head元素中(head标记的开始和结束之间),但也可以包含在body元素中,还可以包含在这两部分之中。
47 例1-1显示了一个完整的、合格的Web页面,其中包括一个JavaScript语句块,它使用内置的alert函数来打开一个消息框,消息框中显示的是“Hello, World!”。
48 例1-1:文档head中的JavaScript语句块
49 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html>
<head>
<title>Example 1-1</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
50 <script type="text/javascript">
var dt = Date();
51 // say hello to the world
var msg = 'Hello, World! Today is ' + dt;
alert(msg);
52 </script>
</head>
<body onload="hello();">
</body>
</html>
53 将其复制到一个文件中,然后在任意一个浏览器中打开此文件。在页面加载后,立刻会弹出一个消息框。如果不是这样,可能是因为浏览器中禁用了JavaScript,或者是因为浏览器不支持JavaScript—这种情况在今天很少见。
54 虽然本例很简单,它还是展示了当前所使用的大多数JavaScript应用的基本部件。它值得我们进一步研究。
55 本书中的例子的设计都符合XHTML,这就意味着它们都包含DOCTYPE、文档标题和内容类型。在你重写这些例子时,可以丢掉这些。然而,较好的方式应该是:创建一个包含了DOCTYPE、标题、内容类型、head、body的Web页面框架,然后将其复制后用于大多数的例子。
56 1.4.1 script标记
57 与其他语言不同,JavaScript几乎总是嵌入到另一种语言的环境之中,比如HTML和XHTML(它们都是真正的语言,虽然动态的部分可能不是那么明显)。我这么说的意思是:脚本如何加到页面上是有限制的。你不能随心所欲地在页面中加入JS。
58
59 即便是在用于其他非Web环境之中时,JavaScript通常也是文档或模版中的一部分。
60 在例1-1中,(X)HTML的script元素标记中间包含的是JavaScript。这使浏览器知道:当它遇到这一标记时,它不应将此标记的内容处理成HTML或XHTML。从这一点开始,对于内容的控制权已转移给了另一个内置的浏览器代理,也就是脚本引擎。
61 并非所有嵌入到Web页面中的脚本都是JavaScript,script标记中包含了一个定义脚本类型的属性。在本例中,type属性的值为text/javascript。type属性的其他允许值为:
62 • text/ecmascript
63 • text/jscript
64 • text/vbscript
65 • text/vbs
66 第1个是JavaScript的另一种说法,接下来是Microsoft在IE中实现的一个JavaScript 的变种,最后两个是VBScript。
67 所有的这些type值都描述了内容的MIME类型。MIME也就是多功能邮件传递扩展标准(Multipurpose Internet Mail Extension),这是一种确定内容如何编码(如text文本)以及内容的特定格式是什么(javascript)的方式。此概念是从电子邮件技术中产生的,但它很快扩展到其他的因特网用途中,比如指明一个脚本块中的脚本类型。
68 提供了MIME类型,那些能够处理这一类型的浏览器就会执行它,而不能处理这一类型的浏览器就会跳过这一部分。这确保了脚本只由那些能够处理它的应用程序访问。
69 script标记的早期版本采用了一个language属性,它用于指明语言的版本,就像type属性的值为javascript那样,相应的language的值应为javascript 1.2。然而,尽管仍能看到很多这么写的JavaScript的例子,但HTML 4.01反对使用language属性。这其中涉及一项最早的跨浏览器技术。
70 几年前,在处理跨浏览器的兼容性问题时,针对每种浏览器专门创建脚本并不罕见。针对不同浏览器的脚本分别保存在不同的部分或文件之中,然后使用language属性来确保只有兼容的浏览器可以访问这一代码。让我们来看一下过去的(大概1997年左右)DHTML的例子:
71 <script src="ns4_obj.js" language="javascript1.2">
</script>
72 <script src="ie4_obj.js" language="jscript">
</script>
73 这种方法的思路是:只有那些能处理JavaScript 1.2的浏览器才会使用第1个语句块
74 (那时主要是指Navigator 4.x),而只有那些能处理JScript的浏览器才会使用第2个语句块(IE4)。够勉强的吧?确实,但是在早期的那些年,在试图处理频繁受阻的跨浏览器DHTML的时候,一直都是使用这种凑合的方法。
75 然而最终,人们的偏好转向一种叫做对象检测的方式,这种方法只是在不赞成使用language属性的情况下才鼓励使用的。我们会在后面的章节中更详细地探讨对象检测,特别是在那些与Ajax相关的章节中。目前,对象检测的方式是:测试某个特定的对象或对象的属性是否存在;如果存在,就执行JavaScript的一个分支,否则就执行另一个分支。
76 我们回过头来继续说script标记,此标记其他的有效属性是src、defer和charset。charset属性定义了脚本使用的字符编码方式。除非你需要使用的字符编码方式与文档所定义的字符编码方式不同,这一属性通常不需要设置。
77 一个非常有用的属性是defer。如果把defer属性的值设为“defer”的话,它会告诉浏览器:脚本不会生成任何文档内容,浏览器可以继续处理页面的其他部分,当页面处理和显示完成之后再返回到脚本:
78 <script type="text/javascript" defer="defer">
...no content being generated
</script>
79 在使用一个较大的JavaScript语句块或引用一个较大的JS库时,使用这一属性有助于加快页面加载的速度。最后一个属性是src,它和加载这样的程序库有关,我们在后面再来探究它。
80 1.4.2 JavaScript代码的位置
81 在例1-1中,JavaScript语句块嵌入在Web页面的head元素中。脚本也可以包含在body中,例1-2是修改后的应用程序。
82 例1-2:将JavaScript嵌入到文档body标记中
83 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0
Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html>
<head>
<title>JavaScript Code Block Example</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
</head>
<body>
<script type="text/javascript">
//<![CDATA[
84 var dt = Date();
var msg ='<h3>Hello, World! Today is ' + dt + '</h3>';
document. writeln(msg);
85 //]]>
</script>
</body>
</html>
86 注意:在本例中,没有使用alert函数,而是直接使用DOM中的document对象向页面写信息。
87 对于JS在什么时候应该包含在head中,什么时候应当包含在body中,人们有着不同的看法。但下面的规则是适用的:
88 1 1. 当JavaScript要在页面加载过程中动态建立一些Web页面的内容时,应将JavaScript放在body中。
89 2 2. 定义为函数并用于页面事件的JavaScript应当放在head标记中,因为它会在body 之前加载。
90 放置脚本的一个很好的经验规则是:仅当页面载入期间脚本会建立一些Web页面内容时,才将脚本嵌入在body中;否则,将其放在head元素中。采用这种方法,页面就不会被脚本搞得一团糟,在每个页面中,总可以在同一个位置找到脚本。
91 有种方法可以避免将JavaScript插入到body中,即使用DOM生成一个新内容,再将其附加给一个页面元素。本书的后面会介绍
92 这种方法。
93 1.4.3 隐藏脚本
94 在例1-2中,脚本块包含在一个XHTML CDATA块之中。XHTML 处理器不会解释CDATA块中所含的数据。
95 使用CDATA块的原因是:XHTML处理器会解释诸如header (H3)的开始和结束等标记,即便这些标记包含在JavaScript字符串中也要解释。如果不使用CDATA,虽然页面可能会显示正确,但是当你进行页面验证时,会看到有验证错误。
96 那些通过使用SRC属性而导入到页面中的JavaScript,会被假设为与XHTML兼容,因而不需要用CDATA块。但是,内联的或嵌入的JS应当使用CDATA来隔开,尤其是当它包含在BODY元素之中时。
97 对于大多数浏览器,你还需要使用JavaScript注释符号(//)来隐藏CDATA块的开始和结束标记。否则,就会得到一个JavaScript错误。
98
99 JavaScript最佳实践:同时使用CDATA块和JavaScript注释很重要,这是本书将介绍的众多JavaScript最佳实践中的第一条。
100 在使用一个XHTML DOCTYPE时,应将内联JavaScript或嵌入JavaScript包含在CDATA块中,然后再使用JavaScript注释将CDATA块注释掉。Web页面应当总是采取XHTML格式,因此总要使用CDATA。
101 当然,保持Web页面不混乱的最好方式是:从页面中完全删除JavaScript,转而使用JavaScript文件。本书的大多数例子都嵌入在页面中,这主要是为了便于创建。然而,Mozilla Foundation建议:从页面中清除所有内联的或嵌入的JavaScript,将其放入独立的JS文件中。这么做能够防止验证和对文本的不正确解释的问题,而无论页面是作为HTML还是XHTML处理。
102
103 JavaScript最佳实践:将JavaScript程序代码块放入到外部JavaScript文件中。
104 1.4.4 古怪模式与标准模式和DOCTYPE
105 在这些例子中,使用的DOCTYPE是XHTML 1.0 Transitional(过渡模式),尽管本书的所有例子都用作HTML,并且带有.html扩展名。使用DOCTYPE可以影响如何解释页面标记、CSS甚至JavaScript。所有这些都基于一个叫做“古怪(quirks)”模式,与之对应的就是标准(standards)模式或严格模式。
106 浏览器改进了它们对于标记和CSS标准的支持,同时,它们还必须维持对于为旧浏览器创建的页面的向下兼容。为此,一种方法是按照该浏览器早期支持“古怪”行为的方式显示这些页面,这种模式就是所谓的“古怪模式”。根据DOCTYPE类型不同,浏览器要么使用诸如IE的名声狼藉的CSS盒子模型缺陷之类的对规范的早期解释来显示页面,要么根据基于标准模式的方式来显示页面。
107 对于大多数浏览器,XHTML 1.0 Transitional DOCTYPE触发了标准模式,即便页面本身没有说明应作为一个XHTML文档来解释。要想实际上作为一个XHTML来使用,需要将XHTML MIME类型传给浏览器,通常这由扩展名是否为.xhtml来触发。当然,还需要对页面进行其他修改,如确保它是一个合法的XHTML,还要修改HTML标记的开始标记,使之包括标记的名字空间,这个名字空间所指的位置有所有元素的定义:
108 <html xmlns="http://www.w3.org/1999/xhtml">
109 对于大多数浏览器,HTML 4.01 strict (严格模式)DOCTYPE也会触发标准模式,但本书中没有使用,因为它不会验证页面是否使用了适当的XML标记。例如,在大多数例子中,meta标记使用了一种带关闭标记的格式:
110 <meta http-equiv="Content-Type"content="text/html; charset=utf-8" />
111 但这对于HTML 4.01 strict DOCTYPE,是不合法的,对于这种DOCTYPE模式,我们需要使用:
112 <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
113 虽然HTML 4.01是一个目前依然支持的标准,但我们应该尽可能养成使用XHTML 特有标记的习惯,即便是页面仍然作为HTML来使用。这意味着:当我们准备删除掉所有HTML,使用纯粹的XHTML文档时,我们的工作就少了。
114 我前面提到过DOCTYPE如何影响JavaScript。然而,实际上,它对如何使用该页面文件也有影响。有几个例子都使用了document.write或document.writeln。我使用这两个方法的原因是:这些例子修改了页面。在本书的后面,我将介绍文档对象模型(DOM)并展示如何使用“适当”的方法来修改文档。然而,只有当我们学到那里,并且在那些提示对话框不能很好起作用的情况下,这些例子才会使用document.write。遗憾的是,使用document.writeln有一个缺点,这就是:在当做XHTML来使用的文档中,它是不合法的。实际上,它也不能起作用。
115 显示为XHTML的文档不允许document.write的原因是:XHTML文档被当做是一个合法的XHTML,如果不是就不会显示。如果我们可以使用document.write修改文档,浏览器就没办法知道写到页面中的内容是否合法。我们可以引入“坏”标记,从而来显示对于XHTML合法性的保证。
116 在使用innerHTML(我们将在本书的后面介绍)时,也存在同样的顾虑。然而,大多数浏览器支持这个相当重要的属性,有些浏览器甚至会验证那些用来设置innerHTML的内容,以确保它是合法的。
117 在本章的其他章节中,都使用了XHTML 1.0 Transitional DOCTYPE来确保使用标准模式。然而要记住:名字空间设置(http://www.w3.org/1999/xhtml)、扩展名(.xhtml),还有是否出现document.write,都会影响那些实际被作为XHTML来使用的文档。在本书可下载的例子中,提供了使用不同文档类型和扩展名的多个版本,用以讨论本节所讨论的差异。每个文件中都使用了注释来讨论为了使文档正
118 确和合法做了哪些变更。
119 1.4.5 JavaScript文件
120 随着JavaScript变得更加面向对象和更加复杂,开发人员开始创建可复用的JS对象,这些JS对象用于不同开发者所开发的各种应用中。复用这些对象的唯一有效的方法就是:把它们创建在独立的文件中,然后在Web页面中提供文件的连接。除了便于复用之外,JavaScript文件还有其他的优点。例如,不在很多页面中重复相同的代码,从而避免在修改的时候要更改很多地方。代码创建在一个文件中,任何修改只要在一个地方进行就可以了。现在,除了最简单的JavaScript之外,所有的JavaScript都建立在独立的脚本文件中。无论使用多个文件会造成什么样的额外开销,这些开销所带来的好处则更多。
121 在Web页面中引用一个JavaScript库或一个脚本文件时,使用这样的语法:
122 <script type="text/javascript" src="somejavascript.js"></script>
123 虽然script元素中不包含内容,但是还是必须要有一个结束标记。
124 在浏览器将脚本文件载入页面时,载入的顺序是它们出现在页面中的顺序,处理脚本文件也按照同样的顺序,除非使用了defer。应当把脚本文件看成是实际包含在页面中的代码;脚本文件和嵌入的JavaScript语句块在行为上没有不同。
125 本书整个后半部分讲述的都是创建和使用定制的程序库,而第11章则讲述了很多基础知识。
126 1.4.6 注释
127 每个以双斜线(//)开始的行都是JavaScript的注释,注释通常是附近的代码的解释。在JavaScript中,注释是非常有用的方式,能够迅速注明此代码块在做什么以及它有什么依赖。注释使代码更具可读性,更具可维护性。
128 在应用中,你可以使用两种不同类型的注释。第一种是使用双斜线,只是注释特定一行:
129 // This line is commented out in the code
130 第2种用法是使用JavaScript注释的开始和结束分隔符,即(/*)和(*/),这样可以标记一行以上的注释:
131 /* This is a multiline comment
that extends through three lines.
Multiline comments are particularly useful commenting on a function */
132 单行注释使用起来相对安全,而对于多行注释,如果意外删除掉开始或结束符号,就有可能产生问题。
133 通常,单行注释用在那些进行一个特定处理或创建一个特定对象的JS语句块之前;多行注释用在一个JavaScript文件的开始。
134 JavaScript最佳实践:在每个JavaScript语句块、函数或对象定义的开始,都使用至少一行注释。另外,在所有JavaScript库文件的开始都提供一个更详细的注释块,其中包括作者、日期、依存关系以及脚本的详细目的等信息。
135 1.4.7 浏览器对象
136 尽管例1-1和例1-2很小,但它们都使用了一组强大的浏览器内置的全局对象来跟用户通信。
137 第1个例子使用了alert函数来创建一个小弹出窗口(通常叫做对话窗口)来显示信息。虽然在文本中没有特别标明,alert对话框是window对象的一个函数。window 对象是浏览器对象模型(Browser Object Model,BOM)的顶级对象。BOM是一组在大多数现代的浏览器中实现了的基本对象。
138 第2个例子也使用了一个来自BOM的对象:document对象。例1-2使用document对象向页面写信息。document对象、window对象以及所有BOM对象将在第9章中讲述。
139
140 BOM是前面提到过的DOM的一个变种,有时它也被称作0版的DOM。
141 1.4.8 JavaScript内置的对象
142 例1-1和例1-2还使用了另两个内置对象,虽然只有一个是显式使用的。这个显式的对象就是日期对象date,它读取了今天的日期。而第2个对象是隐式的,这就是字符串对象string,它是调用date函数时所返回的对象的类型。实际上,下面的代码都是同样代码的不同的等价实现:
143 var dt = String(Date( ));
var dt = Date( ).toString( );
144 我们将在第4章中更详细地讲述JavaScript的内置对象string和date。
145 1.4.9 JavaScript用户自定义函数
146 在例1-1中,全局函数和内置对象使用在一个用户自定义函数(user-defined function,UDF)的环境之中。创建一个UDF的典型语法是:
147 function functionname(params) {
...
}
148 关键字function之后是函数的名字,然后是括号,括号中可以包含0个或多个参数(函数参数),接着是函数代码,代码包含在一对花括号之中。函数可以返回值,也可以不返回值。用户自定义函数封装了一段JavaScript代码块,可以用于以后的或重复的处理。
149 从技术上讲,函数是另一种内置的JavaScript对象。它们看起来像是语句,但你不需要担心这种差别,除非你创建了很多函数。虽然它们是对象,但由于它们很复杂也很重要,因此需要用第5章整整一章来专门讲解函数。
150 1.4.10 事件句柄
151 在例1-1中,body的开始标记中有一个属性叫做onload,对onload所赋的值就是hello函数。onload属性就是我们所说的事件句柄(event handler)。这个事件句柄,还有其他事件句柄,都是每种浏览器都支持的DOM的一部分。它们可以将一个函数绑定到事件上,这样当事件发生时,相应的代码就会执行。
152 在不同类型的元素中,可以捕获不同的事件,每个事件上都可以指定事件发生时应当执行的代码。
153 直接在元素的标记上增加事件句柄,是绑定事件句柄的一种方式。第2种方法是在JavaScript中使用如下的语法:
154 <script type="text/javascript">
document.onload=hello( );
155 function hello( ) {
156 var dt = Date( );
157 var msg = 'Hello, World! Today is ' + dt;
158 alert(msg);
159 }
160 </script>
161 使用这种方法,你就不用把事件句柄作为属性加入到标记中,而是将它们加入到JS本身中。在第6章中,我们将更详细地讲述事件句柄。虽然上述例子中没有演示出这一点,但事件常常用来和HTML表单一起,在提交之前验证表单的数据。我们将在第7章中讲述表单。
162
163 Mozilla提供了一个很好的文档集,讲述的是Gecko引擎(就是Camino和Firefox之类的浏览器中用来实现JavaScript的引擎)。事
164 件句柄的URL是http://www.mozilla. org/docs/ dom/ domref/ dom_event_ref.html。
165 1.4.11 关键字var和作用域
166 我们已经看过了内置对象、内置函数、用户自定义函数和事件句柄。现在我们可以对JavaScript代码的每一行进行扼要的讲解。
167 例1-1和例1-2使用关键字var来声明变量dt和msg。使用var声明的变量,每个变量的作用域都是局部的,这就意味着它们只能在它们所定义的函数内部访问。如果不使用var的话,变量的作用域就是全局的,也就是变量可以被Web页面中任何地方的所有JavaScript代码(或者在本页面所包含的任何外部JS库中)访问。
168 如果你有相同名字的全局变量和局部变量的话,设定变量的作用域很重要。例1-1 没有任何全局变量,但是重要的是从开始就养成良好的JavaScript编程习惯。这种习惯之一就是:显式地定义变量的作用域。
169 下面是一些考虑作用域时的规则:
170 � • 如果函数中的变量在声明时使用了关键字var,它就只能在此函数的局部使用。
171 � • 如果函数中的变量在声明时没有使用关键字var,而又存在一个同名的全局变量,它就会被当作那个全局变量。
172 � • 如果变量在局部中声明并使用了关键字var,但它又没有被初始化(比如赋值),它可以访问但没有被定义。
173 � • 如果变量在局部中声明,但没有使用关键字var,或显式地声明为全局变量,但它又没有被初始化,它在全局上可以访问但没有被定义。
174 通过在函数中使用var,你可以防止使用同名的全局变量和局部变量所造成的问题。这在使用外部JavaScript库的时候尤为关键(关于JS变量和简单数据类型的详细内容,请参见第2章)。
175 1.4.12 属性操作符
176 在JavaScript中,有几种操作符:数学操作符(+和-)、条件操作符(<和>),以及其他一些操作符,这些都会在本书后面的章节中全面详细地讲述。例1-2向你介绍了第一个操作符:点操作符(.),它也叫做属性操作符(property operator)。
177 在例1-2中的下面一行中,属性操作符访问了document对象的一个特定属性:
178 document.writeln(msg);
179 数据元素、事件句柄以及对象方法都被认为是JavaScript中的对象属性,它们都要通过属性操作符来访问。
180 1.4.13 语句
181 这两个例子演示了JavaScript语句的一个基本类型:赋值语句。JS语句有几种不同的类型,包括:赋值、打印消息、查找数据直到符合某个条件等等。我们对于JavaScript的初探的最后一个部分就是JS语句的概念,包括它的终止符:分号(;)。
182 例1-1中的语句都以分号结尾。这在JavaScript中不是一个绝对的要求,除非你想要在同一行中写多个语句。如果要在同一行中写多个语句的话,你就必须在每个语句后面使用分号进行分隔。
183 当在一行中输入一个完整的语句而没有带分号结束时,换行符终止了这一语句。然而,和var的使用一样,这是一个能够帮你避免某些错误的好习惯。我在使用JS 开发时,所有的语句中都使用分号。
184 在第3章中,我们将讲述分号、其他操作符和语句。
185 1.4.14 你过去没看到什么
186 十年以前,大多数浏览器还处于它们的第一或第二版,它们对JavaScript的支持都很粗浅,并且每个浏览器实现的版本也不一样。那时的浏览器(比如基于文本的Lynx),在遇到script标记时,通常只是把脚本打印输出到页面上。
187 为了防止这种问题,把脚本内容包在HTML注释<!--和-->之间。使用了HTML注释以后,不支持JS的浏览器会忽略那些注释掉的脚本,而新浏览器则知道应执行这些脚本。
188 这是一种勉强的方法,但是这是在那时应用非常广泛的一种方法。而现在,大多数带有JavaScript 的Web页面都具有加入了HTML注释这一特征,因为复制来的脚本代码要比原创的多。遗憾的是,今天,一些新浏览器会把XHTML严格作为XML来处理,这就意味着注释的代码将会被抛弃。在这种情况下,JavaScript就被忽略了。因此,HTML注释已经不再受欢迎,在本书的例子中也没有使用注释。
189 JavaScript最佳实践:不要使用HTML注释来“隐藏”JavaScript。不支持JS的浏览器已经不存在了,而使用HTML注释会与创建成
190 XHTML的页面产生冲突。
191 1.5 JavaScript沙箱
192 当JavaScript第一次发布的时候,有一个可以理解的忧虑,那就是打开一个页面可能会直接在机器上执行一段代码。如果JavaScript中含有一些有害的代码,比如删除所有Word文档,或者更糟的是,向脚本的编写者复制这些Word文档,那该怎么办呢?
193 为了防止这种情况发生,同时也为了让浏览器的用户放心,JavaScript构建为只在沙箱中运行。沙箱是一个受保护的环境,在这个环境中,脚本不能访问浏览器所在的计算机资源。
194 另外,浏览器所实现的安全条件高出并超过了JavaScript语言所建立的最低条件。这些都定义在一个与浏览器相关的安全策略中,它决定了脚本能做什么不能做什么。例如,一个这样的安全策略规定脚本不能与脚本所来源的域以外的页面通信。大多数浏览器还提供了定制这一策略的方式,这可以使脚本所运行的环境限制变得更严或更松。
195 不幸的是,即便是有了JavaScript沙箱和浏览器安全策略,JavaScript还是经过了一段难熬的时光,黑客已经发现并充分利用了JavaScript的一些错误,有些错误与浏览器无关,有些错误与浏览器有关。较严重的一个是跨站脚本(cross-site scripting,XSS)。这实际上是一类安全破坏(其中一些通过JavaScript,另一些通过浏览器的漏洞,还有一些通过服务器),它能够导致cookie盗窃、暴露客户端或网站的数据,或导致许多其他的严重问题。
196 我们会在后面的章节中更详细地讨论XSS,以及如何防止XSS,还有其他安全问题及其防治。我们将在第8章讲述名声不太好的小糖果——cookie。
197 CERT 是在安全问题方面最具权威性的网站,访问网址http://www. cert.org/advisories/CA-2000-02.html可以找到一篇专门讨论XSS的文章。CGISecurity.com网站上也有一篇关于XSS的、有深度的FAQ文件,地址是:http://www. cgisecurity. com/ articles/xss-faq.shtml。
198 重要的是要知道:即使有部分浏览器供应商的一片好心,JavaScript还是很容易受到攻击。然而,这并不应该妨碍你对JavaScript的使用;通过理解问题的本质,遵循安全专家建议的步骤,你可以防止大多数问题。
199 1.6 可访问性和JavaScript的最佳实践
200 在理想的世界中,每个访问你的Web网站的人都使用同样的操作系统和浏览器,并且都启用了JavaScript。永远不会有人通过移动电话或其他各式各样的设备访问网站;盲人不需要屏幕阅读器,瘫痪者不需要语音导航。
201 这不是一个理想世界,但很多JS开发者编码时却总把这当作是理想世界。我们变得急于追逐那些我们所能创造的奇迹,反倒忘记了并非所有人都能分享这些奇迹。
202 与JavaScript相关的最佳实践有很多,但是如果说有一条最佳经验是在你读过本书后要时刻牢记的,那这就是:无论你创建一个什么样的JavaScript功能,它绝不能离间你的网站和其访问者。
203 “离间你的网站和其访问者”是什么意思呢?应避免以这样的方式使用JavaScript:对于那些不能或不愿启用JavaScript的人,如果他们使用不启用脚本的浏览器,便不能访问网站的基本资源。如果使用JS创建了一个下拉菜单,那么你还要为使用不支持JS的设备的人们提供另一种导航方法。如果你的访问者是盲人,那么JS绝不能妨碍语音浏览器的工作;如果访问者使用黑白屏幕的手机,或者他们是色盲,那么页面就不应依靠颜色来提供反馈。
204 很多开发者没有遵循这些实践原则,因为他们认为这些实践原则会增加额外的工作,而在大多数情况下,这确实增加了额外的工作。然而,这部分工作并不一定是个负担—当结果是提高了网站的可访问性(accessibility)的时候,这就不是个负担了。另外,现在很多公司都要求他们的网站能达到某个级别的可访问性。最好在开始时就养成创建具有可访问性页面的习惯,而不要等到事后再去修正这些页面或纠正你的习惯。
205 1.6.1 可访问性指南
206 关于如何创建具有可访问性的JavaScript,WebAIM网站(http://www. webaim.org)上有一个出色的教程(下载地址为:http://www.webaim.org/techniques/javascript/)。它讲述了你应避免的JavaScript的使用方式,比如将JS用于菜单或其他导航方式。此外,网站也提供了一些使用JS的方式,它们能够使一个网站具有更强的可访问性。
207 一个建议是:无论你是否使用鼠标都能触发的事件,以此作为基本反馈。例如,不要捕获鼠标点击事件,应捕获你使用键盘或鼠标都能触发的事件,比如onfocus 或onblur。如果有下拉菜单,应当提供一个指向独立页面的连接,并在那个独立页面上提供一个静态的菜单。
208 在看完WebAIM上的教程之后,你会想花点时间看一下W3C的Web可访问性行动(Web Accessibility Initiative,网址为http://www.w3.org/WAI/)。从那里,你还能访问到美国政府508规范的网站,那里讨论了什么叫做“符合508规范”。对于符合508规范的网站,无论有什么物理约束,都具有可访问性。在这个Web网站上,你可以访问到各种工具:用以评估可访问性的工具,比如Cynthia Says网站(Cynthia Says ,网址为http://www.cynthiasays.com/);将不具可访问性的Word或Adobe PDF文件转化为HTML文件的工具,比如伊利诺斯可访问性Web发布向导
209 (Illinois Accessible Web Publishing Wizard,网址为http://cita.rehab. uiuc.edu/ software/office/);还有帮助你从开始就开发具有可访问性内容的工具,比如Web 可访问性工具栏(Web Accessibility Toolbar,网址为http://cita.rehab. uiuc.edu/ software/office/)。
210 无论你的网站是否位于美国,你都希望它能具有可访问性;因此,无论你在哪里,看看508 规范都很有用。
211 当然,不是所有的可访问性问题都和那些缺省地限制或禁用JavaScript的浏览器有关,也会与阅读者有关。很多人不信任JavaScript,或者不关心它,因而选择禁用JavaScript。对于这两类人(包括那些不愿使用JavaScript的人和那些无法选择的人),提供一个不能使用脚本时的替代方案很重要。替代方案之一就是noscript。
212 1.6.2 noscript标记
213 一些浏览器或别的应用没有处理JavaScript的部件,或者它们解释JavaScript的能力有限。如果JavaScript对于导航或交互不是至关重要的,那么即使浏览器忽略了脚本,也不会有什么损害。然而,如果JavaScript对于访问网站的资源是至关重要的,而你又没有提供替代方案,那么就等于是在告诉这些人:请走开。
214 几年前,那时JavaScript还相当新,有种流行的方法就是:提供另一个可以通过链接访问到的纯文本页面,而链接通常会放在原页面的顶部。然而,维护两个网站的工作量实在令人望而却步,更不用说还要一直操心怎样保持两个网站的同步了。
215 一项更好一些的技术是:为动态的、脚本生成的内容提供一个静态替代。在你使用JavaScript创建一个下拉菜单时,同时还要提供一个标准的、多层次的、链接式的菜单;当你使用脚本根据用户交互情况将表单元素改变为可编辑时,还要再提供一些传统的链接指向第二个页面来完成同样的工作。
216 能使所有这些变为可能的标记就是noscript。在你需要静态内容的地方,增加一个noscript元素,将要显示的内容包括在开始和结束标记之间。然后,如果一个浏览器或其他应用不能处理脚本(因为某些原因而将JavaScript禁用了),那么它就会处理noscript的内容;否则的话,noscript的内容会被忽略掉。
217 例1-3显示的是在我们原来的例子中加入了noscript的情况。当使用一个启用了JavaScript的浏览器的时候,该页面会显示一个标着“First Example”的链接。如果你在浏览器的设置选项中禁用了JavaScript,该页面所显示出的就是一个标着
218 “Original Example”的链接。
219 例1-3:对于未启用JavaScript的浏览器使用noscript标记
220 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html>
<head>
<title>Example 1-3</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
</head>
<body>
<script type="text/javascript">
var dt = Date( );
var msg ='<a href="js1.htm">First Example</a>';
document.writeln(msg);
221 </script>
<noscript>
<a href="js1.htm">Original Example</a>
</noscript>
</body>
</html>
222 本例只是noscript标记的一个简化的用法;在本书后面的章节中,你会看到更复杂的用法。
223 虽然noscript很有用,但是在较复杂的页面中,在网页中各处嵌入noscript元素的工作会变得很枯燥。在下一节中,我们会介绍一种替代方法。
224 还有一种情况会处理noscript内容,那就是:浏览器或别的应用虽然启用了脚本,但它们却不能处理脚本块中的MIME类型。这时,脚本也不能执行,也会处理noscript内容。然而,很多流行的浏览器,比如Firefox和Safari,在这种情况下都不处理noscript 内容。这是一个错误,如果你依赖noscript的话,你应当知道这个错误。
225 1.6.3 noscript的替代方案
226 向Web页面增加的东西越多,Web页面就越难以维护。如果使用JavaScript来提供大量的功能,并使用noscript来提供静态替代内容,那么页面就会变得很大,也很复杂。
227 当你根据用户的交互隐藏或显示Web内容时,还有另一种方法,这也是我建议的方法:使用静态元素设计页面,然后使用脚本。使用脚本时,一种方法是:隐藏原有的静态元素,再提供替代的动态内容;另一种方法是:保留原有的静态元素,然后在此基础上提供一些动态内容作为额外的补充。
228 目前最流行的照片网站Flickr(http://flickr.com)采用的就是这种技术。当你作为照片的所有者来访问一个照片页面时,无论是否启用了JavaScript,你都能看到一个链接,点击这个链接后可以编辑照片的标题、标记和描述。而如果你启用了JavaScript,点击标题或描述,会把原来的标题或描述直接变成一个编辑框;点击单独的链接“Add a tag”,会打开一个编辑框,可直接增加一个新标记,如图1-1 所示。
229 如果JavaScript被禁用了,点击标题和描述不会引起页面发生任何变化,也看不到那个增加标记(“Add a tag”)的链接。因为CSS属性的显示不依赖于JavaScript,无论是否启用脚本,这些项目都是隐藏的。
230
231 图1-1:Flickr中使用的DHTML和Ajax
232 当启用了脚本时,通过将事件句柄与页面元素(如标题和描述)相关联,在Web 页面浏览者点击了这些项目时,你就可以使用JavaScript来把那些先前被隐藏了的对象显示出来。
233 我不赞成Flickr将信息显示给用户的方式。这一信息(如图1-2所示)会给用户留
234
235 图1-2:禁用JavaScript的Flickr页面,上面显示有“if only(仅在⋯)”信息
236 下这样的印象:如果他们使用启用了JavaScript的浏览器,他们就能看到这样那样的功能。问题是有些人可能不能使用JavaScript。那些能够启用JavaScript但是却选择禁用JavaScript的人,通常有很合理的理由这样做。这理由不可能因为一个网站而改变,无论他们多么喜欢这个网站。在页面上增加一个“if only(仅在⋯)”的信息,就像20世纪90年代末非常流行的“您需要使用IE浏览器才能查看这些页面”一样。在那时,这是一个糟糕的想法,用来告诉Web页面的浏览者应该用什么不应该用什么;而现在,它依然是个糟糕的想法。
237 你也可以放上一个“噢,你真是个讨厌鬼”的符号,因为这正是你所要表达的意思。
238 1.6.4 使用浏览器和其他开发工具
239 在JavaScript刚刚实现时,人们对它的接受很慢,因为没有这种脚本的调试工具和开发工具。现在,大多数浏览器都有内置了JavaScript控制台或其他工具,这就简化了JavaScript的开发和调试过程。
240 Firefox有一个能列出错误和警告的JavaScript控制台。点击工具条上的符号(错误显示为停止符号,警告显示为一个带惊叹号的三角形,消息显示为带i字的会话气泡)或选择Tools菜单下的JavaScript Console项,都可以打开JavaScript控制台。该控制台为每个页面的JavaScript都提供了调试信息,这些信息会保持到你点击清除按钮清除控制台内容为止,如图1-3所示。
241
242 图1-3:Firefox中的JavaScript控制台
243 Firefox还提供了一个DOM查看器(DOM Inspector)。这是一个非常有用的工具,它使你能够查看页面中的DOM对象,它包括下列非常有用的信息(图1-4显示了其中的已计算样式):
244 DOM节点
245 节点名称、类型、类、名字空间URI和值盒子模型(Box Model)
246 位置,x和y值已计算样式(Computed style)
247 对象的缺省样式XBL绑定
248 可扩展的绑定语言(Extensible Binding Language,本书没有涉及)CSS样式规则
249 样式表文件所给出的缺省应用于元素的CSS样式规则
250
251 图1-4:在Firefox DOM查看器中显示的已计算样式
252 图1-5中显示的JavaScript对象尤其重要,因为它提供了可通过JavaScript针对对象访问的事件、特性、属性、函数的列表。
253
254 图1-5:DOM查看器中的JavaScript对象
255 除此之外,现在已经有了很多针对JavaScript的工具,既有独立的工具,也有嵌入的工具。对于那些便于使用的小配件、程序库和工具,我没有在专门的一章中介绍它们,我会在几章的版块中都给出它们的简要介绍。
JavaScript最好的一点是它的宽容,尤其是在数据类型方面。如果你开始时使用了一个字符串,然后又想把它用作一个数字,JavaScript能很好地完成这些(当然,字符串中实际保存的应当是个数字,而不是个邮件地址之类的东西)。如果你后来又想把它再当成是字符串,同样没有问题。
也有人会说JavaScript的宽容本性是这一语言最糟糕的方面之一。如果想把两个数字加在一起,而JavaScript引擎却把其中一个变量解释成字符串,那么得到的就会是一个奇怪的字符串,而不是我们期望得到的那个和。
在说到JavaScript的数据类型时,环境(context)就是一切。而当说到处理变量这一最基本的JavaScript元素的时候,环境也一样重要。
本章将讲述JavaScript 的3个基本数据类型:字符串型、布尔型和数值型。在这过程中,我们会研究一下字符串中的转义序列,并简要讲述一下Unicode。本章还将探讨和变量有关的话题,包括变量的作用域,以及如何建立一个有效并且有意义的变量标识符。我们还将讲述基于Ajax的最新一代的JavaScript应用引发的对标识符的影响。
2.1 变量的标识
JavaScript变量都有一个标识符、作用域和具体的数据类型。因为JavaScript是宽松类型的,也就是说,数据类型可以不事先声明就改变。
JavaScript中的变量和其他语言的变量很相似;它们用来保存值,使该值可以在代码的不同地方显式地访问到。在其所使用的作用域(后面还会详细讨论)范围内,每个变量的标识符是唯一的。标识符由字母、数字、下划线和$符组成。标识符必须以一个字母、$符或下划线开始,除此之外,没有格式上的要求。
_variableidentifier
variableIdentifier
$variable_identifier
var-ident
从JavaScript 1.5开始,你还可以在变量标识符中使用Unicode的字母(比如ü)和数字,也可以使用转义序列(比如\u0009)。下面的变量标识符对于JS也是合法的:
_üvalid
T\u0009
JavaScript是区分大小写的,即把大写字母和小写字母看作不同的字母。下面的两个变量标识符在JS中被看作是两个不同的变量:
strngVariable
strngvariable
另外,变量标识符不能是JavaScript关键字。表2-1列出了JavaScript关键字。在新版本的JavaScript(从技术上叫做ECMAScript)发布时,还会增加一些其他的关键字。
表2-1:JavaScript关键字
|
break |
else |
new |
var |
|
case |
finally |
return |
void |
|
catch |
for |
switch |
while |
|
continue |
function |
this |
with |
|
default |
if |
throw |
|
|
delete |
in |
try |
|
|
do |
instanceof |
typeof |
由于有了扩展ECMA 262规范的提案,表2-2中的也被视为是保留字。
表2-2:ECMA 262规范的保留字
|
abstract |
enum |
int |
short |
|
boolean |
export |
interface |
static |
|
byte |
extends |
long |
super |
|
char |
final |
native |
synchronized |
|
class |
float |
package |
throws |
|
const |
goto |
private |
transient |
|
debugger |
implements |
protected |
volatile |
|
double |
import |
public |
除了ECMAScript保留字之外,在大多数浏览器中都实现了一些JavaScript专用字,它们被视为是相应实现中的保留字。这些保留字中很多是基于浏览器对象模型
(BOM)的对象,比如document和window。表2-3中列出了一些较常见的保留字,不过它并不是最终的一个列表。
表2-3:浏览器中典型的保留字
|
alert |
eval |
location |
open |
|
array |
focus |
math |
outerHeight |
|
blur |
function |
name |
parent |
|
boolean |
history |
navigator |
parseFloat |
|
date |
image |
number |
regExp |
|
document |
isNaN |
object |
status |
|
escape |
length |
onLoad |
string |
2.1.1 命名规则
任何名字都可以用于代码中的变量和函数,但是有一些命名的惯例,其中很多是从Java和其他编程语言那里继承过来的。这些惯例能使代码更易于理解和维护。
第一条,使用有含义的词,不要使用胡乱拼凑的东西:
var interestRate = .75;
好于
var iRt = .75;
你也可以使用暗示数据类型的内容作为名字的一部分,如使用类似下例的一些东西:
var strName = "Shelley";
这种类型的命名惯例叫做匈牙利命名法(Hungarian notation),它在Windows开发中非常流行。因此,你很可能经常会在为IE创建的老JScript应用中看到这种用法,但在较现代的JS开发中这种用法就相对少些。
对于项目的集合,名称应当使用复数形式:
var customerNames = new Array();
通常,对象名首字母大写:
var firstName = String("Shelley");
而函数和变量是以小写字母开始的:
Function validateName(firstName,lastName) ...
很多时候,变量名和函数名是由一个或多个词连接在一起而成的唯一标识符,这种连接方式遵循在别的语言中普遍使用的格式,也就是常说的骆驼命名法(CamelCase):
validateName
firstName
这种方法使得变量更具可读性,虽然在变量名的“词”和“词”之间使用横线或下划线也能达到这种效果:
validate-name
first_name
较新的JavaScript库毫无例外地都使用骆驼命名法。
骆驼命名法(CamelCase)一词来自于Perl语言中普遍使用的大小写混合的格式,而Larry Wall等人所著的畅销书
《Programming Perl》(也是O’Reilly出版的)的封面图片正是一匹骆驼。在Wikipedia上有一篇关于该命名法和其他命名法的有趣、动态的文章,地址为:http:// en.wikipedia. org/wiki/ CamelCase。骆驼命名法还有一个变体叫做StudlyCaps,有意思的是它也可以在Wikipedia 中找到,网址为http://en. wikipedia.org/wiki/Studlycaps。
虽然变量名的第一个字符可以是$符、数字或下划线,但最好还是用字母。毫无必要地在变量命中使用一些意想不到的字符,这会使代码难以阅读和理解,尤其是对于那些JavaScript开发新手。
此外,如果你看过一些较新的JavaScript库和例子的话,你会注意到在变量命名方面有一些新的惯例。Prototype JavaScript库在这方面的影响很强,因此,我想建立一个新的命名惯例,叫做“Prototype效果(Prototype effect)”。
2.1.2 Prototype效果和较新的命名惯例
有很多新的或相对较新的命名惯例引入到JavaScript中,它们的出发点不再是让语言更具可读性,而是让JavaScript看起来和用起来都更像是Java、Python或Ruby之类的编程语言。
例如,JavaScript有一些与面向对象类似的能力,其中包括为对象创建私有成员的能力。这就是有一些只能在此对象的函数内部访问的属性或方法,而不能直接由应用程序使用对象访问。
在JavaScript中,没有任何预设的东西能标明对象是私有的而不是公开的。然而,越来越多的JavaScript开发人员都遵循了Java和Python的命名惯例,使用下划线(_)来标明私有变量:
var _break = new Object();
var _continue = new Object();
Prototype库还引入了使用$来表示快捷方法,用这种方法不需要写出细节也能访问对象的引用:
$();
$A();
类对象以大写字母开始,函数和变量以小写字母开始,所有的都使用我们前面讨论过的骆驼命名法。缩写也按照这种命名法而重新定义了格式(比如XmlName,而不是XMLName),而例外的只有常量(那些被视作不会改变的静态值的变量),它们通常全部使用大写字母(比如MONTH,而不是month或Month)。
对于函数的名称,应当使用动词;而变量则使用名词:
var currentMonth;
function returnCurrentMonth...
在那些要用于发行的独立JavaScript语句块(通常指的是JavaScript库或包)中,函数和全局变量的标识符应当包含对包的引用,从而避免名称冲突(名字之间相互冲突):
dojo_someValue;
otherlibrary_someValue;
迭代变量(用在for循环和其他循环机制)应当简单些,可以由i、j、k等组成,使用时按照字母顺序选择(这是一个从很久以前延续而来的传统,那时的FORTRAN之类的编程语言要求所有的整数变量名要从i、j等开始)。
在更新一些的JavaScript开发中,还建立了一些别的惯例,它们大多数都在一个文档中有相当详细的说明。这个文档就是Dojo组织的《JavaScript Programming Conventions》,它现在还在编写之中,其下载地址为http:// dojotoolkit.org/ js_style_guide.html。
对于本节中所讲述的多数惯例,我都同意并拥护。唯一例外的惯例是在Prototype 库中对$符的使用。它给JavaScript增加了一个让人困惑的非必要的元素,这样会使开发新手难以理解,无所适从。
无论个人偏好怎样,我所介绍的这些命名惯例中没有什么是强制性的或神秘的东西,除非一些JavaScript引擎所强制的要求。它们是一种便利约定。
我们将在第14章中讲述Prototype库,但是现在,随着你开始研究JavaScript,当你看到在网站上的样例代码中使用了这些命名规范的时候,你会知道我没有把大量的JavaScript功能遗漏在本书之外。
2.2 作用域
变量的下一个关键特征就是它的作用域:无论它是特定函数中的一个局部范围,还是整个JavaScript应用程序中的一个全局范围。局部作用域的变量在一个函数内部中定义、初始化和使用;当函数终止时,变量也就不存在了。而全局变量则相反,在Web网页所包含的任何JavaScript中的任何地方都可以访问全局变量,无论JS是直接嵌入到页面中的还是从JavaScript库中引入的。
在第1章中,我提到过:具体定义一个变量时,在语法上并没有特殊的要求。变量可以在同一行代码中创建和实例化,它看上去并不需要与典型的赋值语句有什么不同:
num_value = 3.5;
这是更好的一种方式:
var num_value = 3.5;
这两者之间的不同在于关键字var的使用。
虽然不是必须的,还是强烈建议:在定义一个变量时,显式地使用关键字var;对于局部变量这么做,有助于防止同名的局部变量和全局变量之间的冲突。如果在函数中显式地定义了一个变量,那么它的作用域就被限制在这个函数之内,该函数中任何对于此变量的引用,开发者和JavaScript引擎都把它理解成那个局部变量。随着更大、更复杂的JS库的不断普及,往往会发生这样的情况:你认为所使用的是个局部变量,而实际上它却是个全局变量,因此,应使用var来防止这种情况引起的意料之外的副作用。
为了形象地说明这种副作用和显式声明变量的重要性,例2-1展示了一个带有几个独立JavaScript语句块的Web页面,其中每个JavaScript语句块都访问同一个变量和消息。此页面引用了两个外部JavaScript文件,这两个文件都设置同一个变量:一个在函数外面全局地使用它;另一个在函数内部局部地使用它。在定义变量时,本例没有使用关键字var来显式定义。
例2-1:全局变量与未显式定义的局部变量的危险
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html>
<head>
<title>Scope</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<script type="text/javascript" src="global.js" >
</script>
<script type="text/javascript" src="global2.js">
</script>
<script type="text/javascript">
message = "I'm in the page";
function testScope() {
message += " called in testScope()";
alert(message);
}
</script>
</head>
<body onload="testScope();global2Print();globalPrint()">
<script type="text/javascript">
message += " embedded in page";
document.writeln(message);
</script>
</body>
</html>
在最初载入页面时,首先会处理head元素中的JS,变量message被赋予一个字符串类型的值“I’m in the page”。然后,在页面载入过程结束前,函数testScope把这一变量输出到一个对话窗口中。当然,在打印变量message之前,函数也把字符串
“called in testScope( )”连接在原来的字符串的后面。在Web页面中的后面,另一个JavaScript语句块也访问了变量message,并且也把自己的文本“embedded in page”连接在原来的message字符串的后面。然后把修改后的字符串打印输出到Web页面上。
此页面还导入了两个JavaScript外部文件。第一个是global.js,它把自己的字符串“globally in globalPrint”连接到变量message的后面。此源文件中也有一个函数globalPrint,函数会打开一个对话框并显示变量message:
if(typeof(message) !='undefined') message += " globally in globalPrint";
function globalPrint() {
alert(message);
}
本应用程序的最后一个部件是JavaScript源文件global2.js,它有一个函数global2Print。这一函数也修改message,这一次在message后面拼接的字符串是“also accessed in global2Print”:
function global2Print() {
message += " also accessed in global2Print";
alert(message);
}
在第一次打开Web页面的时候,首先在第一个语句块中对变量message进行赋值,然后在页面加载时,在第2个语句块中被修改并打印在页面上。此时,变量message的值为:
I'm in the page embedded in the page
这里还没有什么太令人惊讶的东西。通过body标记中的onload事件,testScope函数在页面加载完成后立即调用。此函数又修改了message字符串,并在弹出窗口中显示为:
I'm in the page embedded in the page called in testScope()
在点击OK按钮关闭窗口后,就会调用在onload事件中所列的下一个函数,这就是global2.js中的global2Print。这一函数给message加上了自己的字符串后,将其显示在对话框中,结果为:
I'm in a page embedded in page called in testScope() also accessed in global2Print
在经过两个文件的多个JS语句块的修改之后,变量message已经变得很长了,但这还没有结束。在onload事件中最后调用的函数是global.js中的globalPrint。此文件中的JavaScript还要修改字符串message,globalPrint把它通过alert函数输出出来。输出的文本为:
I'm in a page embedded in a page called in testScope() also accessed in global2Print
现在,变量message在这时发生了什么,这开始变得让人迷惑。你可以看到,在调用global2.js中的函数和调用global.js中的函数之间,message没有变化,但是两个文件中的JavaScript都对字符串进行了修改。
结果的差异是:在打印前,global2.js在函数中直接修改字符串,它被当作一个局部变量,而global.js在函数之外修改字符串,它被当作是个全局变量。在加载JavaScript源文件时,在加载页面的同时处理JavaScript,一旦处理了Web页面的head元素中的JavaScript之后,变量message在全局作用域中所设置的值便被丢掉了。这一脚本语句块把message当作一个局部变量来设置,而不是修改,这个变量message的内容覆盖了在同名的全局变量中设置的值。
在几次更多的定向和重定向后,以及在对全局访问和局部访问未作任何区分的情况下对变量进行了混合访问之后,无论怎样仔细地跟踪这一变量,你会对某些点的结果感到惊讶。这是肯定的。
对于在一个代码块(由花括号分隔而成)中声明的变量,它的作用域会超出该块,虽然本例中没有演示这一点。它可以被整个函
数或脚本(如果代码块不在一个函数中的话)的代码访问。JavaScript 1.7增加了一种块级的作用域,但是目前浏览器还没有普遍实现这一语言版本。
那么,所有这些的启示是什么呢?实际上有两点启示。
第一点,在使用全局变量的时候,要尤其小心。有些人会说,有了JavaScript创建对象和附加属性、传值作为函数参数的能力,你就不应使用全局变量。然而,全局变量可能用起来非常顺手:它们能保持运行计数或保存定时器,或任何多个函数所需要的值。
尽管如此,如果你使用很大的JavaScript库,无论你怎么仔细,全局变量的名字和你使用的程序库中的变量重名的事也会发生。如果发生这种情况,就会有不可预料的副作用。
另一个避免使用全局变量的理由是:它们增加了JS应用的总体内存负担。JavaScript的内存管理是为我们而进行的,但是我们能改进这一过程。与局部变量不同,在函数结束时,局部变量会被释放,而全局变量一直都保留着,直到Web页面和JS应用程序不再载入浏览器为止。
如果“在使用全局变量时要极其小心”是从本节学到的一个启示,那么第2个是什么呢?它就是:永远显式地使用关键字var来定义一个局部变量。如果你没有这么做,它就会被当作一个全局变量,这种方式纯粹而简单。
JavaScript的最佳实践:在定义任何变量时,无论是全局变量还是局部变量,都要使用关键字var。有句老话叫做“开始了就要坚持下去”。这在学习JavaScript时尤其正确。
JavaScript的小配件
在与变量作用域战斗了漫长而难熬的一天之后,我想花点时间玩玩那些被我称为“小玩意儿”的软件。它们是一些能快速安装,给人感觉简单和容易使用的、有趣的实用工具和玩具。
我有一个Mac笔记本,还有一个Windows笔记本,这两个我都喜欢,虽然我愿意用苹果机。对于我苹果机,我尤其喜欢的项目之一就是能安装到Dashboard上的小配件的数量,小配件的空间可能会覆盖你的内容。这就是“小玩意”的很棒的亮点,包括JavaScript的“小玩意”。
-续
现在安装在我的Mac机上的“小玩意”有:HTML Test 2,它可以用于测试JavaScript;ExecScript 2.0,它能在窗口中运行JS;Regex,一个正则表达式的必备工具;还有Rob Rohan,一个JavaScript shell。
在小配件下载网站(http://www.apple.com/downloads/dashboard/developer/)的Developer分类中可以找到很多JavaScript小配件。每个都只要点一下按钮就能安装,它们只需要一点资源就能使用。
2.3 简单类型
JavaScript是一种裁剪过的语言,它的功能恰好能完成任务,不多也不少。然而,正如我说过的,JavaScript在某些方面是一种令人迷惑的语言。
例如,只有3种简单的数据类型:字符串型、数字型和布尔型。每一种都是通过所包含的直接量来明确区分的,它们分别包含字符串、数字和布尔值。然而,我们知道还有一些内置的对象也叫数字、字符串和布尔型。它们看起来是相同,但实际上绝非这样:前3个是基本值的分类,而后3个是由其本身的类型所构成的复杂构造:对象。
我们不会把类型和对象混在一起来讲,在后面3小节中,我们会讲解每一种简单数据类型、如何创建它们,以及一种类型的值如何转换为其他类型。在第4章中,我们会介绍这3种内置的JS对象,还有其他的内置JS对象及其方法和属性。
2.3.1 字符串型
例2-1展示过字符串变量。因为JavaScript是一种宽松类型的语言,除了在对其初始化时赋给它的直接量和它使用的环境之外,它与数值或布尔值的变量没有任何区别。
字符串直接量是一个使用单引号或双引号分隔的多字符的序列:
"This is a string"
'But this is also a string'
除了结束的引号必须与开始的引号相同之外,没有什么规则要求你使用哪种类型的引号。任何字符的变种都可以包含在字符串中:
"This is 1 string."
"This is--another string."
在JavaScript中,并非所有的字符都会受到同样的对待。字符串中还可以包含转义序列(escape sequence),比如用\n代表换行符。
转义序列是一组字符,某些字符只有编码成这组字符之后才能包含在字符串中。在下面的这段代码中,赋给变量的字符串直接量中包含了一个换行符的转义序列。当在对话窗口中使用这一字符串时,转义序列\n被准确地解释后,显示出一个新行:
var string_value = "This is the first line\nThis is the second line";
其结果就是:
This is the first line
This is the second line
如果你需要在字符串中使用引号,可以交替使用单引号和双引号这两种不同的引号:
var string_value = "This is a 'string' with a quote."
或:
var string_value = 'This is a "string" with a quote.'
你还可以使用反斜线(\)来表示字符串中的引号只是一个字符,而并不是代表字符串结束的终止符号。
var string_value = "This is a \"string\" with a quote."
要在字符串中使用反斜线,应使用两个反斜线:
var string_value = "This is a \\string\\ with a backslash."
本行代码的结果是一个字符串,其中“string”一词前后各有一个反斜线(即This is a \string\ with a backslash.)。
还有一个JavaScript函数escape,它可以对整个字符串进行编码,把ASCII码转换为URL编码(ISO Latin-1 [ISO 8859-1])。它可以用于HTML处理之中。如果你要为Web应用处理数据的话,这个函数尤其重要。例2-2展示了escape函数如何处理两个不同的字符串。
例2-2:使用escape函数对字符串进行转义
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html>
<head>
<title>Convert Object to String</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
</head>
<body>
<script type="text/javascript">
//<![CDATA[
var sOne = escape("http://oreilly.com");
document.writeln("<p>" + sOne + "</p>");
var sTwo = escape("http://burningbird.net/index.php?pagename=$1&page=$2");
document.writeln("<p>" + sTwo + "</p>");
//]]>
</script>
</body>
</html>
程序的结果是下面两个转义后的字符串:
http%3A//oreilly.com
http%3A//burningbird%2Cnet/index.php%3Fpagename%3D%241%26page%3D%242
被转义的字符是空格、冒号、斜线和其他在HTML环境中有意义的字符。如想要得到原来的字符串,可以对修改后的字符串使用unescape函数。
虽然很方便,escape还是有些问题,这就是escape不能处理非ASCII码字符。还有另外两个函数:encodeURI和decodeURI,它们所能编码的字符集不仅仅限于ASCII。下面的表2-4种列出的编码,是从《Mozilla Core JavaScript 1.5 Reference》中复制来的。
表2-4:URI编码的字符分类
|
类型 |
包括 |
|
保留字符 |
; , / ? : @ & = + $ |
|
不转义的字符 |
Alphabetic, decimal digits, - _ . ! ~ * ‘ ( ) |
|
Score |
# |
可以把例2-1中的JavaScript语句块的主体替换成下面这样:
var sURL = "http://oreilly.com/this_is_a_value&some-value='some value'";
sURL = encodeURI(sURL);
document.writeln("<p>" + sURL + "</p>");
页面中打印出来的字符串是:
http://oreilly.com/this_is_a_value&some-value='some%20value'
函数decodeURI可以用来检索出原来的、未转义的字符串。
还有另外两个用于URI编码的函数:encodeURIComponent和decodeURIComponent,它们也能编码&、+和=,所以可以用于Ajax操作。我们将在第13章中讲述它们。
我们还可以把Unicode字符包含在字符串中,只要使用\u加上该字符的4位16进制值就可以了。例如,下面的例子将输出简体中文的“爱”字:
document.writeln("\u7231");
显示出什么,在一定程度上与浏览器相关;当然,现在大多数较常用的浏览器都有充分的Unicode支持。
想要对Unicode有更多的了解,请访问http://www. unicode.org/的相关文章。
空字符串是一种特殊情形;它通常用来在定义字符串变量时对其进行初始化。下面就是空字符串的例子:
var string_value = '';
var string_value = "";
对于JavaScript引擎来说,你使用哪种引号都没有不同。更重要的是一致地使用一种引号。
这些例子展示了如何显式地创建一个字符串变量以及含有特殊字符的字符串直接量的变种。特定变量中的值也可以从其他的数据类型转换而来,这依赖于环境。
如果把数值型变量或布尔型变量传给一个期望收到字符串变量的函数,该值就会先被转换成一个字符串,然后处理这个值。
var num_value = 35.00;
alert(num_value);
另外,当变量相加时,根据当时的环境,非字符串的值也会被转换为字符串。当非字符串的值加(字符串连接)到一个字符串上,然后显示在对话窗口时,你会看到这种操作:
var num_value = 35.00;
var string_value = "This is a number:" + num_value;
你还可以使用string函数(在ECMAScript中是toString)来显式地把一个变量转换为字符串。如果将被转换的值是布尔型的,结果字符串便是布尔值的文字表达:true被转换为“true”(真),false被转换为“false”(假)。对于数字,结果字符串同样是这个数字的文字表达,比如-123.06被转换为“-123.06”,这也会根据数字的位数和精确度(小数点的位数)的不同而不同。对于NaN(非数字/ Not a Number,将在后面讨论),返回值是“NaN”。
表2-5列出了对不同数据类型使用String函数的结果。
在表2-5的最后一条中,讨论了字符串转换如何处理一个对象。在ECMAScript规范中,在类型转换之前,转换例程首先调用toPrimitive函数。此函数会调用对象的DefaultValue方法,如果有的话,就会返回结果,例如,在有些浏览器中对String对象自己使用toString会返回如下一个值:
[object Window]
表2-5:toString转换表
|
输入 |
结果 |
|
Undefined |
"undefined" |
|
Null |
"null" |
|
布尔型 |
如果是真,返回"true";如果是假,返回"false" |
|
数字型 |
见上文 |
|
字符串型 |
不转换 |
|
对象 |
此对象的缺省表示的字符串表示 |
如果你为了调试的目的想要探知一个变量所保存的值的话,这会很有用。
2.3.2 布尔型
布尔型有两个值:true和false。它们不必用引号引起来;也就是说,“false”和false不一样。
Boolean函数(ECMAScript中为ToBoolean)可以将其他值转换位布尔值true或false,详见表2-6。
表2-6:ToBoolean转换表
|
输入 |
结果 |
|
Undefined |
false |
|
Null |
false |
|
布尔型 |
原值 |
|
数字型 |
当数字为0或NaN时,为false;否则为true |
|
字符串型 |
如果字符串为空,则为false;否则为true |
|
对象 |
true |
2.3.3 数值型
JavaScript中的数字是浮点数,但是它们可以有小数部分,也可以没有,如果没有小数点或小数部分,它就会被当作是10进制的整数,范围是-253到253。下面都是有效的整数:
-1000
0
2534
浮点表示中有小数点,右边是小数部分。它还可以被表示成一个指数,使用一个上标或指数计数法。下面的这些都是有效的浮点数:
0.3555
144.006
-2.3
442
19.5e-2 ( 等价于19.5-2)
虽然支持更大的数字,但是某些函数只能处理范围在-2e31到2e31(即-2 147 483 648 到2 147 483 648)范围内的数字;因此,你应该把所使用的数字限制在这一范围内。
有两个特别的数字:正负无穷大。在JavaScript中,它们用Infinity和-Infinity来表示。在JS应用发生了数学溢出的时候,会返回一个正无穷大。
除了十进制的表示法,也可以使用八进制和十六进制表示法。不过,八进制较新并且在较老的浏览器中可能会与十六进制相混淆。十六进制的数字以0x开头:
-0xCCFF
八进制的数字以0开头,没有x:
0526
你可以把字符串或布尔值转换为数字;有两个转换函数:parseInt和parseFloat,你可以根据想要返回的数值的类型不同从中选择使用。
无论字符串的格式是个整数还是个浮点数,parseInt函数都返回字符串中的数字的整数部分。parseFloat函数返回字符串中第一个非数值字母之前的数字的浮点值。在例2-3中,3个包含了数字的字符串被传给了parseInt或parseFloat,其返回值被输出到页面上。
例2-3:使用不同的全局函数把字符串转换为数字
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/
xhtml1/DTD/xhtml1-transitional.dtd">
<html>
<head>
<title>Convert String to Number</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
</head>
<body>
<p>
<script type="text/javascript">
//<![CDATA[
var sNum = "1.23e-2";
document.writeln(parseFloat(sNum));
var fValue = parseFloat("1.45inch");
document.writeln("<p>" + fValue + "</p>");
var iValue = parseInt("33.00");
document.writeln("<p>" + iValue + "</p>");
//]]>
</script>
</p>
</body>
</html>
使用Firefox作为浏览器,该页面打印出的值为:
0.0123
1.45
33
注意,对于第一个数值,打印出的数字是十进制的,而不是原来字符串中的指数计数法。还要注意到,parseInt函数截掉了数字的小数部分。
parseInt函数还能将八进制或十六进制的数字转换回十进制的表示形式。此函数还有第二个参数,以多少为基数,其缺省为10即十进制(以10为基数)。如果指定了其他的基数,范围在2至36之间,字符串都相应地转换。你可以把例2-3中的文档输出部分替换成下面的JavaScript代码:
var iValue = parseInt("0266",8);
document.writeln("<p>" + iValue + "</p>");
var iValue = parseInt("0x5F",16);
document.writeln("<p>" + iValue + "</p>");
这些八进制和十六进制的值在页面上被打印为:
182
95
作为对parseInt和parseFloat的补充,Number函数也可以转换数字。转换之后返回的类型和字符串的内容有关:浮点数的字符串返回的是浮点数,整数的字符串返回整数。表2-7中列出了每种数据类型转换为数字型的情况。
除了将字符串转换位数字之外,还可以通过函数IsFinite来测试一个变量的值是否是无穷大。如果值是无穷大(infinity)或NaN,函数返回false;否则,返回true。
还有其他一些用于数字的函数,但它们都与Number对象有关,所以我们将在第4 章中讲述。现在,我们再来看看基本数据类型中的两个特殊的JavaScript类型:null和undefined。
表2-7:从其他数据类型到数字型的转换
|
输入 |
结果 |
|
Undefined |
NaN (非数字) |
|
Null |
0 |
|
布尔值 |
如果是true,则结果为1;否则为0 |
|
数字 |
原来的值 |
|
字符串 |
见上文 |
|
对象 |
对象的缺省表达的数字表达 |
2.3.4 null和undefined
在JavaScript中,直接量、简单数据类型和对象之间的区分很模糊,现在我们来看
看两个代表不存在或不完全存在的值:null(空)和undefined(未定义)。
一个null(空)变量,它没有被定义,也没有被赋过值。下面是一个空变量的例子:
alert(sValue); // results in JavaScript error because sValue is not declared first
在此例中,既没有使用关键字var声明过变量sValue,它也不是函数的一个参数。
如果声明过这个变量,但没有初始化过这个变量的话,它就被视作是undefined (未定义)。
var sValue;
alert(sValue); // no error, and a window with the word 'undefined' is opened
如果既声明过变量又给过它初始值,那么这个变量就不会是null也不会是undefined。
var sValue = "";
在使用多个JS库和相当复杂的代码时,如果变量没有被赋值就试图在表达式中使用它时,往往会有反作用,通常是个JavaScript错误。如果你不能确定变量的状态,一种测试变量的方法就是:将该变量用于一个条件测试,例如这样:
if (sValue) ... // if not null and initialized, expression is true; otherwise false
我们将在下一章中讲述条件语句,但对于仅由变量sValue组成的表达式来说,如果已经声明并初始化过sValue,表达式的结果就为真;否则,表达式的结果为假:
if (sValue) // not true, as variable has not been declared, and is therefore null
var sValue;
if (sValue) // variable is not null, but it's still not
true, as variable has not been defined (initialized with a value)
var sValue = 1;
if (sValue) // true now, as variable has been set, which automatically declares it
你可以使用关键字null来专门测试一个值是否为空:
if (sValue == null)
在JavaScript中,即便是声明了变量,它还是undefined,直到它被初始化为止。它与null不同,把null值用作函数的参数会引发一个错误,而把一个undefined变量用作函数的参数通常不会引发错误:
alert(sValue); // JS error results, "Error: sValue is not defined"
var sValue; // no JS error and the window reads, "undefined" which is the value of
the object
变量可以未经声明就初始化,这种情况下,它不是null也不是undefined。然而,这时,它被视作是一个全局变量,正如前面讨论过的,没有使用关键字var来声明变量,往往比使用var来声明更容易导致问题。
还有一个与变量类型有关的唯一值,这个第三个值与存在性没有什么关系,它就是NaN,也就是Not A Number(不是一个数字)。如果一个字符串型的或布尔型的变量不能被强制转换为一个数字,那么它就被认为是NaN,并受到相应的对待:
var nValue = 1.0;
if (nValue == 'one' ) // false, the second operand is NaN
你可以使用isNaN函数来专门测试一个变量是否是NaN:
if (isNaN(sValue)) // if string cannot be implicitly converted into number, return true
从本质上讲,null值是NaN,因此它是undefined。
在O’Reilly公司2006年的ETech会议上,作家、同时也是受人尊敬的技术专家Simon Willison做过一次很棒的演讲,标题为“A
(Re)-Introduction to JavaScript”。你可以在他的网站上看他的幻灯片,网址为http://simon.incutio.com/ slides/2006/ etech/ javascript /js-tutorial.001.html。整个演讲都非常值得一读,但我最喜欢的是下面这行:
0、" "、NaN、null和undefined都是假的。剩下的东西都是真的。
换句话说,零、null、NaN和空字符串天生就是假;而其他的天生
都是真。
对于大多数的部分,JavaScript开发者都在以这样一种方式创建代码:我们知道应当事先定义变量并给它赋值。在大多数情况下,我们没有显式地检查是否一个变量被定义过,如果是,它是否也赋过值。
然而,在使用巨大而复杂的JS库,以及可以和Web服务的响应一起协作的应用程序时,测试在我们控制之外定义和(或)赋值的变量就变得尤为重要,更别说要知道当应用访问null和undefined的变量时,这些变量会有什么行为了。
2.4 常量:有名称但不改变
很多时候,我们都希望对一个值只定义一次,此后便把它作为一个只读的值来使用。关键字const被用来创建一个JavaScript常量:
const CURRENT_MONTH = 3.5;
常量可以是任何值,因为不能在之后对它进行赋值和重赋值,因此,在定义它时,就应使用它的常量值来对其进行初始化。
和变量一样,JavaScript常量也有全局的和局部的之分。我使用全局常量,根本原因是它们包含了我想让JavaScript语句块访问(但不修改)的值。
2.5 习题
1. 在下列标识符中,哪些是有效的,哪些不是有效的,为什么?
$someVariable
_someVariable
1Variable
some_variable
somèvariable
function
.someVariable
some*variable
2. 使用2.1节列出的惯例,转换下列标识符:
var some_month;
function theMonth // function to return current month
current-month // a constant
var summer_month; // an array of summer months
MyLibrary-afunction // a function from a JavaScript package
3. 下列字符串直接量是否有效?如果无效,应如何修改?
var someString = 'Who once said, "Only two things are infinite, the universe and
human stupidity, and I'm not sure about the former."'
1 4. 对于数字432.54,怎样用JavaScript代码返回它的整数部分,如何得到它的十六进制和八进制的转换格式?
2 5. 在一个可用于其他应用程序的库中创建一个JavaScript函数。函数有一个传入参数someMonth。你如何确定传入的参数是否为null或undefined?
答案见附录。
到目前为止,本书中的例子大多数都是进行一些简单的工作,诸如定义变量并给它赋值;将值打印到页面中或者弹出窗口中;通过加法、乘法或其他形式来修改变量等等。这些都要使用JavaScript语句和运算符。
在JavaScript中,有几种不同的语句类型:赋值、函数调用、条件语句和循环语句。每种都相当直观,用法简单,并能迅速掌握。这真是小菜一碟。就像很多编程语言一样,在JavaScript中,语句很容易学;但真正繁杂的是:把它们一行行地排列起来,从而使它们能做一些有用的事情。
本章将详细地讲述语句和运算符,它们共同的特点是什么,以及它们的区别是什么。
3.1 JavaScript语句的格式
虽然不是所有语句都需要显式地给出一个终止符,但JavaScript语句通常以分号结尾。如果JavaScript引擎确定一个语句是完整的(无论它是什么类型的语句),而这一行的结尾有换行符,那么就可以省略掉分号:
var bValue = true
var sValue = "this is also true"
如果在同一行中有多条语句,那么每个语句就必须使用分号来结束:
var bValue = true; var sValue = "this is also true";
然而,不显式地结束每个JavaScript语句是一个坏习惯,这会导致意料不到的后果。因此,在每个JavaScript语句的结尾使用分号,这是一条JavaScript最佳实践。
JavaScript最佳实践:显式地以分号结束一个JavaScript语句,无论这是否必要。
在JS中,使用空白对于代码没有什么影响。例如,下面的两行代码在解释时是完
全一样的:
var firstName = 'Shelley';
var firstName = 'Shelley';
除了在引号中分隔单词或为了结束语句之外,额外的空白(如Tab键、空格或换行)都无所谓。在下面的代码中,变量的赋值完全成功,虽然语句中间有一个换行符将其分为了两行。
var firstName
= 'Shelley';
alert(firstName);
在此例中,引擎没有把换行符解释为语句的终止符,因为到换行为止并不是一个完整的语句。JavaScript应用会继续处理它发现的东西,直到遇到一个分号或发现语句完整了为止。在这个赋值语句中,只有在得到了语句的右边的表达式之后,才达到了这个状态。
决定是否应将一个换行符解释为语句的终止符,这完全是JavaScript的宽容本性的一部分:JavaScript所努力的方面是成功地执行代码,因而所有有助于这一目的的任何事情它都会做。当然,除非这样做会引入混淆。在下面的代码中:
var firstName =
var lastName = 'Powers';
JavaScript引擎会返回一个错误,因为第二行不会被视为赋值语句的右半部分。
再回到对空白的讨论。在贯穿本书的例子中都使用了缩进,为的是使代码更具可读性。但是并没有编程方面的原因要求我们一定要使用Tab符或空格对行进行缩进。同样,对于诸如赋值运算符(=)或算术运算符(比如+)之类的运算符前后的空白,也不是因为编程方面的要求。空白不是必须的。空白、注释和有意义的标识符都是为了让代码易于维护。
JavaScript“压缩”
增加空白的原因是为了可读性、关键语言元素的分割、行结束等等。但是如果删除掉这些空白会怎样呢?JavaScript压缩程序会去掉JavaScript应用程序中的所有与代码无关的空白。这类工具背后的思想是:你在JavaScript中放进越多的空白,它下载得就越慢,所消耗的客户端资源就越多。很多工具和网站都可以对JS代码进行压缩,比如Packer,网址为http://dean.edwards.name/packer/(如图3-1所示)。Radok公司还在网站上列出了很多其他的Web工具,网址为http://www.radok.com/javascript-compression.html。
这些是否必要呢?对于小脚本,没必要,当然没必要。对于较大的JavaScript文件
-续
呢?很难说,即便是最复杂的JS库也不过几百行。通常,我们不需要压缩,因为大多JS库都很小;然而一些较新的Ajax库则可能会非常大。
然而,今天的Web页面常常嵌入了200KB的图片,并连接了很多网站上的资源素材。JS库的大小还会像过去那样有很大影响吗?答案还是:这与网页有关,并且也与你所了解到的客户的期待和环境有关。
还有一些不使用压缩的理由。如果代码中有错误,压缩就会使代码难以调试。代码也变得不具可读性,它抑制了共享,而共享是脚本社区的一个标志。
当然,有时你想限制共享。压缩的真正本质(混淆)可能是使用压缩程序的原因之一。Packer不仅是压缩代码,它还对代码进行了混淆,如图3-1所示,这样就使代码难以(甚至是不可能)复制。有一些加密和混淆工具可以使JS完全不可读,然而它们大多是商业化的产品(Packer不是)。
图3-1:Packer,一种JavaScript压缩服务
3.2 简单语句
有些JavaScript语句会超过一行,比如那些循环语句,它们有开始和结束。而其他语句都是一条语句一行。这些简单语句包括赋值语句。
3.2.1 赋值语句
最常见的语句是赋值语句。它是一个表达式,由左边的变量、赋值运算符(=)和右边的被赋给的值组成。右边的表达式可以是一个直接量值:
nValue = 35.00;
也可以是一个变量和直接量再加上运算符的组合:
nValue = nValue + 35.00;
它还可以是一个函数调用:
nValue = someFunction();
一行中可以包含一个以上的赋值语句。例如,下面的代码在一行中将空串赋给了多个变量:
var firstName = lastName = middleName = "";
在赋值语句之后,第二种常见的语句类型是计算表达式,它会包含某些数学运算符,下面我们就讨论算术语句。
3.2.2 算术语句
在上一节中,第二个例子展示了一个二元算术表达式:两个运算数,中间有个算术运算符,它得出一个新结果。在与赋值运算结合使用时,此结果就会被赋给左边的变量:
nValue = vValue + 35.00;
更复杂的例子会使用任意数量的算术运算符,并组合上任意的直接量和变量:
nValue = nValue + 30.00 / 2 - nValue2 * 3;
表达式中所使用的运算符来自下面这组二元运算符:
|
+ |
加 |
|
- |
减 |
|
* |
乘 |
|
/ |
除 |
|
% |
取余 |
因为这些运算符需要两个运算数,运算符的前后各有一个运算数,因此它们被认为是二元运算符。在一个语句中可以使用任意数量的运算符,并把结果赋给一个变量:
var bigCalc = varA * 6.0 + 3.45 - varB / .05; 这段代码展示了处理数值的二元运算符。那么,对于字符串值又怎么样呢?在前面的某些例子中,我使用加号(+)把几个字符串连接在一起,这和把两个数值加起来一样:
var newString = "This is an old " + oldString;
当加号(+)用于数值时,它是一个加法运算符。然而,当它用于字符串时,它是一个连接运算符。对于其他的二元运算符,你也可以使用字符串作为其运算数,但是字符串所包含的必须是个数值。在这样的情况下,在表达式计算之前,值会先转换为一个数值。
var newValue = 3.5 * 2.0; // 结果是7
var newValue = 3.5 * “2.0”; // 结果仍然是7
var newValue = "3.5" * "2.0"; // 还是7
而反过来(知道这一区别非常重要),如果把一个数值型直接量或变量和一个字符串加在一起的话,数字的值会转换为字符串。
在下例中,你可能会认为得到的结果应该是5.5,但实际上,结果是个字符串:“3.52.0”var newValue = 3.5 + "2.0"; // 结果是一个字符串, "3.52.0"
这一点可能会经常让你犯错。在多种类型混合在一起,又只进行隐式地转换的时候,要小心再小心;任意一个值的一次简单的意外就会导致不可思议的结果。如果你认为一个变量被JavaScript引擎当作是字符串,较好的办法是使用parseInt、parseFloat和Number函数来显式地转换值:
var aVar = parseFloat(bVar) + 2.0;
3.2.3 一元运算符
除了上面讲的二元算术运算符之外,还有三个一元运算符。它们与前一批运算符的区别是,它们只有一个运算数:
++ 值加一--值减一
-负值运算符下面是一些一元运算符的例子:
someValue = 34;
var iValue = -someValue;
iValue++;
document.writeln(iValue);
在第二行中,通过使用一元运算符取负,数字转换为一个负值。使用++后,这个值被加1,++是
iValue=iValue + 1;
的快捷版本。最终的结果是-33。
运算符++和--还有另外一个有趣的方面。在一个表达式中,如果把运算符放在变量前面,就会先调整数值,然后再进行赋值。然而,如果把运算符放在变量后面,就会先把变量原来的值赋值给结果变量,然后再调整值:
var iValue = 3.0;var iValue2 = ++iValue; //iValue2 被设置为4.0,iValue 现在的值是4.0,var iValue3 = iValue++; //iValue3 被设置为4.0,iValue 现在的值是5.0,var iValue4 = iValue; // iValue4 和iValue5的值均为5.0
3.2.4 运算符的优先级
在JavaScript中,运算符是有优先级的。在语句中,当所有的运算符的优先级都相同时,表达式自左向右进行计算。如果语句中所使用的运算符的类型不同,优先级也不同,规则就是先计算高优先级的运算符,然后自左向右地计算表达式的其余部分。
让我们来看一下下面的代码:
newValue = nValue + 30.00 / 2 - nValue2 * 3;
如果nValue的值是3,而nValue2的值是6,那么结果就是0。
详细地说,就是首先计算30.00除以2,因为它的优先级比加法高,其结果为15。乘法运算符的优先级与除法运算符一样,但是它发生在除法之后。因为在优先级相同的情况下表达式是自左向右计算的,所以,先计算除法,之后才是计算乘法。然后,变量nValue2的值乘以3,其结果就是18。此时,表达式只是由加法和减法组成(它们优先级一样),因此自左向右地对它们进行计算:
newValue = nValue + 15 - 18;
赋值运算符的优先级最低,所以只有在算术表达式计算完成之后,才将结果赋给变量newValue。
为了控制优先级的影响,使用括号将你想要先计算的表达式括起来。回到刚才的例子,使用括号可以导致结果又很大不同:
newValue = ((nValue + 30.00) / (2 - nValue2)) * 3;
现在,加法和减法先执行,它们先于除法和乘法。此表达式的结果是-24.75。
你在基础数学课上就已经知道这些了。然而,我们不妨再次确认一下:尽管是在JavaScript中,规则还是一样的。
在JavaScript中,与其他语言不同的是,除法运算的结果是个浮点数,而不是被截取之后的一个整数。下面这行代码的结果是1.5,而不是近似值1:
iValue = 3 / 2;
到目前为止的例子中,我们已经列出了在使用二元运算符时的全部表达式。对于这些表达式,有一个快捷的表达方法。在下一节中,我们就来介绍一下这个方法。
3.2.5 方便的快捷方式:带运算符的赋值
如果同一个变量同时出现在赋值运算符的两边的话,赋值和算术运算可以组合成一个简单的语句。例如,下面的代码:
nValue = nValue + 30;
简化后的语句为:
nValue += 3.0;
所有的二元运算符都可以使用这种类型的快捷方法,它叫做带运算的赋值:
nValue %= 3;
nValue -= 3;
nValue *= 4;
nvalue += 5;
这种类型的运算也可以用于赋值运算符和4个位运算符的组合。
3.2.6 位运算符
本节将介绍JavaScript的位运算符,并假设你有一些布尔代数的经验。在JavaScript中,这不是一个使用很广的功能,在第一次接触这一语言时,你完全可以略过它。如果你对布尔代数不熟悉,而又想继续学习本节,有一个优秀的布尔代数的参考手册,它是由BBC (英国广播公司)汇编而成的,下载地址为http://www.bbc.co.uk/dna/h2g2/A412642。
位运算符将运算数当作一个由32个0或1组成的序列。在进行运算时,逐位地进行位操作;操作的类型与运算符的类型有关:
&位运算中的与(AND)运算,当且仅当两个运算数的位值都为1时,结果位值才为1。
| 位运算中的或(OR)运算,只要有一个运算数的位值为1,结果位值便为1。
^ 位运算中的异或(XOR)运算,当且仅当两个运算数的位值不同时,结果位值为1。如果两个运算数的位值都为1或都为0,则结果位值为0。否则结果位值便为1。
~ 位运算中的非运算,返回位的反转值(补)。例如,位值为1返回0,位值为0返回1。
这样看起来,除了是一种在程序中创建二进制标志的便利方法之外,位运算在JavaScript中似乎没有太大的用处。二进制标志与变量类似,但是二进制标志使用了更少的内存(降低为1/32)。Mozilla Core JavaScript 1.5 reference提供了一个使用二进制标志的例子,其地址为http://developer.mozilla.org/ en/docs/ Core_JavaScript_ 1.5_Reference:Operators:Bitwise_Operators。在该例中,4个标志使用了下面一个变量表示:
var flags = 0x5;
这个二进制值0101(略去了前面的0)等价于:
flag A: false
flag B: true
flag C: false
flag D: true
而每个位掩码标志表示为:
var flag_A = 0x1;
var flag_B = 0x2;
var flag_C = 0x4;
var flag_D = 0x8;
想要测试在我们的标志变量中是否设置了flag_C,应使用位运算中的与运算:
if (flags & flag_C) {
do stuff
}
例3-1使用了一个二进制标志和位掩码来模拟假想出来的表单提交。在这个例子中,我们假设提交了5个字段,但其中只有3个有值,它们是字段A、C和E。如果字段A和C都填写了,对于这种情况,要在对话框窗口输出一条信息。
例3-1:使用二进制标志和位掩码来创建内存友好的标志
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html>
<head>
<title>Using Binary Flags</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<script type="text/javascript">
//<![CDATA[
var FIELD_A = 0x1; // 00001
var FIELD_B = 0x2; // 00010
var FIELD_C = 0x4; // 00100
var FIELD_D = 0x8; // 01000
var FIELD_E = 0x10; // 10000
// assume fields A, C, and E are filled in
var fieldsSet = FIELD_A | FIELD_C | FIELD_E; // 00001 | 00100 | 10000 => 10101
if ((fieldsSet & FIELD_A) && (fieldsSet & FIELD_C)) {
alert("Fields A and C are set");
}
//]]>
</script>
</head>
</body>
<p>Imagine a form with five fields and a button here...</p>
</html>
这是一种为应用程序节省空间的方法,因为你可以在一个布尔变量所需的空间里处理多个二进制值。然而,这确实也影响了代码的可读性。
例3-1还使用了另一个运算符:逻辑与(表示为&&)。我们将在后面的“3.5 逻辑运算符”一节中详细介绍它。
在Mozilla Core JavaScript 1.5 reference中,还有更多的关于使用位运算符来测试输入的内容;位运算是一项有趣的技术,它表明:对于JavaScript来说,虽然内存管理是在后台进行的,但只要你需要,你就会有窍门和技术来解决内存问题。
还有其他3个位运算符:左移(<<)、带符号的右移(>>)和用0 补足的右移(>>>)。它们会将左运算数的位向右或向左移动,
而右运算数指定了移动的位数(应在0到31之间):
newValue = oldValue >>> 3;
上个例子中还引入了一个不同的语句类型和运算符集合的概念,这就是条件语句,以及关系和相等运算符。我们在接下来的几个小节中将会讲解这些内容。
3.3 条件语句和程序流
在JavaScript中,程序流通常是线性的:按顺序处理每个语句,一条接着一条。可以通过判断对比来改变这种线性执行顺序。你可以将代码放入一个函数,仅基于某些行动或事件才调用这一函数,或者,你也可以进行某种形式的条件测试,仅在测试结果为真的情况下才执行某块代码。
在JavaScript中,修改程序流的一个更普遍的方式是使用条件语句。正如在前面几小节中看到过的那样,典型的条件语句有如下的格式:
if (value) {
statements processed
}
“条件”一词来自于这样一个事实:在执行相关的一组语句之前,必须先要符合某个条件。此例等于说:如果某个值(可以是一个表达式的结果,或者一个变量,或者一个直接量)为真,那么就执行后续的代码;否则,跳转到该语句块的结尾,继续处理紧接着的下一行代码。
关键字if的使用标志着条件测试的开始,括号中的表达式封装了该测试。在下面这段代码中,使用了两个位掩码来测试二进制标志,检查它们是否匹配。如果都匹配了,并且只有在都匹配了的情况下,才会执行条件表达式之后那段包含在花括号中的代码:
if ((fieldsSet & FIELD_A) && (fieldsSet & FIELD_C)) {
alert("Fields A and C are set");
}
在本例中,并非必须使用花括号,因为在条件为真时所要执行的JavaScript代码只有一行。如果需要执行的JS语句多于一行,那么所有的代码就应该包括在一对花括号之中。这通常叫做“JavaScript语句块”或“代码块”,这对花括号能让脚本引擎知道:如果条件为真,则执行本语句块中所包含的所有JavaScript。
因为不知道将来会不会增加额外的代码,所以应使用花括号将在某些控制流事件(比如条件语句)下执行的单个语句括起来,这是一个好习惯。
JavaScript最佳实践:对于那些作为某个控制流事件(比如条件语句)的结果而执行的控制块语句,应使用花括号({,})将其括起来。
为了使JavaScript更具可读性,应当考虑缩进花括号中包含的代码,这是一种很好的形式。如果所包含的代码中还有条件语句,与之相关的语句也应从原来的位置开始再作一层同样的缩进。如果还有条件语句,以此类推。例3-2展示了3个嵌套的条件语句,每个都有一个代码块,每个都进行了缩进。可改变变量的初始值来测试不同的条件表达式。
例3-2:3个嵌套的条件语句,为了更易读进行了缩进
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html>
<head>
<title>Nested Indented Conditional statements</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<script type="text/javascript">
//<![CDATA[
var prefChoice = 1;
var stateChoice = 'OR';
var genderChoice = 'F';
if (prefChoice == 1) {
alert("You've picked option 1. Here is what will happen...");
if (stateChoice == 'OR') {
alert ("You've picked 1 and you're from Oregon.");
if (genderChoice == 'M') {
alert("You've picked 1 and you're from Oregon and you're a man.");
} // innermost block
} // middle block
} // outerblock
//]]>
</script>
</head>
<body>
<p>Imagine a form with five fields and a button here...</p>
</body>
</html>
通常,每块的代码缩进3个空格,花括号与条件语句对齐。关于这一点并没有固定的规则;它也不会影响代码的有效性。
另外,在每块的结束花括号之后,都有备注加以说明。如果代码相当长、相当复杂并且全是嵌套的语句块,就像是例3-2中那样,那么应该在结束花括号处使用一些注释,这样会让代码更易于阅读和维护。
JavaScript最佳实践:在较长或较复杂的脚本块中,在结束花括号处使用注释,使读者能较容易确定是哪个块结束了。
3.3.1 if . . . else
在很多情形中,条件测试之后,会执行一个一条语句或多条语句的代码块,然后
程序的流程从结束处继续执行。然而,不是所有的逻辑都可以只用一个测试表示。即使是在我们讲话用的语言中,比如英语,我们也有if . . . then . . . else这样的概念,用以列出不同的选项:
If the sun is out, we’ll go to the park; otherwise, we’ll go to the movies.
(如果出太阳了,我们就去公园;否则,我们就去电影院。)
在JavaScript中,可以使用关键字else来完成同样的功能:它为程序流程提供了在测试条件结果为假时应执行的另一组语句:
if (expression) {
...
} else {
...
}
在下面这段代码中,如果stateCode的值为“MA”,也就是马萨诸塞州,那么税率就被设为3.5;否则,税率被设为4.5:
if (stateCode == "MA") {
taxPercentage = 3.5;
} else {
taxPercentage = 4.5;
}
无论州代码是不是“MA”,都设置了税率。
然而,并非所有的条件都只是“要么这样要么那样”。在有些情况下,可能不止一个的相关的条件输出,那么你就需要进行一系列的测试:if then . . . else if then . . . else if then . . .等等。在JavaScript中,这可以通过在else子句后面增加条件表达式来实现,如:
if (conditional expression) {
block of code
} else if (other conditional expression) {
block of code
}
这些可以一个接一个地串接起来,直到所有条件都测试完为止。
在例3-3中,变量中保存了在程序中设置的州代码(只是为了测试,一般来说你并不知道变量的值是什么)。要测试3个州代码,如果三个之中有任一个匹配上了,就会设置一个不同的税率。
例3-3:使用多个条件语句测试一个值
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html>
<head>
<title>if...then...else...if</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<script type="text/javascript">
//<![CDATA[
var stateCode = 'MO';
if (stateCode == 'OR') {
taxPercentage = 3.5;
} else if (stateCode == 'CA') {
taxPercentage = 5.0;
} else if (stateCode == 'MO') {
taxPercentage = 1.0;
} else {
taxPercentage = 2.0;
}
alert(taxPercentage);
//]]>
</script>
</head>
<body>
<p>Imagine a form with options to pick state code</p>
</body>
</html>
本程序依次计算每个表达式,直到发现一个为真的表达式为止。此时,执行所包含的语句,然后程序继续执行整个条件语句之后的那一行。如果没有为真的表达式,就会执行else后面的那个没有任何条件的代码块,对税率进行相应的设置。
你可以再增加别的else if语句来测试同一个变量,但是增加太多以后,格式就变得笨拙、难以阅读和低效。一个更好的办法是使用switch语句。
3.3.2 switch条件语句
JavaScript的switch语句用于那些条件表达式有多个可能输出结果的情况。JavaScript引擎处理表达式,并根据其结果,选择执行一个或多个备选项:
switch (expression) {
case firstlabel:
statements;
[break;]
case secondlabel:
statements;
[break;]
...
case lastlabel:
statements;
[break;]
default:
statements;
刚开始时,给了switch语句会返回某个值的一个表达式。然后按从上到下的顺序计算case语句,检查是否能匹配。如果发现了可匹配的情况,便会执行该case语句代码块中所包含的语句。此时,程序流会继续处理接下来的每个case语句,而如果使用了可选的break的话,程序便会转去执行switch语句结束之后的第一行语句。
如果没有一种情况能匹配,JavaScript引擎就会寻找可选的default语句;如果有default语句,则会执行default语句的代码块,然后程序继续执行switch后面的第一行语句。
对于那些两个或更多的case标签都执行相同的一组语句的情形,可以把这些标签列在一起,下面紧跟着那些语句,如下所示:
case labelone:
case labeltwo:
case labelthree:
statements;
break;
采用这种方法之后,如果labelone、labeltwo和labelthree 三个标签中任一个标签能匹配上,都会执行这段语句。
switch语句最适合用例子来解释。在例3-4中,测试州代码,如果值为OR、MA或WI,税率就设为3.5,州税率就为0.5;如果值为MO,税率就设为1.0,州税率就为1.5;如果值为CA、NY或VT,税率就设为4.5,州税率就为2.6;如果值为TX,税率就设为3.0,州税率就为0.0;否则,税率就设为2.0,州税率就为2.3。
例3-4:使用switch语句针对多个值测试表达式
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html>
<head>
<title>switch statement</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<script type="text/javascript">
//<![CDATA[
var stateCode = 'NY';
var statePercentage = 0.0;
var taxPercentage = 0.0;
switch (stateCode) {
case 'OR','MA','WI' :
statePercentage = 0.5;
taxPercentage = 3.5;
break;
case 'MO' :
taxPercentage = 1.0;
statePercentage = 1.5;
break;
case 'CA' :
case 'NY' :
case 'VT' :
statePercentage = 2.6;
taxPercentage = 4.5;
break;
case 'TX' :
taxPercentage = 3.0;
break;
default :
taxPercentage = 2.0;
statePercentage = 2.3;
}
alert("tax is " + taxPercentage + " and state is " + statePercentage);
//]]>
</script>
</head>
<body>
<p>Imagine a form with options to pick state code</p>
</body>
</html>
开始时,switch语句中所给出的表达式只是一个州代码变量stateCode。它可以是使用任何关系运算符或逻辑运算符(将在下一节中讨论)的表达式。然后,计算case语句以判断是否匹配。在第一个case语句中,如果州代码为OR、MA或WI,税率就被设置为相同的值。在此例中,相应的几个case值是使用逗号彼此分隔的,意思是三个中任一个匹配上都会执行相应的代码。
如果州代码为TX或MO,则执行各自的case块,但是如果州代码是CA、NY或VT,则会执行VT的case之后的语句块。其他两个州代码没有自己的语句块;它们后面也没有break语句。这就意味着,如果州代码变量与它们三个中的一个能匹配上,程序就会执行到switch语句的结尾或者碰到一个break语句为止的所有语句。这是将同一个语句块附加到多个case值上的另一种方法。这样做与列出这些选项然后用逗号分开,在结果上完全一样。
最后,如果没有一个case语句能匹配得上,则会执行default语句块,然后程序会继续执行switch语句后面的语句。
注意,本例中只对switch控制块本身使用了花括号。这是因为对于switch,程序流用break语句来控制,而不用花括号来控制。然而,仍应使用缩进,但待执行语句可以与case条件放在同一行中,虽然这不是很常用:
case 'OR' : taxPercentage = 3.5; statePercentage = 2.0; break;
在条件控制语句中,所测试的大多数表达是都是相当简单的相等性测试。使用条件运算符可以实现更复杂的条件表达式,甚至是多个表达式。下面我们就来讨论条件运算符。
3.4 条件运算符
条件运算符是测试如等于、等同、关系和逻辑等特定条件的方法。虽然处理会不同,并且它们有的简单有的复杂,但是使用这些运算符的结果只会是两个值(真或假)中的一个。
3.4.1 等于和等同(字符串等于)运算符
在条件表达式中使用的最常见的运算符就是等于(equality)运算符==。在比较一个变量与另一个变量或直接量时使用它,根据结果的不同,会触发一个操作或一组操作:
// at some point in application, assign 3 to variable nValue
var nValue = 3;
...
if (nValue == 3) ...
在此例中,如果变量nValue等于3,就会执行后续的语句(上文中用省略号表示省略)。否则,程序流会跳过该代码块,执行其后的第一条语句。
要注意不要丢掉第二个等号(=)。如果丢掉了第二个等号,表达式就变成了一个赋值表达式,而不是条件测试。变量nValue赋值为3。因为赋值是成功的,所以它会返回真。它总是返回真。不会发生JavaScript错误,因此,在调试时很难发现这个错误。
和其他的运算符一样,为了便于计算表达式的值,等于运算符会自动转换变量的数据类型。如果一个值是数字而另一个值是字符串,当两个值“在样式上”相同时,二者的匹配成功。
var nValue = 3.0;
var sValue = "3.0";
If (nValue == sValue) ...
这可能会导致一些有趣且意外的副作用。尤其是,switch语句中隐式地使用了等于运算符,这就意味着,如果switch表达式的值为“3.0”,下面两个case都符合条件:
case 3.0: ...
case "3.0": ...
从JavaScript 1.3开始,增加了一个新操作符:等同(identity),或叫严格等于运算符,这一运算符专门用于同时对值和类型测试。与标准的等于不同,如果两个操作数相等但数据类型不同,严格等于运算不会返回成功。
if (nValue === sValue) ...
除了进行等于和等同测试,你还可以测试不等于和不等同。不等于运算符是!=:
if (sName != "Smith") ...
不等同运算符是!==:
if (sName !== "Smith")...s
这里有个例子能展示出两个运算符之间最明显的不同。在例3-5中,用一个字符串对一个数值变量分别进行等于和等同两种测试,用一个数值对一个字符串变量分别进行不等于和不等同两种测试。
例3-5:等于和等同之间的精确测试
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html>
<head>
<title>Identity and Equality</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<script type="text/javascript">
//<![CDATA[
var sValue = "3.0";
var nValue = 3.0;
if (nValue == "3.0") alert("According to equality, value is 3.0");
if (nValue === "3.0") alert("According to identity, value is 3.0");
if (sValue != 3.0) alert ("According to equality, value is not 3.0");
if (sValue !== 3.0) alert ("According to identity, value is not 3.0");
//]]>
</script>
</head>
<body>
<p>Some page content</p>
</body>
</html>
在第一种情形中,使用了一个字符串“3.0”对数字3.0运用等于运算符来进行测试。结果为真,因此会打开那个对话窗口。然而,等同比较失败了,所以第二个窗口不会打开。
在第二个情形中,使用数字3.0来测试字符串“3.0”。不等于测试失败了,因为对于这一运算符来说,两个值是相同的。然而,在使用不等同运算符时,比较结果就是真,因此该对话窗口会打开。
例子3-5还引入了处理只有一条语句的条件语句的一种快捷方法。在这种情形中,不需要用花括号,因为这种关联很有可读性,并
且只有一条要执行的语句。
正如在例3-5中所看到的,等同运算符更精确。如果是这样,你可能会想知道为什么它反而应用得不那么广泛。
从JavaScript刚开始时,就有等于运算符及其逆运算—不等于。所有JS引擎都支持它们。等同运算及其逆运算是在后来的JavaScript 1.2中增加的。另外,在ECMA 262规范的第一版中,排除了等同运算符,直到ECMA 262的3.0版本才重新加上它们。因此,也不能保证所有的浏览器和所有的JS引擎都支持等同运算。
除非你能控制用哪种浏览器来访问脚本,你必须假设等同或严格等于运算是不被支持的。在几年后,随着一些老浏览器的最终消亡,严格等于运算符可能会被广泛使用。
测试是否等于很有用,但是有时你还需要测试一个范围的值,而不只是一个特定值。这就要用到大于和小于。
3.4.2 其他的关系运算符
关系运算符指的是:一个操作数与另一个操作数相比较,根据结果的不同,会执行一行或几行代码。等于和严格等于运算符也是关系运算符,除此之外,有时我们还需要一些其他的关系运算符来测试一个值是否比另一个值大或小,而不仅是等于。
对于大于运算符(>),如果右侧的操作数小于左边的操作数的话,则返回真。对于大于等于运算符(>=),如果右侧的操作数小于或等于左边的操作数的话,则返回真:
var nValue = 1.0;
if (nValue > 3.0) // false
...
if (nValue >= 1.0) // true
...
if (nValue >= 0.5) // true
...
对于小于运算符(<),如果右侧的操作数大于左边的操作数的话,则返回真。对于小于等于运算符(<=),如果右侧的操作数大于或等于左边的操作数的话,则返回真,如下列比较例子所示:
var nValue = 1.0
if (nValue < 3.0) // true
...
if (nValue <= 1.0) // true
...
if (nValue <= 0.5) // false
...
与等于运算相似,在对于数值和字符串进行大于或小于运算时,会发生隐式的类型转换。因此,下例中的计算结果为真:
sValue = "1.0";
if (sValue >= 2.0) // true
字符串仅在格式正确时才进行转换。例如,在进行隐式转换的时候,JavaScript不会把“one”转换为“1”或“1.0”。
测试一个值比另一个值大还是小很有用,而测试一个变量或表达式的结果是否在一个范围内也很有用。在例3-6中,测试了一个变量是否在所给定的0至100(包括)的范围内,意思就是值也可以是0和100。它还测试了值是否在0至100的范围内,不包括0和100。最后,它测试这个值是比100大,还是比0小。根据结果显示出相应的消息。
例3-6:测试数字的范围
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html>
<head>
<title>Testing value in range</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<script type="text/javascript">
//<![CDATA[
var nValue = 0;
if (nValue >= 0 && nValue <= 100) {
alert("value between 0 and 100, inclusive");
} else if (nValue > 0 && nValue < 100) {
alert("value between 0 and 100 exclusive");
} else if (nValue > 100) {
alert ("value over 100");
} else if (nValue < 0) {
alert ("value is negative");
}
//]]>
</script>
</head>
<body>
<p>Some page content</p>
</body>
</html>
在前两个比较中,为了建立起范围,需要依赖额外的运算符:逻辑运算符。在“3.2.6位运算”这一节中也曾引入过一个这样的操作符&&。稍后我们会详细讲解这些,但是首先,让我们先讲讲JavaScript中的唯一的一个三元运算符。
3.4.3 仅有的一个JavaScript三元运算符
在本章中,我们介绍过的运算符有一元的(一个操作数),还有二元的(两个操作数)。在JavaScript中,还有一个三元运算符—条件运算符,它要处理三个操作数。下面是一个使用它的例子:
var nValue = 1.0;
var sResult = (nValue > 0.5) ? "value over 0.5" : "value not over 0.5";
此例中,sResult被设为“value over 0.5”,因为条件值为真,就返回第二个操作数。下面是条件运算符的格式:
condition ? value if true; value if false;
从效果上来说,条件运算符是相当常见的“如果(表达式),做这些;否则,做那些”的一种快捷方法,如下列代码:
var stateCode = 'OR';
var taxPercentage = 0.0;
if (stateCode == 'OR') {
taxPercentage = 3.5;
} else {
taxPercentage = 4.5;
}
转换成使用条件运算,代码就变成了:
var taxPercentage = (stateCode == 'OR') ? 3.5 : 4.5;
这是一种既方便又具可读性的快捷方法,因此它使用的相当普遍。在本书后面,还有更多关于此运算符在解析浏览器差别时的应用。
3.5 逻辑运算符
到目前为止,本书中的大多数例子中给出的条件表达式通常是由一个运算符和两个操作数组成。如下例这样:
if (sValue == 'test')
然而,在很多时候,条件表达式要满足几个不同的条件,而每个条件由一个表达式来表示,并使用一个JavaScript逻辑运算符将它们组合在一起。
JS中有三个逻辑运算符:两个二元运算符和一个一元运算符。第一个是逻辑与,使用&&来表示。在用于条件语句时,与运算要求运算符两边的表达式的值都为真,整个表达式的值才为真:
var nValue = 10;if ((nValue > 10) && (nValue <=100)) // 当nValue大于10 且小于等于100时为真
使用这个与运算符进行连接的表达式的返回值为假,因为变量nValue等于10,这意味着第一个表达式为假。如果第一个表达式的值为假,那么JavaScript引擎就不会处理第二个表达式,因为无论怎样整个语句的值都会是假。
第二个运算符是逻辑或运算,它使用两个竖线(||)来表示。在用于条件语句时,或运算要求只要任意一边的表达式为真,整个表达式的值就为真。
var nValue = 10;if ((nValue > 10) || (nValue <= 100)) // 若nValue 大于10 或小于等于100,则为真
这段代码的结果是条件语句为真,因为变量小于100。逻辑或运算符两边都会被计算,因为此运算只要两边的表达式有一个为真就能返回真。
最后一个逻辑运算符是逻辑非。此运算返回了操作数的逻辑反。如果操作数为真,则返回假;如果操作数为假,则返回真:
var nValue = 10;if (!(nValue > 10)) // 若nValue 小于等于10, 则返回真;否则返回假
对于两个逻辑运算符,JavaScript引擎有一种被称作第一表达式短路的计算方法。如果逻辑运算符是与(&&)运算,而第一个表达式为假,就不会计算第二个,因为整个表达式的值只能为假。
在使用逻辑或运算符时,如果第一个表达式的值为真,第二个表达式就不用计算了。运算数中有一个为真,那么或表达式的值就会为真。
理解了短路计算是如何工作的,你可以把对于CPU或其他关键资源占用较少的表达式用在前面,这样做能够帮你的应用提升一点效率。
JavaScript最佳实践:在使用逻辑与/或运算符时,利用短路计算,把关键表达式或占用关键资源少的表达式放在前面。
还要注意,虽然本节中的例子中都对表达式使用了括号,但是这并不是必须的;关系运算符的优先级比逻辑运算符的优先级高,因此会先计算。在例3-6中,我没有对与运算符使用括号。
然而,我发现括号不仅能使整个表达式更具可读性,它还是一个很好的、视觉上的双重检查。
JavaScript最佳实践:用括号将逻辑运算符(&&或||)两边的表达式括起来。
3.6 高级语句:循环语句
在完成剩余两个内置的JavaScript对象之前,我们先花点时间看看高级JS语句:循环。循环语句中带有条件判断,就像前面讲过的if . . . else . . .条件语句那样。然而,当表达式值为真时,程序在每次循环结束之后又会返回去进行同样的条件判断。
3.6.1 while循环
最简单的JavaScript循环在每次循环开始时进行条件判断,如果表达式的值为真,则继续循环。在循环中的某些地方,会对所包含的某些变量进行修改,强制表达式的值变成假,从而使循环终止。关键字while用来表示这种类型的循环。
在例3-7中,用于判断的表达式中的一个变量在每次循环中都加1,直到该变量的值超过10为止。此时,循环就结束了。
例3-7:在while循环中测试条件值
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html>
<head>
<title>While Loop</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
</head>
<body>
<script type="text/javascript">
//<![CDATA[
var iValue = 0;
while (iValue < 10) {
iValue++;
document.writeln("iValue is " + iValue + "<br />");
}
//]]>
</script>
</body>
</html>
通常,你在while循环中所做的不仅仅是给某个值加1,更详细的例子可以在本书其他章节中看到。
3.6.2 do . . . while循环
在前面一节中,while循环展示了如何在循环执行之前进行条件判断。如果条件在一开始就不成立,那么循环所包含的代码就一次也不会执行。然而,有很多时候,我们希望代码至少执行一次,无论条件如何,即无论条件成立如否。这就需要用do . . . while循环。
与while循环不同,do . . . while循环在代码块结束时计算条件表达式。因此,代码块会至少执行一次。对于例3-7,如果想让所包含的代码块至少执行一次,循环可以修改成这样:
do {
iValue++;
document.writeln("iValue is " + iValue + "<br />");
} while (iValue < 10)
对于while循环和do . . . while循环来说,都是条件判断决定了循环是否继续执行。什么样的条件都可以—包括复杂的条件,比如下面这样的:
while (iValue < 10 && iValue >= 3) ...
还有另一种循环:for循环。使用for循环时,你可以指定循环的内容所执行的次数。
3.6.3 for循环
除了使用条件判断之外,可以使用for循环对循环中所包含的代码执行若干次。有两种不同类型的for循环,虽然它们不是在所有浏览器中都实现了。
在所有浏览器中都实现了的是最常见的for循环,它分3段:变量设置一个起始值;该变量由每次循环来做更新;在符合了一个什么样的条件后,循环结束:
For (initial value; condition; update) {
...
}
下面这段代码表示要循环10次,每次循环会打印出一个“hello”:
for (var i = 0; i < 10; i++) {
document.writeln("hello<br />");
}
变量i设为0。随着每次循环迭代,都会测试条件是否满足(值还小于10);如果不满足,就执行循环中的代码块,然后对条件变量加1。条件可以设置为用户变量或设置为对数组元素的遍历(第4章将介绍数组)。
第二种for循环是for...in循环,它将一个数组中的每个元素作为一个独立的项来访问。这种很方便的语句的语法为:
for (variable in object) {
...
}
在演示for...in循环之前,我先稍微跑一下题,说说关联数组这个话题。我们将在第4章中讲数组,从第9章开始讲对象,而对于一种叫做关联数组的构造来说,for...in循环尤为有用。
关联数组(associative array)是一个哈希(hash)数组,其中的每个元素都可以通过一个键值来访问,键值是一个与值相关联的字符串。对象,比如JavaScript中的document对象,都是关联数组的例子。前面的例子中的document对象只有一个项目—writeln函数,这个函数是它的属性数组的一个成员。实际上有很多这样的document对象的属性。不用像大多数数组那样必须使用数字型的索引才能访问这些属性,我们可以使用属性名来访问它们。
我们再回到for...in循环上,这种控制语句不仅可以用于遍历一个对象的所有属性,还可以遍历每个属性的值。在例3-8中,这种方法不仅用来打印出对象的属性,还用到属性的值—使用eval来计算字符串,就仿佛这一字符串就是一个直接语句。在JavaScript的for...in语句中,使用了window对象来查找它都有什么属性。这些属性中有很多看起来非常陌生,因为它们是2级DOM的一部分,这部分内容将在第11章中讲述。
例3-8:使用for...in来展示一个对象的属性
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html>
<head>
<title>Expose the Objects</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
</head>
<body>
<h1>Expose Me</h1>
<p>Going undercover to expose the document object's dirty little secrets..</p>
<script type="text/javascript">
//<![CDATA[
for (docprop in document) {
document.writeln(docprop + "=");
eval ("document.writeln(document." + docprop + ")");
document.writeln("<br />");
}
//]]>
</script>
</body>
</html>
在不同的浏览器上使用该例子,或使用不同的对象改写该例子,你都会得到一些有趣的结果。虽然不同浏览器在对象实现上非常相似,但也不完全一样。如果把代码修改为使用不同的对象(浏览器对象模型或文档对象模型),你可能会发现一个和我所发现的一样的bug。在这种情况下,Firefox中有一个bug:未实现的属性domConfig会产生一个未处理的异常。
第3种for循环是foreach,它是在基于Gecko的浏览器中的JavaScript
1.6中实现的。这种循环使用了第一个参数中的一个回调函数,并且使用一个对象充当回调函数中的主要引用。因为foreach不是一个跨平台的标准,也不能用于我们尚未讨论过的功能,因此,除了给出Mozilla组织关于此语句的文档之外,我不对它进一步讲述。Foreach语句的文档网址为http:// developer.mozilla. org/en/ docs/Core_JavaScript_1.5_ Reference: Objects: Array:forEach。
在条件语句中也可以使用in。例如,要检查一个键值(属性)是否存在于一个关联数组(对象)中,你可以这样写:
if ("URL" in document) {
alert(document.URL);
}
这种语法不是很常用,我们将在下一章和本书后面的章节中进一步讨论关联数组。当然,如果你将来看到这种类型的代码,你就会认出它是用来作什么。
现在,我们已经了解了很多JavaScript的功能,是该让更进一步了解内置的JavaScript对象的时候了。关于内置的JavaScript对象,请见第4章。
3.7 习题
1. 在下面的代码中添加括号,使表达式的值变成8:
var valA = 37;
var valB = 3;
var valC = 18;
var resultOfComp = valA - valB % 3 / 2 * 4 + valC - 3;
1 2. 使用switch 语句测试一个表达式的值是否为“one”、“two”或“three”,如果表达式为“one”或“two”,将变量赋值为OK;如果表达式为“three”,则赋值为OK2;如果一个也匹配不上,则赋值为NONE。
2 3. 你有3个变量:varOne、varTwo和varThree。请写一段代码测试这三个变量,并且仅在varOne 是33,varTwo 小于等于100,而varThree 大于0的情况下执行某个代码块。
3 4. 执行一个循环打印出10到20之间的每个数字。
4 5. 按相反顺序完成第4题中的要求。答案见附录。

浙公网安备 33010602011771号