第一节 JavaScript基础
1. 接触JavaScript
u JavaScript简史
对网页设计人员来说,这是一个充满着挑战和机遇的时代。近几年来,网页设计工作已经从一种混乱无序和即兴发挥的状态,逐渐发展为一种有着成熟的设计原则可供遵循的流水线作业。有越来越多的网页设计人员开始采用一种标准化的思路来建立网站,而实现这一思路和方法的具体技术则称为“ Web 标准”。
请看以下例子:
当网页设计人员谈沦起与 Web 标准有关的话题时, XHTML (可扩展的超文本标记语言)和 Css (层叠样式表)通常占据着核心地位。不过,由 W3C (万维网联盟)批准并由所有与标准相兼容的 web 浏览器支持的第三方技术称为 DOM (文档对象模型)。我们可以利用 DOM 去改善文档的可交互性,就像我们可以利用 CSS 给文档添加各种样式一样。
在开始学习 DOM 之前,我们先回顾一下使网页具备可交互性的程序设计语言。这种语言就是 JavaScript ,它已经诞生相当长的时间了。
JavaScript 是 Netscape 公司与 sun 公司合作开发的。在 JavaScript 出现之前, Web 浏览器不过是一种能够显示超文本文档的软件的基本部分。而在 JavaScript 出现之后,网页的内容不再局限于枯燥的文本,它们的可交互性得到了显著的改善。 JavaScript 的第一个版本,即 JavaScript 1.0版本,出现在 1995 年推出的 Netscape Navigator 2 浏览器中。
在 JavaScript 1.0 发布时, Netscape Navigator 主宰着浏览器市场,微软的 IE 浏览器则扮演着追赶者的角色。微软在推出 IE3 的时候发布了自己的 VBScript 语言并以 Jscript 为名发布了 JavaScript 的一个版本,以此很快跟上了 Netscape 的步伐。
面对微软公司的竟争, Netscape 和 Sun 公司联合 ECMA (欧洲计算机制造商协会)对 JavaScript 语言进行了标准化。其结果就是 ECMAScript 语言,这使得同一种语言又多了一个名字。虽说 ECMAScript 这个名字没有流行开来,但人们现在谈论的 JavaScript 实际上就是 ECMAScript 。
到了 1996 年, JavaScript 、 ECMAScript 、 JScript ― 随便你们怎么称呼它,己经站稳了脚跟。 Netscape 和微软公司在它们各自的第 3 版浏览器中都不同程度地提供了对 JavaScript 1.1 语言的支持。
这里必须指出的是, JavaScript 与 sun 公司开发的 Java 程序语言没有任何联系。人们最初给 JavaScript 起的名字是 LiveScript ,后来选择“ JavaScript ”作为其正式名称的原因,大概是想让它听起来有系出名门的感觉,但令人遗憾的是,这一选择反而史容易让人们把这两种语言混为一谈,而这种混淆又因为各种 Web 浏览器确实具备这样或那样的 Java 客户端支持功能的事实被进一步放大和加剧。事实上,虽说 Java 在理论上几乎可以部署在任何环境中,但 JavaScript 却只局限于 Web 浏览器。
JavaScript 是一种脚本语言, JavaScript 脚本通常只能通过 Web 浏览器去完成某种操作而不是像普通意义上的程序那样可以独立运行。因为需要由 Web 浏览器进行解释和执行,所以 JavaScript 脚本不像 Java 和 C++ 等编译型程序设计语言那样用途广泛。不过,这种相对的简单性也正是 JavaScript 的长处:因为比较容易学习和掌握,所以 JavaScript 很受那些本身不是程序员,但希望能够通过简单的剪贴操作把脚本嵌入他们的现有网页中的普通用户们的欢迎。
JavaScript 还向程序员提供了一种操控 Web 浏览器的手段。例如, JavaScript 语言可以用来调整 Web 浏览器窗口的高度、宽度和屏显位置等属性。以这种办法给出 Web 浏览器本身的属性可以看做是 BOM (浏览器对象模型)。 JavaScript 的早期版本还提供了一种初级的 DOM (文档对象模型)。
浏览器之争
Netscape Navigator4 (NN4)浏览器发布于 1997 年 6 月, IE4 浏览器发布于同年的 10 月。这两种浏览器都对它们的早期版本进行了许多改进,使用得到极大扩展的 DOM ,可以通过 JavaScript 完成的功能大大增加,而网页设计人员也开始熟悉一个新的名词:DHTML。
u DHTML
DHTML 是“Dynamic HTML”(动态 HTML )的简称。严格地说, DHTML 并不是一项单一的新技术,而是 HTML 、 CSS 和 JavaScript 这三种技术相结合的产物. DHTML 背后的含义是:
* 利用 HTML 把网页标记为各种元素;
* 利用 CSS 设计各有关元素的排版样式并确定它们在窗口中的显示位置;
* 利用 JavaScript 实时地操控和改变各有关样式。
DHTML 指的是上述三项技术的相互结合。利用 DHTML ,复杂的动画效果一下子变得非常容易实现。例如,可以先用 HTML 标记一个如下所示的页面元素:
<div id="myelement">This is my element</div>
然后,可以用 CSS 为这个页面元素定义如下所示的位置样式:
#myelement {
position:absolute;
left: 50px;
top: 100px;
}
接下来,只需利用 JavaScript 改变 myelement 元素的 left 和 top 样式,就可以让它在页面上随意移动。不过,这种简单性只停留在理论上——因为 NN4 和 IE4 浏览器使用的是不同的且不兼容的 DOM ,所以要想实际获得这种效果还需要程序员做很多工作。换句话说,虽然浏览器制造商的目标是一样的,但他们在解决 DOM 问题时采用的办法却完全不同。
u 制订标准
就在浏览器的制造商们为了压倒竞争对手而以 DOM 为武器展开一场营销大战的同时, W3C 不事声张地推出了一个标准化的 DOM 。令人欣慰的是, Netscape 、微软和其他一些浏览器制造商们还能抛开彼此的敌意而与 W3C 携手制定新的标准,并于 1998 年 10 月完成了“第 1 级 DOM " ( DOM Level l )。 回到刚才的例子,我们一起看看新的标准化 DOM 是如何解决同样的问题的。我们己经用 <div> 标签定义了一个 ID 为 myelement 的页面元素,现在需要找出它的 left 位置并把这个值保存到变量 xpos 中。下面是需要用到的语法:
var xpos = document.getElementByld ("myelement" ).style.left
乍看起来,这与刚才那两种非标准化的专有 DOM 相比并没有明显的改进。但事实上,标准化的 DOM 有着非常远大的抱负。浏览器制造商们感兴趣的只不过是一些通过 JavaScript 操控网页的具体办法,但 W3C 推出的标准化 DOM 却可以让任何一种程序设计语言对使用任何一种标记语言编写出来的任何一份文档进行操控。
DOM 是一种 API (应用编程接口)。简单地说,API 就是一组己经得到有关各方共同认可的基本约定。在现实世界中,相当于 API 的例子包括(但不限于):
* 摩尔斯码
* 国际时区
* 化学元素周期表
以上这些都是不同学科领域中的标准,它们使得人们能够更方便地进行交流与合作。如果没有这样的标准,事情往往会演变成为一场灾难。别忘了,英制度量衡与公制度量衡之间的差异至少导致过一次火星探测任务的失败。
在软件编程领域中,虽然存在着多种不同的语言,但很多任务却是相同或相似的。这也正是人们需要 API 的原因.一旦掌握了某个标准,就可以把它应用在许多不同的环境中。虽然有关的语法会因为使用的程序设计语言而有所变化,但这些约定却总是保持不变的。因此,在学完关于如何通过 JavaScript 使用 DOM 的书之后,你们获得的关于 DOM 的新知识对你们今后的工作——例如,需要使用诸如 PHP 或 ASP.NET 之类的程序设计语言去解析一份 XML 文档的时候,也会有很大的帮助。 W3C 对 DOM 的定义是:“一个与系统平台和编程语言无关的接口,程序和脚本可以通过这个接口动态地对文档的内容、结构和样式进行访问和修改。”
W3C 推出的标准化 DOM ,在独立性和适用范围等诸多方面,都远远超出了各自为战的浏览器制造商们推出的各种专有 DOM 。我们知道,浏览器市场份额大战的赢家是微软公司,但具有讽刺意味的是,专有的 DOM 和 HTML 标记对这个最终结果并无影响。 IE 浏览器之所以能击败其他对手,其主要原因不过是有运行着 Windows 操作系统的个人电脑都预装了它而己。受浏览器之争影响最重的人群是那些网站和网页设计人员。需要同时支持多种浏览器的软件开发工作,曾经是程序员们的噩梦。除了刚才提到的那些在 JavaScript 实现方面的差异之外 Netscape Navigator 和 IE 这两种浏览器在对 CSS 的支持方面也有许多非常不同的地方。有不少程序员都把编写那些可以同时工作在这两种浏览器环境下的样式表和脚本的工作视为一种黑色艺术。
为了打破浏览器制造商们筑起的专利壁垒,一群志同道合的程序员建立了名为 Web 标准计划(简称 WASP , http: //webstandards.org/ )的小组。 WaSP 小组采取的第一个行动就是,鼓励浏览器制造商们接受 W3C 制定和推荐的各项标准―也就是在浏览器制造商们的帮助下得以起草和完善的那些标准。或许是因为来自 WaSP 小组的压力,又或许是因为企业的内部决策,浏览器制造商后来推出的下一代浏览器产品对 Web 标准的支持得到了极大的改善。微软公司在正 IE 8 浏览器中内建了对 W3C 制定的标准化 DOM 的支持,但同时继续支持其专有的 Microsoft DOM。
目前使用的 95%以上的浏览器都具备对 DOM 的内建支持。发生在 20 世纪 90 年代后期的浏览器大战已经离我们越来越遥远。虽说还没有一种浏览器能够提供对 W3C DOM 的完美支持,但现代的浏览器都至少实现了 W3C 相关标准中 95%的规范,而这意味着在编写 JavaScript 代码时几乎不需要考虑它们将运行在何种浏览器环境下了。虽然 IE 浏览器的开发工作停顿了下来,但网页设计人员的日子已经不像过去那么困难了。过去,程序员在编写 JavaScript 脚本时往往不得不增加一些代码去探测在客户端运行的是哪种浏览器:现在,程序员只需编写一次代码就几乎可以把它们应用在所有的场合了。只要遵守 DOM 标准,程序员就可以相当有把握地确信,自己编写的脚本几乎在所有的环境下都可以正常工作。
u 小结
在前面对 JavaScript 发展简史的介绍中,特别提到,不同的浏览器采用了不同的办法来完成同样的任务。这一无法回避的事实不仅主宰着如何编写 JavaScript 脚本代码,还影响着 JavaScript 教科书的编写方式。
JavaScript 教科书的作者往往会提供大量的示例代码以演示这种脚本语言的使用方法,而完成同一项任务的示例脚本往往需要为不同的浏览器编写两次或更多次。就像你们在绝大多数网站上查到的代码一样,在绝大多数 JavaScript 教科书的示例脚本中往往充斥着大量的浏览器检测代码和分支调用结构。类似地,在 JavaScript 技术参考文献中,函数和方法的清单也往往是一式多份——至少需要标明哪种浏览器支持哪些函数和方法。
如今这种情况己经有所改变。多亏了标准化的 DOM ,不同的浏览器在完成同样的任务时采用的细节做法己经非常一致了。因此在本书中,当演示如何使用 JavaScript 和 DOM 完成某项任务时,将不再需要撇开主题去探讨如何对付不同的浏览器。如果无特殊的必要,我们将尽量避免涉及任何一种特定的浏览器.。此外,我们在后面的内容中将不再使用“DHTML”这个术语,因为我们认为这个术语与其说是一个技术性词汇,不如说是一个市场营销嘘头。首先,它听起来很像是 HTML 或 XHTML 语言的另一种扩展,因而很容易造成误解或混淆;其次,这个术语容易勾起一些痛苦的回忆―如果你向 20 世纪 90 年代后期的程序员们提起“ DHTML " ,你将很难让他们相信它现在己经变成一种简单、易用的标准化技术。
DHTML 是 HTML/XHTML 、CSS 和 JavaScript 相结合的产物,但把这些东西真正凝聚在一起的是 DOM 。如果真的需要有个词汇来描述这一过程的话,我们就应该使用一个更精确的词汇。用 DHTML 来称呼与浏览器有关的编程工作并不是不可以,但用它来描述基于有关标准的代码编写工作就不那么恰当了.在探讨如何使用 W3C DOM 来处理文档和样式表时,我们认为“ DOM 脚本程序设计”是一种更精确的说法。DHTML 只适用于 Web 文档,“DOM 脚本程序设计”则涵盖了使用任何一种支持 DOM API 的程序设计语言去处理任何一种标记文档的所有情况。具体到 web 文档, JavaScript 语言的特点使它成为了 DOM 脚本程序设计的最佳选择。
在正式介绍 DOM 脚本程序设计技巧之前,我们将在下一节先向大家简要地介绍一下 JavaScript 的语法。
3. JavaScript的语法
在网页中加入 JavaScript ,要在你的网页中使用 JavaScript ,你首先必须要知道该将它放在哪儿。其实很简单,只要在你的网页(HTML文件)中插入 <SCRIPT> 和 </SCRIPT> 标记对,你就可以在这两个标记队之间插入你的 JavaScript 代码了:
<script>
alert("Hello world!");
</script>
另外,你也可以将 JavaScript 代码放在另一个单独的文件里,然后在网页(HTML文件)中使用 “SRC= 此单独文件的路径/地址(URL)”来使用此单独文件里的 JavaScript 程序代码。一般将这个单独的文件保存为扩展名为 .JS 的文件:
<script src="mycode.js"></script>
你可以在一个 HTML 文件中使用 <SCRIPT> </SCRIPT> 标记对任意多次。虽然在通常情况下你都会将所有代码放在 HTML 文件的某一处,但有些时候为了保证你的JavaScript程序能够正确运行,你不得不将它们分散地放在 HTML 文件的多个地方。
不管你是在 HTML 文件中直接插入代码还是通过外部的单独的文件来使用 JavaScript ,通常都是将 <SCRIPT> </SCRIPT> 标记对放在 <HEAD> 和 </HEAD> 标记对之间。这样能够保证在你的网页被下载到客户端后开始执行 JavaScript 的时候你的 JavaScript 代码也已经被下载到客户端了。这同时也是放置你的 JavaScript 函数的好地方,如果你想要某些代码在你的网页显示出来之后才执行的话,你最好将这些代码放在函数里,等网页显示以后再通过调用函数来执行它们。
4. JavaScript 语法基础
在你开始编写代码之前,你必须知道一些基本的 JavaScript 语法和结构。 JavaScript 语法与 C/C++、Java 的语法很相似,如果你想要了解非常详尽的语法,可以参考 Microsoft's JScript Reference ,在那里你可以看到很多关于 JavaScript 语句、运算符、内建函数等等的内容列表,而我们这里只是讲一些在你开始学习 JavaScript 的时候需要掌握的一些基础语法知识!
注释
JavaScript解释器并不要求JavaScript脚本中的每条语句都必须是可执行的。有时,需要在脚本中写出一些仅供参考或提示性的信息,但并不希望JavaScript解释器真的去执行这样的语句。这种语句称为注释(comment)。
注释语句非常有用,它们可以让你把编写代码时的一些想法和考虑记载下来供今后参考,还可以帮助你追踪有关代码的执行流程。类似于日常生活中的便条,注释语句可以帮助程序员跟踪和追查在脚本中发生的事情。
有多种在JavaScript脚本中插入注释的具体做法。例如,如果使用了两个斜杠作为一行的开头,这一行就将被解释为一条注释:
// Note to self: comments are good.
如果使用这种记号方式,就必须在每行注释的开头加上两个斜杠。也就是说,像下面这样的做法是有问题的——第2行将不会被解释为一条注释:
// Note to self:
comments are good.
如果你想写出两行注释,就必须把它们写成如下所示的样子:
// Note to self:
// comments are good.
一条跨越多行的注释还可以用下面这个方式来给出:在整段注释内容的开头加上一个“/*”,在整段注释内容的末尾加上一个“*/”。下面是一个多行注释的例子:
/* Note to self:
comments are good. */
这种记号方式在需要插入跨越多行的大段注释内容时很有用,它可以提高整个脚本的可读性。
还可以使用HTML风格的注释,但这种做法仅适用于单行注释。换句话说,JavaScript解释器对“<!-”的处理与对“//”的处理是一样的:
<!- This is a comment in JavaScript.-->
如果是在HTML文档中,还需要以“->”来结束这种注释语句,如下所示:
<!- This is a comment in HTML ->
但JavaScript不要求这样做,它会把“->”视为注释内容的一部分。
请注意,HTML允许上面这样的注释跨越多个行,但JavaScript要求这种注释的每行都必须在开头加上“<!-”来作为标志。
因为JavaScript解释器在处理这种风格的注释时与大家所熟悉的HTML做法不同,为避免发生混淆,笔者建议大家最好不要在JavaScript脚本中使用HTML风格的注释。如果没有特别的理由,用“//”记号给出单行注释、用“/*”记号给出多行注释。
变量
在日常生活里,有些东西是固定不变的,有些东西则会发生变化。例如,人的姓名和生日是固定不变的,但心情和年龄却会随着时间的推移而发生变化。在谈论程序设计语言时,人们把那些会发生变化的东西称为变量(variable)。
我们的心情会随着我的切身感受而变化。假设我有一个变量mood(意思是“心情”),我可以把此时此刻的心情存放到这个变量中。不管这个变量的值是“happy”还是“sad”,它的名字始终是mood。我们可以随时改变这个值。
类似地,假设我现在的年龄是33岁。一年之后,我的年龄就是34岁。我可以使用变量age来存放我的年龄并在生日那天改变这个值。当我现在去查看age变量时,它的值是33;但一年之后,它的值将变成34。
把值存入变量的操作称为赋值(assignment)。我把变量mood赋值为“happy”,把变量age赋值为33。
下面是在JavaScript中对这些变量进行赋值的语法:
mood = "happy";
age = 33;
把值赋值给变量后,我们就可以说该变量包含这个值。例如,变量mood现在包含值“happy”,变量age现在包含值33。我们可以用如下所示的语句把这两个变量的值显示在一个弹出式警告窗口中:
alert(mood);
alert(age);
请注意,JavaScript允许程序员直接对变量进行赋值而无需提前对它们做出声明。这在许多程序设计语言中都是不允许的。有相当一部分程序设计语言要求在使用变量之前必须先对它做出“介绍”——术语称之为声明(declare)。
在JavaScript脚本中,如果程序员在对某个变量进行赋值之前未对其做出声明,赋值操作将自动声明该变量。虽然JavaScript没有要求程序员必须这么做,但提前对变量做出声明仍是一种良好的编程习惯。下面的语句对变量mood和age做出了声明:
var mood;
var age;
每次只声明一个变量的做法并不是绝对的,JavaScript也允许程序员用一条语句声明多个变量,如下所示:
var mood, age;
JavaScript甚至允许程序员把声明变量和对该变量进行赋值的这两项操作合起来一次完成:
var mood = "happy";
var age = 33;
我们甚至还可以像下面这样做:
var mood = "happy", age = 33;
像上面这样声明和赋值各有关变量是最有效率的做法,这条语句的效果相当于下面这些语句的总和:
var mood, age;
mood = "happy";
age = 33;
在JavaScript语言里,变量和其他语法元素的名字都是区分字母大小写的。名字是mood的变量与名字是Mood、MOOD或mOOd的变量没有任何关系,它们不是同一个变量。下面的语句是在对两个不同的变量进行赋值:
var mood = "happy";
MOOD = "sad";
age = 33;
JavaScript语法不允许变量的名字中包含空格或标点符号(但美元符号“$”例外)。下面这条语句将导致语法错误:
var my mood = "happy";
JavaScript变量名允许包含字母、数字、美元符号和下划线字符。为了让比较长的变量名有更好的可读性,可以在变量名中的适当位置插入一个下划线字符,就像下面这样:
var my_mood = "happy";
在上面这条语句中,单词“happy”是JavaScript语言中的一个字面量(literal),也就是可以在JavaScript代码中直接写出来的数据内容。字面量除了它本身所给出的内容外无任何附加含义,用大力水手Popeye的话来说:“它就是它!”与此形成对照的是,单词“var”是 JavaScript语言中的一个关键字,my_mood是一个变量的名字;它们都有自身内容以外的含义。
变量mood的值是一个字符串类型的字面量,变量age的值则是一个数值类型的字面量。虽然它们是两种不同的数据类型,但在JavaScript脚本中为它们做出声明和进行赋值的语法无任何区别。有些程序设计语言要求程序员在声明变量的同时还必须明确地对其数据类型做出声明,这种做法称为类型声明(typing)。
要求程序员必须明确地对数据类型做出声明的程序设计语言被称为强类型(strongly typed)语言。像JavaScript这样不要求程序员进行类型声明的语言则被称为弱类型(weakly typed)语言。所谓弱类型意味着程序员可以随意改变某个变量的数据类型。
下面这条语句在强类型语言中是非法的,但在JavaScript语言里却完全没有问题:
var age = "thirty there";
age = 33;
JavaScript并不关心变量age的值是字符串还是数值。
接下来,我们一起看看JavaScript语言中最重要的几种数据类型。
1. 字符串
字符串由零个或多个字符构成。字符包括字母、数字、标点符号和空格。字符串必须放在引号里——单引号或双引号都允许使用。下面这两条语句有着同样的效果:
var mood = 'happy';
var mood = "happy";
你们可以随意选用,但最好能根据字符串所包含的字符来加以选择:如果字符串包含双引号字符,就应该把整个字符串放在单引号中;如果字符串包含单引号字符,就应该把整个字符串放在双引号中:
var mood = "dont't ask";
如果想在上面这条语句中使用单引号,就必须保证字母“n”和“t”之间的单引号能被解释为这个字符串的一部分。换句话说,必须保证这个单引号被解释为这个字符串里的一个字符,而不是被解释为这个字符串的结束标志。这个问题需要使用字符转义(escaping)功能来解决。在JavaScript语言中,对字符进行转义需要用到反斜杠字符,如下所示:
var mood = "dont"'t ask";
类似地,如果想用双引号来给出一个本身就包含着双引号字符的字符串,就必须用反斜杠字符对这个字符串中的双引号进行转义,如下所示:
var height = "about 5'10"" tall;
为了对其他字符进行转义而添加到字符串中的那些反斜杠字符并不是字符串的实际组成部分。你们可以自己去验证这一点:把下面这段代码添加到example.js文件中,然后重新加载test.html文件:
var height = "about 5'10"" tall;
alert(height);
下面是用反斜杠字符对有关字符进行转义后的一个屏幕输出示例。
就个人而言,笔者比较喜欢用双引号来给出字符串。作为一种良好的编程习惯,不管你们选择的是双引号还是单引号,最好能在整个脚本中保持一致。如果在同一个脚本中一会儿使用双引号,一会儿又使用单引号,代码很快就会变得难以阅读和理解。
2.数值
如果想让某个变量包含一个数值,不用限定它必须是一个整数。JavaScript允许程序员使用带小数点的数值,并且允许数值是任意位数,这类数值称为浮点数(floating-point number):
var age = 33.25;
还可以使用负数。负数的表示方式是在有关数值的前面加上一个减号(-),如下所示:
var temperature = -20;
var temperature = -20.33333333;
请看以下例子:
3.布尔值
另一种重要的JavaScript数据类型是布尔(boolean)类型。
布尔数据只有两种可取值——true或false。假设需要这样一个变量:如果我正在睡觉,这个变量将存储一个值;如果我没有睡觉,这个变量将存储另一个值。可以用字符串数据类型来解决这个问题——只要根据具体情况把有关变量赋值为“sleeping”或“not sleeping”即可达到目的,但布尔数据类型显然是一个更好的选择:
var sleeping = true;
从某种意义上讲,为计算机设计程序就是与布尔值打交道。作为最基本的事实,所有的电子电路只能识别和使用布尔数据:电路中有电流或是没有电流。根据具体情况,这两种状态可以代表“真或假”、“是或否”或者“1或0”,但不管它们代表什么,这两种状态绝不可能同时出现——换句话说,在任意时刻只能使用两种可取值中的一种。
与字符串值不同,千万不要把布尔值用引号括起来。布尔值false与字符串值'false'或"false"是两回事!
下面这条语句将把变量married设置为布尔值true:
var married = true;
下面这条语句将把变量married设置为一个包含着单词”true”的字符串:
var married = "true";
数组
字符串、数值和布尔值都属于离散值(scalar)。如果某个变量是离散的,它在任意时刻就只能有一个值。如果想用一个变量来存储一组值,就需要使用数组(array)。
数组是由名字相同的多个值构成的一个集合,集合中的每个值都是这个数组的元素(element)。例如,我们可以用名为beatles的变量来保存Beatles乐队全体四位成员的姓名。
在JavaScriptd脚本中,数组要用关键字Array来声明。在声明数组的同时,程序员还可以对这个数组的元素个数,也就是这个数组的长度(length),做出规定:
var beatles = Array(4);
有时,我们无法提前预知某个数组最终会容纳多少个元素。这没有关系,JavaScript并不要求在声明数组时必须给出它的元素个数,我们完全可以在声明数组时不给出明确具体的元素个数:
var beatles = Array();
向数组中添加元素的操作称为填充(populating)。在填充数组时,不仅需要给出新元素的值,还需要在数组中为新元素指定存放位置,这个位置要通过下标(index)给出。数组里的每个元素都有一个相应的下标。在语句中,下标值必须放在方括号内,如下所示:
array[index] = element;
现在来填充刚才声明的beatles数组。我们将按照人们在提到Beatles乐队成员时的传统顺序(即John、Paul、George和Ringo)进行。首先是第一个下标和元素:
beatles[0] = "John";
以0而不是1作为第一个下标值多少会让人感到有些不习惯,但JavaScript语言就是这么规定的,所以我们这里只能这么做。这一点很重要,但也很容易被忘记,初出茅庐的程序员在刚接触JavaScript数组时经常在这个问题上犯错误。
下面是声明和填充beatles数组的全过程:
var beatles = Array(4);
beatles[0] = "John";
beatles[1] = "Paul";
beatles[2] = "George";
beatles[3] = "Ringo";
有了上面这些代码,我们即可在脚本中通过下标值“2”(beatles[2])来检索取值为“George”的元素了。请注意,beatles数组的长度是4,但它最后一个元素的下标却是3。因为数组下标是从0开始计数的,你们或许需要一些时间才能习惯这一事实。
像上面这样填充数组未免有些麻烦。这里有一种相对简单的方式:在声明数组的同时对它进行填充。这么做时别忘了用逗号把各个元素分隔开:
var beatles = Array("John","Paul","George","Ringo");
上面这条语句会为beatles数组中的每个元素自动分配一个下标:第一个下标是0,第二个是1,依此类推。因此,beatles[2]仍将对应于取值为“George”的元素。
我们甚至用不着明确地表明我们是在创建数组。事实上,只需用一对方括号把各个元素的初始值括起来就足以创建出我们想要的数组了:
var beatles = Array["John","Paul","George","Ringo"];
不过,在声明或填充数组时写出Array关键字是一个良好的编程习惯,这可以提高JavaScript脚本的可读性,并让我们一眼就看出哪些变量是数组。
数组元素不必非得是字符串。可以把一些布尔值存入一个数组,还可以把一组数值存入一个数组:
var years = Array(1940,1941,1942,1943);
甚至可以把这三种数据类型混在一起存入一个数组:
var lennon = Array("John",1840,false);
数组元素还可以是变量:
var name = "John";
beatles[0] = name;
这将把beatles数组的第一个元素赋值为“John”。
数组元素的值还可以是另一个数组的元素。下面两条语句将把beatles数组的第二个元素赋值为“Paul”:
var names = Array("John","Paul","George","Ringo");
beatles[1] = names[3];
事实上,数组还可以包含其他的数组!数组中的任何一个元素都可以把一个数组作为它的值:
var lennon = Array("John",1840,false);
var beatles = Array();
beatles[0] = lennon;
现在,beatles数组的第一个元素的值是另外一个数组。要想获得那个数组里的某个元素的值,我们需要使用更多的方括号。beatles[0][0]的值是“John”,beatles[0][1]的值是1940,beatles[0][2]的值是false。
运算操作符和表达式
我们此前给出的示例语句都非常简单,只是创建了一些不同类型的变量而已。要想通过JavaScript去完成一些有用的工作,我们还需要能够进行计算和处理数据。这就需要完成一些操作(operation)。
加法是一种操作,减法、除法和乘法也是如此。这些算术操作(arithmetic operation)中的每一种都必须借助于相应的操作符(operator)才能完成。操作符是JavaScript为完成各种操作而定义的一些符号。你们其实已经见过一种操作符了:它就是刚才在进行赋值操作时使用的等号(=)。加法操作符是加号(+),减法操作符是减号(-),除法操作符是斜杠(/),乘法操作符是星号(*)。
JavaScript还提供了一些非常有用的操作符作为各种常用操作的快捷方式。例如,如果你想给一个数值变量加上1,可以使用如下所示的语句:
year = year +1;
也可以使用++操作符来达到同样的目的:
year++;
类似地,--操作符将对一个数值变量的值进行减1操作。
加号(+)是一个比较特殊的操作符:它既可以用于数值,也可以用于字符串。把两个字符串合二为一是一种很直观易懂的操作:
var message = "I am feeling" + mood;
像这样把多个字符串首尾相连在一起的操作叫作拼接(concatenation)。这种拼接也可以通过变量来完成:
var mood = "happy";
var message = "I am feeling" + mood;
我们甚至可以把数值和字符串拼接在一起;因为JavaScript是一种弱类型语言,所以这种操作是允许的。此时,数值将被自动转换为字符串:
var year = 2005;
var message = "The year is" + year;
请记住,如果把字符串和数值拼接在一起,其结果将是一个更长的字符串;但如果你用同样的操作符来“拼接”两个数值,其结果将是那两个数值的算术和。请对比下面两条alter语句的执行结果:
alert("10" + 20);
alert(10 + 20);
另一个非常有用的快捷操作符是+=,它可以一次完成“加法和赋值”(或“拼接和赋值”)操作:
var year = 2005;
var message = "The year is";
message += year;
执行完上面这些语句后,变量message的值将是“The year is 2005”。可以用如下所示的alter对话框来验证这一结果:
条件语句
此前介绍给大家的语句都是相对比较简单的声明或运算,而脚本的真正威力体现在它们还可以根据人们给出的各种条件做出判断和决策。JavaScript脚本需要使用条件语句(conditional statement)来做出判断和决策。
在解释脚本时,浏览器将依次执行这个脚本中的各条语句,而我们可以在这个脚本中用条件语句来设置一个条件,只有满足了这一条件才能让更多的语句得到执行。最常见的条件语句是if语句,下面是if语句的基本语法:
if (condition) {
statements
}
条件必须放在if后面的圆括号中。条件的求值结果永远是一个布尔值,即只能是true或false。花括号中的语句——不管它们有多少条,只有在给定条件的求值结果是true的情况下才会得到执行。因此,在下面这个例子中,alter消息永远也不会出现:
if (1 > 2) {
alert("The word has gone mad!");
}
因为1不可能大于2,所以上面这个条件的值永远是false。
在这条if语句中,我是故意把所有的东西都放在花括号里的。这并不是JavaScript的一项语法要求——这么做只是为了让代码更容易阅读和理解。
事实上,if语句中的花括号本身并不是必不可少的。如果if语句中的花括号部分只包含着一条语句的话,那就根本用不着使用花括号,而且这条if语句的全部内容可以写在同一行上:
if (1 > 2) alert("The word has gone mad!");
不过,因为花括号可以提高脚本的可读性,所以在if语句中使用花括号通常是个好主意。
if语句还可以用else来扩展。包含在else子句中的语句会在给定条件的值为false时得到执行:
if (1 > 2)
{
alert("The world has gone mad!");
}
else
{
alert("All is well with the world");
}
因为给定条件“1>2”的值是false,所以我们将看到如下所示的结果。
JavaScript还提供了许多几乎只能用在条件语句里的操作符,其中包括诸如大于(>)、小于(<)、大于或等于(>=)、小于或等于(<=)之类的比较操作符。
如果想比较两个值是否相等,可以使用“等于”比较操作符。这个操作符由两个等号构成(==)。别忘了,单个等号(=)是用于完成赋值操作的。如果你在条件语句的某个条件里使用了单个等号,那么只要相应的赋值操作取得成功,那个条件的求值结果就将是true。
下面是一个错误地进行“等于”比较的例子:
var my_mood = "happy";
var your_mood= "sad";
if (my_mood = your_mood) {
alert("We both feel the same.");
}
上面这条语句的错误之处在于,它是把变量your_mood赋值给变量my_mood,而不是在比较它们是否相等。因为这种赋值操作总会成功,所以这个条件语句的结果将永远是true。
下面才是进行“等于”比较的正确做法:
var my_mood = "happy";
var your_mood= "sad";
if (my_mood == your_mood) {
alert("We both feel the same.");
}
这次,条件语句的结果是false。
JavaScript还提供了一个用来进行“不等于”比较的操作符,它由一个感叹号和一个等号构成(!=)。
if (my_mood != your_mood) {
alert("We're feeling different moods.");
}
逻辑操作符
JavaScript允许我们把条件语句里的操作组合在一起。例如,如果想检查某个变量——不妨假设这个变量的名字是num,它的值是不是在 5~10之间,我将需要进行两次比较操作:首先,比较这个变量是否大于或等于5;然后,比较这个变量是否小于或等于10。这两次比较操作叫作逻辑操作数(operand)。下面是把这两个逻辑操作数组合在一起的具体做法:
if (mun >= 5 && mun <= 10) {
alert("The number is in the right range.");
}
在这里使用了“逻辑与”操作符,它由两个“&”字符构成(&&),它是一个逻辑操作符。
逻辑操作符的操作对象是布尔值。每个逻辑操作数返回一个布尔值true或者是false。“逻辑与”操作只有在它的两个操作数都是true时才会是true。
“逻辑或”操作符由两个垂直线字符构成(||)。只要它的操作数中有一个是true,“逻辑或”操作就将是true。如果它的两个操作数都是true,“逻辑或”操作也将是true。只有当它的两个操作数都是false时,“逻辑或”操作才会是false。
if (mun > 10 || mun < 5) {
alert("The number is not in the right range.");
}
JavaScript还提供了一个“逻辑非”操作符,它由一个感叹号(!)单独构成。“逻辑非”操作符只能作用于单个逻辑操作数,其结果是把那个逻辑操作数所返回的布尔值取反:如果那个逻辑操作数所返回的布尔值是true,“逻辑非’操作符将把它取反为false:
if (!(1<2)) {
alert("All is well with the world");
}
请注意,为避免产生歧义,在上面这条语句中把逻辑操作数放在了括号里——我想让“逻辑非”操作符作用于括号里的所有内容。
我们可以用“逻辑非”操作符把整个条件语句的结果颠倒过来。在下面的例子里,我特意使用了一对括号来确保“逻辑非”操作符将作用于两个逻辑操作数的组合结果:
if (!(mun > 10 || mun < 5)) {
alert("The number is in the right range.");
}
循环
if语句或许是最重要、最有用的条件语句了。if语句的唯一不足是它无法用来完成重复性的操作。在if语句里,包含在花括号里的代码块只能执行一次。如果需要反复多次地执行同一个代码块,就必须使用循环语句。
循环语句可以让我们反复多次地执行同一段代码。循环语句分为几种不同的类型,但它们的工作原理几乎一样:只要给定条件仍能得到满足,包含在循环语句里的代码就将重复地执行下去;一旦给定条件的求值结果不再是true,循环也就到此为止。
while
while循环与if语句非常相似,它们的语法几乎完全一样:
while (condition) {
statements;
}
while循环与if语句唯一的区别是:只要给定条件的求值结果是true,包含在花括号里的代码就将反复地执行下去。下面是一个while循环的例子:
var count = 1;
while (cpunt < 11) {
alert(count);
count++;
}
我们来仔细分析一下上面这段代码。首先,创建数值变量count并赋值为1;然后,以count < 11——意思是“只要变量count的值小于11,就重复执行这个循环”,为条件创建一个while循环。在while循环的内部,用“++”操作符对变量count的值执行加1操作;而这一操作将重复执行10次。如果用Web浏览器来观察这段代码的执行情况,将会看到一个alert对话框闪现了10次。这条循环语句执行完毕后,变量count的值将是11。
注意 这里的关键是在while循环的内部必须发生一些会影响循环控制条件的事情。在上例中,我们在while循环的内部对变量count的值进行了加1操作,而这将导致循环控制条件在经过10次循环后的求值结果会变成false。如果我们不对变量count的值执行加1操作,这个while循环将永远执行下去。
do...while
类似于if语句的情况,while循环的花括号部分所包含的语句有可能不被执行:因为对循环控制条件的求值发生在每次循环开始之前,所以如果循环控制条件的首次求值结果是false,那些代码将一次也不会被执行。
在某些场合,我们需要那些被包含在循环语句内部的代码至少执行一次。这时,do循环将是我们的最佳选择。下面是do循环的语法:
do {
statements;
} while (condition);
这与刚才介绍的while循环非常相似,但有个显而易见的区别:对循环控制条件的求值发生在每次循环结束之后。因此,即使循环控制条件的首次求值结果是false,包含在花括号里的语句也至少会被执行一次。
我们可以把前一小节里的while循环改写为如下所示的do...while循环:
var count = 1;
do {
alert(count);
count++;
} while (count < 11);
这段代码的执行结果与while循环的执行结果完全一样:alert消息将闪现10次;在循环结束后,变量count的值将是11。
再来看看下面这个变体:
var count = 1;
do {
alert(count);
count++;
} while (count < 1);
在上面这个do循环里,循环控制条件的求值结果永远不为true:变量count的初始值是1,所以它在这里永远不会小于1。可是,因为do循环的循环控制条件出现在花括号部分之后,所以包含在这个do循环内部的代码还是执行了一次。也就是说,仍将看到一条alert消息。这里还有一个细节需要大家注意:这些语句执行完毕后,变量count的值将是2而不是1——虽然这没有改变这个例子里“循环控制条件的求值结果是false”的事实。
for
用for循环来重复执行一些代码也很方便。从循环执行一些代码的意义上讲,它类似于while循环;从另一个方面看,for循环只是刚才介绍的do循环的一种变体形式。如果仔细观察上一小节里的do循环的例子,我们就会发现它们都可以被改写为如下所示的样子:
initialize;
while (condition) {
statements;
increment;
}
而for循环不过是把如上所示的循环结构进一步改写为如下所示的紧凑形式而已:
for (initial condition; test condition; alert condition) {
statements;
}
用for循环来重复执行一些代码的好处是循环控制结构更加清晰。与循环有关的所有内容都包含在for语句的圆括号部分。
我们可以把上一小节里的do循环例子改写为如下所示的for循环:
for (var count = 1; count < 11; count ++) {
alert(count);
}
与循环有关的所有内容都包含在for语句的圆括号里。现在,当我们把一些代码放在花括号中间的时候,我们清楚地知道那些代码将被执行10次。
for循环最常见的用途之一是对某个数组里的全体元素进行遍历处理。这往往需要用到数组的array.length属性,这个属性可以告诉我们在给定数组里的元素的个数:
var beatles = Array("John","Paul","George","Ringo");
for (var count = 0; count < beatles.length; count++) {
alert(beatles[count]);
}
运行这段代码,你们将看到4条alert消息,它们分别对应着Beatles乐队的四位成员
函数
使用 function 关键字来定义一个函数:
function name(arg1, arg2, ...) {
JavaScript statements...
}
函数名必须符合变量名的命名规则,调用函数的时候,使用函数名以及函数需要的参数即可:
var w = 5;
var h = 3;
setDimensions(w, h);
正如我们前边所讲的,函数最好是放在 <HEAD>.....</HEAD> 标记对之间,那样可以保证当任何语句调用函数的时候函数已经被下载到客户端了,这样才不会出错,否则可能会产生找不到函数之类的错误!
在函数中可以使用 return 语句返回一些值,例如:
function add(x, y) {
return x + y;
}
var total = add(7, 10);
文档对象与Windows对象
1. 对象简介
什么是对象?
顾名思义,JavaScript 是居于 Java 程序设计语言而建立起来的脚本语言,两者都是面向对象的 (object-oriented),我们并不想深入探讨面向对象程序设计 (OOP),但是为了你能够更好地了解 JavaScript 语言,我们希望你能够知道一些与对象有关的术语和基本概念。
JavaScript 所处理的每一个对象都是属于一个类 (class) ,类里边定义了组成对象的数据、属性、方法(即是类里边的一些函数)等,使用类可以增加程序代码的复用性,这在一种程序语言中是很重要的,因为这样可以避免重复开发!
你可以将字符串、整数等数据类型看作是一个对象,当你定义一个变量的时候,你就创建了一个对象实例 (通常简称 “对象”),你可以创建很多对象实例,使它们都属于同一个类,也即是相同的对象,比如说,“人”是一个对象(类),而每一个人都是“人”这个对象(类)中的一个对象实例。创建一个对象实例时使用 new 运算符:
var text = new String("This is my text.");
var max = new Number(4.02);
var today = new Date(); // Date() 是 JavaScript 的内建对象类
new 运算符用来调出对象的数据结构(包括对象的属性、方法),这是用来创建一个对象实例的方法,同时还对对象实例的属性进行初始化。有一些对象 (例如 Date 对象) 拥有多种数据结构,那到底 JavaScript 会为你创建哪一种数据结构呢?这就要看你在使用 new 创建对象实例的时候传的是什么参数了。就以 Date 对象为例吧,如果在创建时不传递任何参数,则会创建一个当前系统日期时间的对象实例,就像上边的例子中的一样;但是如果你在创建对象实例时向 Date 类传递了 year(年)、month(月)、date(日) 和 time (时间) 的值的话,JavaScript 将会根据这些值为你创建一个对象实例,而不再是当前系统时间的对象实例。
一个对象既可以是 core 核心对象 (下边将会提到),又可以是另外一些预定义对象 (例如一个图片 Image 对象),甚至可以是你创建的一个全新的对象。
对象的属性 (Propertie)
在 JavaScript 中使用英文句号 (.) 来访问对象的属性值:
var text = new String("A long time ago...");
var len = text.length;
// len 为字符串对象 text 的字符串长度
//length 为字符串对象的字串长度属性。
通常情况下,你可以直接改变某个对象的属性,例如,为了动态的改变页面的背景颜色,你可以直接通过动态地改变 document 对象的 bgColor 属性来实现,请看 在线示例 。
然而,有一些属性你是不能直接修改的,例如字符串对象 String 的 length 属性,但是如果你使用 String 对象的 concat() 方法合并两个字符串的话,length 属性也就会自动的改变了,但这种改变是系统自动实现的,而不是你直接通过赋值改变的。
对象的方法 (Method)
同样的,对象的方法也是通过英文句号 (.) 来执行的,在方法后边紧跟一对圆括号(),而在圆括号里边包含了使用该方法所需要的参数:
var errorMsg = new String("");
var msgHeader = new String("Error: ");
var errorCode = new String("X001");
errorMsg = msgHeader.concat(errorCode);
此例执行的结果:变量 errorMsg 的值为 "Error: X001"。
JavaScript 中的对象
JavaScript 包含很多预定义对象,一些是可以用来表示不同的数据类型 (如数字和字符串对象),而另一些是在网页中定义的与 HTML 标记密切相关的对象 (如超链接和图片对象)。
核心 (Core) 对象
在 JavaScript 中可供使用的最基本的数据类型被称为核心 (core) 对象,这些对象是:
Array - 数组对象;
Boolean - 布尔对象;
Date - 时间对象;
Number - 数值对象,可以是整数和浮点数;
String - 字符串对象,可以是单引号 (') 或双引号 (") 括起来的任何字符串。
对于上边给出的对象,你都可以使用 new 操作符创建相应的对象实例,其实你也可以隐含地声明/创建 Boolean、Number 和 String 这几个对象,就像下边那样:
var found = false;
var max = 47.9;
var name = "Fred"; // 等价于:var name = new String("Fred");
2. 文档中的对象模块
DOM中的文档
DOM是“Document Object Model”(文档对象模型)的首字母缩写。如果没有document(文档),DOM也就无从谈起。当创建了一个网页并把它加载到Web浏览器中时, DOM就在幕后悄然而生。它将根据你编写的网页文档创建一个文档对象。
在人类语言中,“对象”这个词的含义往往不那么明确和具体,它几乎可以用来称呼任何一种客观存在的事物。但在程序设计语言中,“对象”这个词的含义非常明确和具体。
DOM中的对象
JavaScript语言里的对象可以分为三种类型:
* 用户定义对象(user-defined object):由程序员自行创建的对象。本书不讨论这种对象。
* 内建对象(native object):内建在JavaScript语言里的对象,如Array、Math和Date等。
* 宿主对象(host object):由浏览器提供的对象。
在JavaScript语言的发展初期,程序员在编写JavaScript脚本时经常需要用到一些非常重要的宿主对象,它们当中最基础的是window对象。
window对象对应着浏览器窗口本身,这个对象的属性和方法通常被统称为BOM(浏览器对象模型)——但我觉得称之为Window Object Model(窗口对象模型)更为贴切。BOM向程序员提供了window.open和window.blur等方法,你们在上网冲浪时看到的各种弹出窗口和下拉菜单——其数量之多已经到了泛滥成灾的地步——几乎都是由这些方法负责创建和处理的。难怪JavaScript会有一个不好的名声!
值得庆幸的是,在这本书里我们不需要与BOM打太多的交道。我们将把注意力集中在浏览器窗口的内部而不是浏览器窗口本身。我们将着重探讨如何对网页的内容进行处理,而用来实现这一目标的载体就是document对象。
在本节的后续内容里,我们将尽可能地只讨论document对象的属性和方法。
现在,我们已经对DOM中的字母“D”(document,文档)和字母“O”(object,对象)做了解释,那么字母“M”又代表着什么呢?
DOM中的模型
DOM中的“M”代表着“Model”(模型),但说它代表着“Map”(地图)也未尝不可。模型也好,地图也罢,它们的含义都是某种事物的表现形式。就像一个模型火车代表着一列真正的火车、一张城市街道图代表着一个实际存在的城市那样,DOM代表着被加载到浏览器窗口里的当前网页:浏览器向我们提供了当前网页的地图(或者说模型),而我们可以通过JavaScript去读取这张地图。
既然是地图,就必须有诸如方向、等高线和比例尺之类的记号。要想看懂和使用地图,就必须知道这些记号的含义和用途——这个道理同样适用于DOM。要想从DOM获得信息,我们必须先把各种用来表示和描述一份文档的记号弄明白。
DOM把一份文档表示为一棵树(这里所说的“树”是数学意义上的概念),这是我们理解和运用这一模型的关键。更具体地说,DOM把文档表示为一棵家谱树。
家谱树本身又是一种模型。家谱树的典型用法是表示一个人类家族的谱系并使用parent(父)、child(子)、sibling(兄弟)等记号来表明家族成员之间的关系。家谱树可以把一些相当复杂的关系简明地表示出来:一位特定的家族成员既是某些成员的父辈,又是另一位成员的子辈,同时还是另一位成员的兄弟。
类似于人类家族谱系的情况,家谱树模型也非常适合用来表示一份用(X)HTML语言编写出来的文档。
请看下面这份非常基本的网页,它的内容是一份购物清单。
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>购物清单</title>
</head>
<body>
<h1>您都买了??</h1>
<p title="清单">请不要忘记结帐</p>
<ul id="purchases">
<li>一斤牛肉</li>
<li>一瓶啤酒</li>
<li>两个面包</li>
</ul>
</body>
</html>
这份文档可以用下图的模型来表示。
我们来分析一下这个网页的结构。这种分析不仅可以让我们了解它是由哪些元素构成的,还可以让我们了解为什么上图中的模型可以如此完美地把它表示出来。在对Doctype做出声明后,这份文档首先打开了一个<html>标签,而这个网页里的所有其他元素都包含在这个元素里。因为所有其他的元素都包含在其内部,所以这个<html>标签既没有父辈,也没有兄弟。如果这是一棵真正的树的话,这个<html>标签显然就是树根。
如果在这份文档里更深入一层,我们将发现<head>和<body>两个分枝。它们存在于同一层次且互不包含,所以它们是兄弟关系。它们有着共同的父元素<html>,但又各有各的子元素,所以它们本身又是其他一些元素的父元素。
<head>元素有两个子元素:<meta>和<title>(这两个元素是兄弟关系)。< body>元素有三个子元素:<h1>、<p>和<ul>(这三个元素是兄弟关系)。如果继续深入下去,我们将发现<ul>也是一个父元素。它有三个子元素,它们都是<li>元素。
利用这种简单的家谱关系记号,我们可以把各元素之间的关系简明清晰地表达出来。
例如,<h1>和<ul>之间是什么关系?答案是它们是兄弟关系。
那么<body>和<ul>之间又是什么关系?<body>是<ul>的父元素,<ul>是<body>的一个子元素。
如果把各种文档元素想像成一棵家谱树上的各个节点的话,我们就可以用同样的记号来描述DOM。不过,与使用“家谱树”这个术语相比,把一份文档称为一棵“节点树”更准确。
节点(node)这个名词来自网络理论,它代表着网络中的一个连接点。网络是由节点构成的集合。
在现实世界里,一切事物都由原子构成。原子是现实世界的节点。但原子本身还可以进一步分解为更细小的亚原子微粒。这些亚原子微粒同样是节点。
DOM也是同样的情况。文档也是由节点构成的集合,只不过此时的节点是文档树上的树枝和树叶而已。在DOM里存在着许多不同类型的节点。就像原子包含着亚原子微粒那样,有些DOM节点类型还包含着其他类型的节点。
在描述刚才那份“购物清单”文档时,我们使用了诸如<body>、<p>和<ul>之类的元素。如果把Web上的文档比作一座大厦,元素就是建造这座大厦的砖块,这些元素在文档中的布局形成了文档的结构。各种标签提供了元素的名字。文本段落元素的名字是“p”,无序清单元素的名字是“ul”,列表项元素的名字是“li”。
元素可以包含其他的元素。在我们的“购物清单”文档里,所有的列表项元素都包含在一个无序清单元素的内部。事实上,没有被包含在其他元素里的唯一元素是<html>元素。它是我们的节点树的根元素。
元素只是不同节点类型中的一种。如果一份文档完全由一些空白元素构成,它将有一个结构,但这份文档本身将不会包含什么内容。在网上,内容决定着一切,没有内容的文档是没有任何价值的,而绝大多数内容都是由文本提供的。
在“购物清单”例子里,<p>元素包含着文本“Don’t forget to buy this stuff.”。它是一个文本节点(text node)。在XHTML文档里,文本节点总是被包含在元素节点的内部。但并非所有的元素节点都包含有文本节点。在“购物清单”文档里,<ul>元素没有直接包含任何文本节点——它包含着其他的元素节点(一些<li>元素),后者包含着文本节点。
还存在着其他一些节点类型。例如,注释就是另外一种节点类型。但我们这里还想向大家再多介绍一种节点类型。
元素都或多或少地有一些属性,属性的作用是对元素做出更具体的描述。例如,几乎所有的元素都有一个title属性,而我们可以利用这个属性对包含在元素里的东西做出准确的描述:
<p title="a gentle reminder">Dont't forget to buy this stuff.</p>
并非所有的元素都包含着属性,但所有的属性都会被包含在元素里。
一个元素节点,它包含着一个属性节点和一个文本节点
getElementById()方法
DOM提供了一个名为getElementById()的方法,这个方法将返回一个与那个有着给定id属性值的元素节点相对应的对象。请注意, JavaScript语言区分字母的大小写情况,所以大家在写出“getElementById”时千万不要把大小写弄错了。如果把它错写成“GetElementById”或“getElementbyid”,你将无法得到你真正想要的东西。
这个方法是与document对象相关联的函数。在脚本代码里,函数名的后面必须跟有一组圆括号,这组圆括号包含着函数的参数。getElementById()方法只有一个参数:你想获得的那个元素的id属性值,这个id值必须放在单引号或双引号里。
document.getElementById(id)
下面是一个例子:
document.getElementById("purchases")
这个调用将返回一个对象,这个对象对应着document对象里的一个独一无二的元素,那个元素的HTML id属性值是purchases。再说一遍,getElementById()方法将返回一个对象。你们可以用typeof操作符来验证这一点。 typeof操作符可以告诉我们它的操作数是不是一个字符串、数值、函数、布尔值或对象。
下面是把一些JavaScript语句插入到前面给出的“购物清单”文档之后得到的一份代码清单,新增的代码(黑体字部分)出现在< /body>结束标签之前。顺便说一句,我本人并不赞成把JavaScript代码直接嵌入一份文档的做法,但它不失为一种简便快捷的测试手段:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>购物清单</title>
</head>
<body>
<h1>您都买了??</h1>
<p title="清单">请不要忘记结帐</p>
<ul id="purchases">
<li>一斤牛肉</li>
<li>一瓶啤酒</li>
<li>两个面包</li>
</ul>
<script type="text/javascript">
alert(typeof document.getElementById("purchases"));
</script>
</body>
</html>
把上面这些代码保存为一个XHTML文件。当在你们的Web浏览器里加载这个XHTML文件时,屏幕上将弹出一个如下所示的alter对话框,它向你们报告document.getElementById ("purchases")的类型——它是一个对象。不仅如此,如果用上述办法去检查其他元素节点的类型,你们也会看到类似的alert对话框。
事实上,文档中的每一个元素都对应着一个对象。利用DOM提供的方法,我们可以把与这些元素相对应的任何一个对象筛选出来。
一般来说,我们用不着为文档里的每一个元素都分别定义一个独一无二的id值;那也太小题大做了。要知道,即使某个元素没有独一无二的id值,我们也可以利用DOM提供的另一个方法把与之对应的对象准确无误地筛选出来。
getElementsByTagName()方法
getElementsByTagName()方法将返回一个对象数组,每个对象分别对应着文档里有着给定标签的一个元素。类似于getElementById(),这个方法也是只有一个参数的函数,它的参数是(X)HTML标签的名字:
element.getElementsByTagName(tag)
它与getElementById()方法有许多相似之处,但有一点要特别提醒大家注意:getElements- ByTagName()方法返回的是一个数组;你们在编写脚本时千万注意不要把这两个方法弄混了。
下面是一个例子:
document.getElementsByTagName("li")
这个调用将返回一个对象数组,每个对象分别对应着document对象中的一个列表项(li)元素。与任何其他的数组一样,我们可以利用length属性查出这个数组里的元素个数。
首先,在上一小节给出的XHTML示例文档里把<script>标签中的alter语句替换为下面这条语句:
alert(document.getElementsByTagName("li").length)
然后,用浏览器里重新加载那个XHTML文件,你们就会看到这份示例文档里的列表项元素的个数:3。这个数组里的每个元素都是一个对象。可以通过利用一个循环语句和typeof操作符去遍历这个数组的办法来验证这一点。例如,你们可以试试下面这个for循环:
for (var i = 0; i < document.getElementsByTagName("li").length; i++) {
alert(typeof document.getElementsByTagName("li")[i]);
}
请注意,即使在整个文档里只有一个元素有着给定的标签名,getElementsByTagName()方法也将返回一个数组。此时,那个数组的长度是1。
或许早就觉得用键盘反复敲入document.getElementsByTagName("li")是件很麻烦的事情,而这些长长的字符串会让代码变得越来越难以阅读。有个简单的办法可以减少不必要的打字量并改善代码的可读性:只要把 document.getElementsByTagName("li")赋值给一个变量即可。
请在上一小节给出的XHTML示例文档里把<script>标签中的alter语句替换为下面这些语句:
var item = document.getElementsByTagName("li")
for (var i = 0; i < item.length; i++) {
alert(typeof item[i]);
}
现在,当用浏览器里重新加载那个XHTML文件时,你们将看到三个alert对话框,显示在这三个alert对话框里的消息都是“object”。
getElementsByTagName()方法允许我们把一个通配符当为它的参数,而这意味着文档里的每个元素都将在这个函数所返回的数组里占有一席之地。通配符(星号字符“*”)必须放在引号里,这是为了让通配符与乘法操作符有所区别。如果你想知道某份文档里总共有多少个元素节点,像下面这样以通配符为参数去调用getElementsByTagName()方法是最简便的办法:
alert(document.getElementsByTagName("*").length);
我们还可以把getElementById()和getElementsByTagName()方法结合起来运用。例如,我们刚才给出的几个例子都是通过document对象调用getElementsByTagName()方法的,如果只想知道其id属性值是purchase的元素包含着多少个列表项(li)的话,你就必须通过一个更具体的对象去调用这个方法,如下所示:
var shopping = document.getElementById("purchases");
var item = shopping.getElementsByTagName("*");
在这两条语句执行完毕后,items数组将只包含其id属性值是purchase的无序清单里的元素。具体到我们这个例子,items数组的长度刚好与这份文档里的列表项元素的总数相等:
如果还需要更多的证据,下面这些语句将证明items数组里的每个值确实是一个对象:
for (var i = 0; i < item.length; i++) {
alert(typeof item[i]);
}
window 对象
当页面已经被下载到客户端时,浏览器就会创建几个用来存储页面信息和页面内容的对象。这些对象构成了一个树状结构 (对象的引用也是根据这种树一层一层引用的,引用时使用 “.” 号),在这棵“树”中,最高层的对象是 window 对象。
我们在调试 JavaScript 脚本代码的时候经常会使用 alert() 方法,你可以将这个函数放在你的代码中,让它显示出所要调试的变量或对象的值,这对于在调试代码时跟踪错误是很重要的。
window 提供的另一些方法是打开、关闭窗口或者改变窗口的显示方式,我们将在第三部分讲到这几个方法的使用。
另外,window 对象中还有一个经常被用到的方法:setTimeout() 方法。此方法可以让 JavaScript 每隔一段间隔时间执行一段代码,代码的执行遵循事件句柄中的规则,也就是说,在这个函数中,每隔一段时间就会执行几条 JavaScript 代码 (每条代码使用“;”隔开) 或者执行调用的函数:
setTimeout("count++; alert('Hello world!');", 500)
上边的代码将会每隔半秒钟 (500毫秒) 执行一次 “count++; alert('Hello world')” 代码,此代码包含了两条 JavaScript 语句。也即是说,setTimeout() 方法有两个参数,第一个参数是想要执行的代码或函数 (在此例中是代码:count++; alert('Hello world')),第二个函数是间隔时间,单位是毫秒 (在此例中是500毫秒)。
setTimeout() 方法的返回值是一个唯一的数值,这个数值有什么用呢?如果你想要终止 setTimeout() 方法的执行,那就必须使用 clearTimeout() 方法来终止,而使用这个方法的时候,系统必须知道你到底要终止的是哪一个 setTimeout() 方法 (因为你可能同时调用了好几个 setTimeout() 方法),这样 clearTimeout() 方法就需要一个参数,这个参数就是 setTimeout() 方法的返回值 (数值),用这个数值来唯一确定结束哪一个 setTimeout() 方法:
var id = setTimeout("myFunction(arg1, arg2);", 3000);
加一个ClearTimeout的例子
document 对象
document 对象可能将会是你使用的最多的对象之一。此对象包含了与页面内容相关的几个属性,请看:
bgColor - 页面的背景色;
fgColor - 页面的前景色,即文本的颜色;
linkColor - 超文本链接的颜色;
lastModified - 此页面 (HTML 文件) 最后被修改的时间;
images - 图片对象组成的数组,数组中的每一个元素对应于网页中的每一个
<IMG> 标记,数组元素对应的顺序是在 HTML 文件代码中标记出现的先后顺序;
forms - 表单 (form) 对象组成的数组,数组中的每一个元素对应于网页中的每一个 <FORM> 标记,数组元素对应的顺序是在 HTML 文件代码中标记出现的先后顺序;
links - 超文本链接对象组成的数组,数组中的每一个元素对应于网页中的每一个 <A> 标记,数组元素对应的顺序是在 HTML 文件代码中标记出现的先后顺序;
通常情况下,这些属性对应的是 HTML 标记的属性值,例如 bgColor 属性对应的是 <BODY> 标记中 BGCOLOR="..." 代码中的属性值,你会发现,在其它标记 (像图片和超文本链接) 中也是这样对应的。
images、forms 和 links 属性实际上都是数组,对于数组中的元素,通常会使用像下边的语句那样访问之:
var abc = document.images[3];
这样变量 abc 指向的是网页中的第四个 (因为在 JavaScript 中数租下标是从 0 开始的) <IMG> 标记对应的图片对象,这样用起来其实并不方便,因为如果图片太多,你就必须知道每一个图片在网页中排在第几,要不然就无法知道数组的下标应该是几。不过不用担心, JavaScript 提供了一个方便的方法让你指向图片对象,那就是通过各个图片元素的名字:
var abc = document.images["menu"];
要使用这种方法,你就必须在 <IMG> 标记中加入 NAME= "图片的英文名" 的代码,此例中,<IMG> 标记的代码为:<IMG NAME="menu" SRC="test.gif">,这样你就再也不用担心你要引用的图片对象在网页中的排位顺序了,只要给它取个名字就可以了。 (在 Netscape 3.0 中,这种给图片命名的方法并不好用,可能会的不到正确的结果,这也是 Netscape 3.0 的一个 bug。)
在使用这种方法给网页中的图片对象取名子的时候,千万注意不要有重名的情况,否则就得不到正确结果了。此外,你还可以使用下边的代码一样代替上边的代码:
var abc = document.menu; // 这条语句和 var abc = document.images["menu"]; 是等价的
【图片对象 Image】
关于一个图片的信息可以保存在一个叫 Image 的对象中,此对象包含了图片路径 (URL)、图片当前的下载状态、图片的高度和宽度等信息,通常情况下你会将此对象指向在 document.images 数组中存在的图片,也就是放在网页中的图片,但是有时候你可能要处理一些不在网页中的图片对象,这时候 Image 对象就派上用场了。
当你要实现图片翻滚效果的时候,提前将你想要使用的图片下载到客户端是一个很好的想法,因为这样的话,当用户触发事件,要换图片的时候,那个图片已经在客户端了,于是图片就会马上显示出来,避免了时间的延迟,否则换图的时候再让浏览器从服务器上下载图片的话,图片翻滚就有时间延迟了。而使用 Image 对象就可以做到提前下载图片的目的,如下边的代码一样,使用 Image 对象的 src 属性指定图片的路径 (URL),这样就将 images 目录下的图片 pic2.gif 下载到客户端了:
var myImg = new Image();
myImg.src = "images/pic2.gif";
这段代码将迫使浏览器开始从服务器下载指定的图片,如果客户端的缓存 (Cache) 中有这个图片的话,浏览器会自动将其覆盖,那样,当用户将鼠标移动到图片上边的时候,图片将会立即变换,不会有时间的延迟。
看看改进后的图片翻滚的 在线示例 吧,此程序使用了 Image 对象预先下载图片的方法。注意:此例不能在 Internet Explorer 3.0 或更早的版本中使用,因为它们不支持。
【表单对象 form】
form 对象自身用的并不是很多,但是 form 对象包含了很多别的元素对象,这些对象组成了表单 form。这些对象包括:文本框对象 (text)、密码框对象 (password)、单选框对象 (radio button)、复选框对象 (check box)、多行文本域 (text area)、隐藏域 (hidden field)、选择列表/下拉列表 (selection list option)、提交按钮 (submit button)、重置按钮 (reset button),它们对应的 HTML 标记可以参照相关的 HTML 语言参考。
这些对象都有共同的属性 name 和 value,value 属性是用户输入的数据或 HTML 文件给定的值,在 HTML 标记中,这两个属性分别对应的代码是 NAME="..." 和 VALUE="...."。
在使用 form 里的那些元素对象之前,首先你必须要引用 form 对象才行,因为 form 对象是那些对象的父对象,而 form 对象前边也要引用它的上一层父对象 document,这种层层引用是很重要的:
document.forms[0]
// 指向页面中的第一个 form 对象
document.forms["order"]
// 指向名为 order 的 form 对象,即<FORM>标记中包含有代码 NAME="order" 的表单
document.order
// 这和上一行的代码是等价的
类似的,你可以通过表单 form 对象的 elements 数组访问每一个 input 对象,同时也可以使用表单中各个元素对象的名字来访问这些对象。input 对象的名字也是通过 <INPUT> 标记中的 NAME="..." HTML 代码来命名的:
document.forms[0].elements[3]
// 第一个表单 form 中的第三个 input 元素
document.forms["order"].elements["address"]
// 指向名为 order 的表单中名为 address 的对象
document.order.address
// 指向名为 order 的表单中名为 address 的对象,和上一行代码是一样的
通常我们使用表单对象来检查用户的输入是否正确或者用户提交的数据是否正确,这种检查是在表单数据被提交到服务器之前进行的,这样做可以节省用户填表的时间,保证用户只提交一次表单。请看 在线示例 ,此例中演示了如何使用 form 和 form 里边的其它对象来检查用户输入的。
此例中一些需要注意的地方:
我们在 <form> 标记中使用了 onSubmit 事件,此事件在用户按下“提交”按钮的时候,调用函数 checkForm() 来检查用户输入;
在函数 checkForm() 中,无论表单中的哪一项没有填写,都会弹出一个带有错误信息的消息框,并使用 focus() 方法将鼠标的焦点放在相应的输入框 (input) 中;
如果表单中有项目没有填写,checkForm() 函数会返回 false 值,而 onSubmit() 函数在获得 false 值之后,不执行提交表单内容的动作。如果返回致使 true,onSubmit() 则会执行提交的动作。
【history 对象】
history 对象是一个很有用的对象,此对象记录着浏览器所浏览过的每一个页面,这些页面组成了一个历史记录列表。history 对象具有两种方法:
forward() - 将历史记录向前移动一个页面;
back() - 将历史记录向后移动一个页面;
而还有一个 go() 方法也可以控制历史纪录,而且功能更加强大,使用此方法需要一个参数,这个参数值可以是正负整数、零和字符串,例如 history.go(-2) 将是当前页后退两页;如果给定的参数是字符串,那么浏览器就会搜索列表,找到最接近当前页面位置的并且地址 URL 中含有此字符串的页面,然后转到该页面。
请看,下边的两条语句是等价的:
history.go(-1);
history.back();
下边的代码将页面转到距离本页面位置最近的同时页面地址 URL 中含有字符串 "netscape" (不区分字母的大小写) 的页面:
history.go("netscape");
在使用这三个方法的时候,如果没有找到符合条件的历史记录的话,将不会发生任何变化,不会改变当前的页面,也不会显示错误。
小技巧:如果在你的网站中有很多页面,那么提供一个 “返回” 功能是很必要的,这样可以方便用户浏览你的网站,但是你并不知道用户是从哪一个页面来到当前页面的,于是你就不能使用 <A HREF="页面的地址">...</A> 的方式来做超链接了,但是可以使用下边的代码来做 “返回” 的超链接:
<a href="#" onClick="history.back(); return false;">返回</a>
改变窗口页面的内容
你可以使用 window 对象的 scroll() 方法使窗口/帧的内容滚动,这种滚动可以是水平方向的也可以是垂直方向的,在这个 在线示例 中,帧里边的美国各个城市天气变化的内容将会不停的滚动。
此例中,当页面滚动到一定的值 (此例中为 750 像素) 的时候,又将页面重新设置成最初的样子 (即坐标值为:(0,0)),这样看起来就是不停的滚动了。可惜的是,并不是所有的浏览器都提供实际的页面高和宽的像素值,所以你要亲自实践一下看看这些值要多大才合适。
你还可以重新书写一个页面中的内容,这就要用到 document 对象的 write() (此方法输出的时候不换行) 和 writeln() (此方法输出完成后自动换行)方法了。这两个方法需要一个字符串参数,这个参数就是要在页面中输出的字符串,可以在字符串中带上HTML代码,那样输出的结果就和其它网页是一样的了,看看这个 在线示例 ,查看一下源代码就知道了。这个例子中是上边的帧通过 writeln() 方法向下边的帧输出内容。
在此例中,你可以试一试输入一些 HTML 代码,看看有什么效果!比如说输入下边的代码:
<script>
alert("Hi mom!");
</script>
在下边帧上,点击鼠标右键,查看一下源代码,你会发现源代码就是你输入的那些字符。
在函数 rewriteFrame() 中,有如下语句:
window.parent.frames["text"].document.writeln(document.forms[0].text.value);
window.parent.frames["text"].document.close();
第一条语句是将表单 (form) 的 text 中输入的内容写到下边的帧中,第二行代码是调用 document.close() 的方法告诉浏览器向页面写的操作已经结束。如果你想要是你的输入覆盖掉以前的,那就要使用 document 对象的 close() 方法。