第 2 章 JavaScript语法

第 2 章 JavaScript语法

本章内容

  • 语句
  • 变量和数组
  • 操作符
  • 条件语句和循环语句
  • 函数与对象

本章将简要复习一下JavaScript语法,并介绍其中最重要的一些概念。

2.1 准备工作

编写JavaScript脚本不需要任何特殊的软件,一个普通的文本编辑器和一个Web浏览器就足够了。

用JavaScript编写的代码必须通过HTML/XHTML文档才能执行。有两种方式可以做到这点。第一种方式是将JavaScript代码放到文档<head>标签中的<script>标签之间:

<!DOCTYPE html >
<html lang="en">
<head>
  <meta charset="utf-8"/>
  <title>Example</title>
  <script>
    JavaScript goes here...
  </script>
</head>
<body>
  Mark-up goes here...
</body>
</html>

一种更好的方式是把JavaScript代码存为一个扩展名为 .js 的独立文件。典型的作法是在文档的<head>部分放一个<script>标签,并把它的src属性指向该文件:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8"/>
  <title>Example</title>
  <script src="file.js"></script>
</head>
<body>
  Mark-up goes here...
</body>
</html>

但最好的做法是把<script>标签放到HTML文档的最后,</body>标签之前:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8"/>
  <title>Example</title>
</head>
<body>
  Mark-up goes here...
  <script src="file.js"></script>
</body>
</html>

这样能使浏览器更快地加载页面(第5章将详细讨论这个问题)。

注意 前面例子中的<script>标签没有包含传统的type="text/javascript"属性。因为脚本默认是Java Script,所以没必要指定这个属性。

如果打算实践一下本章中的例子,用一个文本编辑器创建两个文件。先创建一个简单的HTML或XHTML文件,保存为诸如test.html之类的名称。这个文件中一定要包含一个<script>标签,这个标签的src属性设置成你创建的第二个文件的名字,比如example.js

你的test.html文件应该包含如下内容:

<!DOCTYPE html >
<html lang="en">
 <head>
  <meta charset="utf-8" />
  <title>Just a test</title>
</head>
<body>
  <script src="example.js"></script>
</body>
</html>

可以把本章中的任何一个示例复制到你的example.js文件中。虽说那些示例没有什么特别令人激动的地方,但它们可以把有关的语法演示得明明白白。

在本书后面的章节里,我们将演示如何使用JavaScript改变文档的行为和内容。但在本章里,我们只使用一个简单的对话框来显示消息。

如果改变了example.js文件的内容,只需在Web浏览器中重新载入test.html文档即可看到效果。Web浏览器会立刻解释并执行你的JavaScript代码。

程序设计语言分为解释型和编译型两大类。Java或C++等语言需要一个编译器(compiler)。编译器是一种程序,能够把用Java等高级语言编写出来的源代码翻译为直接在计算机上执行的文件。

解释型程序设计语言不需要编译器——它们仅需要解释器。对于JavaScript语言,在互联网环境下,Web浏览器负责完成有关的解释和执行工作。浏览器中的JavaScript解释器将直接读入源代码并执行。浏览器中如果没有解释器,JavaScript代码就无法执行。

用编译型语言编写的代码有错误,这些错误在代码编译阶段就能被发现。而解释型语言代码中的错误只能等到解释器执行到有关代码时才能被发现。

与解释型语言相比,编译型语言往往速度更快,可移植性更好,但它们的学习曲线也往往相当陡峭。

JavaScript的优点之一就是相当容易入门,但千万不要因此小看JavaScript,其实它能完成许多相当复杂的编程任务。不过,本章主要介绍它最基本的语法和用法。

2.2 语法

英语是一种解释型语言。在阅读和处理我们用英语写出来的文字时,你就相当于一个英语解释器。只要遵守英语的语法规则,我们想表达的意思就可以被正确地解读。这些语言结构方面的各项规则,我们就称之为“语法”。

如同书面的人类语言,每种程序设计语言也都有自己的语法。JavaScript的语法与Java和C++语言的语法非常相似。

2.2.1 语句

用JavaScript编写的脚本,与其他语言编写出来的脚本一样,都由一系列指令构成,这些指令叫做语句(statement)。只有按照正确的语法编写出来的语句才能得到正确的解释。

JavaScript语句与英语中的句子很相似。它们是构成任何一个脚本的基本单位。

英语语法要求每个句子必须以一个大写字母开头、以一个句号结尾。JavaScript在这方面的要求不那么严格,程序员只需简单地把各条语句放在不同的行上就可以分隔它们,如下所示:

first statement
second statement

如果你想把多条语句放在同一行上,就必须像下面这样用分号来分隔开它们:

first statement; second statement;

我们建议在每条语句的末尾都加上一个分号,这是一种良好的编程习惯:

first statement;
second statement;

这样做让代码更容易阅读。让每条语句独占一行的做法能更容易跟踪JavaScript脚本的执行顺序。

2.2.2 注释

不是所有的语句都需要JavaScript解释器去解释并执行。有时你需要在脚本中写一些仅供自己参考或提醒自己的信息,你希望JavaScript解释器能直接忽略掉这些信息。这类语句就是注释(comment)。

注释能有效帮助你了解代码流程。在代码中它们扮演生活中便条的角色,可以帮助你弄清楚你的脚本到底干了些什么。

有多种方式可以在JavaScript脚本中插入注释。例如,如果用两个斜线作为一行的开始,这一行就会被当成一条注释:

// 自我提醒:有注释是好事

如果使用这种注释方式,就必须在每个注释行的开头加上两个斜线。像下面这样的写法脚本就会出问题:

// 自我提醒:
   有注释是好事

必须把它们写成类似下面这样才行:

// 自我提醒:
// 有注释是好事

如果打算注释很多行,你可以在注释内容的开头加上一个斜线和一个星号(/*),在注释内容的末尾加上一个星号和一个斜线(*/)。下面是一个多行注释的例子:

/* 自我提醒:
   有注释是好事 */

这种注释方式在需要插入大段注释时很有用,它可以提高整个脚本的可读性。

还可以使用HTML风格的注释,但这种做法仅适用于单行注释。其实JavaScript解释器对“<!--”的处理与对“//”的处理是一样的:

<!-- 这是JavaScript 中的注释

如果是在HTML文档中,还需要以“-->”来结束注释,如下所示:

<!-- 这是 HTML 中的注释 -->

但JavaScript不要求这样做,它会把“-->”视为注释内容的一部分。

请注意,HTML允许上面这样的注释跨越多个行,但JavaScript要求这种注释的每行都必须在开头加上“<!--”来作为标志。

因为JavaScript解释器在处理这种风格的注释时与大家所熟悉的HTML做法不同,为避免发生混淆,最好不要在JavaScript脚本中使用这种风格的注释。建议大家用“//”来注释单行,用“/*”注释多行。

2.2.3 变量

在日常生活里,有些东西是固定不变的,有些东西则会发生变化。例如,人的姓名和生日是固定不变的,但心情和年龄却会随着时间变化而变化。人们把那些会发生变化的东西称为变量(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);

图2-1是一个显示mood变量值的例子。

图 2-1

图2-2是一个显示age变量值的例子。

图 2-2

我们会在本书后面的章节中利用变量做一些很有用的事情,别着急。

请注意,JavaScript允许程序员直接对变量赋值而无需事先声明。这在许多程序设计语言中是不允许的。有很多语言要求在使用任何变量之前必须先对它做出“介绍”,也称为声明(declare)。

在JavaScript脚本中,如果程序员在对某个变量赋值之前未声明,赋值操作将自动声明该变量。虽然JavaScript没有强制要求程序员必须提前声明变量,但提前声明变量是一种良好的编程习惯。下面的语句对变量moodage做出了声明:

var mood;
var age;

不必单独声明每个变量,你也可以用一条语句一次声明多个变量:

var mood, age;

你甚至可以一石两鸟:把声明变量和对该变量赋值一次完成:

var mood = "happy";
var age = 33;

甚至还可以像下面这样:

var mood = "happy", age = 33;

像上面这样声明和赋值是最有效率的做法,这一条语句的效果相当于下面这些语句的总和:

var mood, age;
mood = "happy";
age = 33;

在JavaScript语言里,变量和其他语法元素的名字都是区分字母大小写的。名字是mood的变量与名字是MoodMOODmOOd的变量没有任何关系,它们不是同一个变量。下面的语句是在对两个不同的变量进行赋值:

var mood = "happy";
MOOD = "sad";

JavaScript语法不允许变量名中包含空格或标点符号(美元符号“$”例外)。下面这条语句将导致语法错误:

var my mood = "happy";

JavaScript变量名允许包含字母、数字、美元符号和下划线(但第一个字符不允许是数字)。为了让比较长的变量名更容易阅读,可以在变量名中的适当位置插入下划线,就像下面这样:

var my_mood = "happy";

另一种方式是使用驼峰格式(camel case),删除中间的空白(下划线),后面的每个新单词改用大写字母开头:

var myMood = "happy";

通常驼峰格式是函数名、方法名和对象属性名命名的首选格式。

在上面这条语句中,单词“happy”是JavaScript语言中的一个字面量(literal),也就是可以直接在JavaScript代码中写出来的数据。文本“happy”除了表示它自己以外不表示任何别的东西,正如大力水手Popeye的名言:“它就是它!”与此形成对照的是,单词“var”是一个关键字,my_mood是一个变量名字。

2.2.4 数据类型

变量mood的值是一个字符串,变量age的值则是一个。虽然它们是两种不同类型的数据,但在JavaScript中对这两个变量进行声明和赋值的语法却完全一样。有些其他的语言要求在声明变量的同时还必须同时声明变量的数据类型,这种做法称为类型声明(typing)。

必须明确类型声明的语言称为强类型(strongly typed)语言。JavaScript不需要进行类型声明,因此它是一种弱类型(weakly typed)语言。这意味着程序员可以在任何阶段改变变量的数据类型。

以下语句在强类型语言中是非法的,但在JavaScript里却完全没有问题:

var age = "thirty three";
age = 33;

JavaScript并不在意变量age的值是一个字符串还是一个

接下来,我们一起来复习一下JavaScript中最重要的几种数据类型。

  1. 字符串

    字符串由零个或多个字符构成。字符包括(但不限于)字母、数字、标点符号和空格。字符串必须包在引号里,单引号或双引号都可以。下面这两条语句含义完全相同:

    var mood = 'happy';
    var mood = "happy";

    你可以随意选用引号,但最好是根据字符串所包含的字符来选择。如果字符串包含双引号,就把整个字符串放在单引号里;如果字符串包含单引号,就把整个字符串放在双引号里:

    var mood = "don't ask";

    如果想在上面这条语句中使用单引号,就必须保证字母“n”和“t”之间的单引号能被当成这个字符串的一部分。这种情况下这个单引号需要被看做一个普通字符,而不是这个字符串的结束标志。这种情况我们需要对这个字符进行转义(escaping)。在JavaScript里用反斜线对字符进行转义:

    var mood = 'don\'t ask';

    类似地,如果想用双引号来包住一个本身就包含双引号的字符串,就必须用反斜线对字符串中的双引号进行转义:

    var height = "about 5'10\" tall";

    实际上这些反斜线并不是字符串的一部分。你可以自己去验证一下:把下面这段代码添加到你的example.js文件中,然后重新加载test.html文件:

    var height = "about 5'10\" tall";
    alert(height);

    图2-3是用反斜线对有关字符转义的一个屏幕输出示例。

    图 2-3

    我个人比较喜欢用双引号来包住字符串。作为一个好的编程习惯,不管选择用双引号还是单引号,请在整个脚本中保持一致。如果在同一个脚本中一会儿使用双引号,一会儿又使用单引号,代码很快就会变得难以阅读和理解。

  2. 数值

    如果想给一个变量赋一个数值,不用限定它必须是一个整数。JavaScript允许使用带小数点的数值,并且允许任意位小数,这样的数称为浮点数(floating-point number):

    var age = 33.25;

    也可以使用负数。在有关数值的前面加上一个减号(-)表示它是一个负数:

    var temperature = -20;

    JavaScript也支持负数浮点数:

    var temperature = -20.33333333

    以上是数值数据类型的例子。

  3. 布尔值

    另一种重要的数据类型是布尔(boolean)类型。

    布尔数据只有两个可选值——truefalse。假设需要这样一个变量:如果我正在睡觉,这个变量将存储一个值;如果我没有睡觉,这个变量将存储另一个值。可以用字符串数据类型把变量赋值为“sleeping”或“not sleeping”,但使用布尔数据类型显然是一个更好的选择:

    var sleeping = true;

    从某种意义上讲,为计算机设计程序就是与布尔值打交道。作为最基本的事实,所有的电子电路只能识别和使用布尔数据:电路中有电流或是没有电流。不管是使用术语true和 false、yes 和 no或者1和0,重要的是只能取两种可取值中的一种。

    布尔值不是字符串,千万不要把布尔值用引号括起来。布尔值false与字符串值"false"是两码事!

    下面这条语句将把变量married设置为布尔值true

    var married = true;

    下面这条语句把变量married设置为字符串”true”:

    var married = "true";

2.2.5 数组

字符串、数值和布尔值都是标量(scalar)。如果某个变量是标量,它在任意时刻就只能有一个值。如果想用一个变量来存储一组值,就需要使用数组(array)。

数组是指用一个变量表示一个值的集合,集合中的每个值都是这个数组的一个元素(element)。例如,我们可以用名为beatles的变量来保存Beatles乐队全体四位成员的姓名。

在JavaScript中,数组可以用关键字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世界里的一条规则,所以我们只能这么做。人们很容易忘记这一点,很多程序员新手在刚接触数组时经常在这个问题上犯错误。

下面是声明和填充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" );

上面这条语句会为每个元素自动分配一个下标:第一个下标是0,第二个是1,依次类推。因此,beatles[2]仍将对应于取值为“George”的元素。

我们甚至用不着明确地表明我们是在创建数组。事实上,只需用一对方括号把各个元素的初始值括起来就可以了:

var beatles = [ "John", "Paul", "George", "Ringo" ];

数组元素不必非得是字符串。可以把一些布尔值存入一个数组,还可以把一组数值存入一个数组:

var years = [ 1940, 1941, 1942, 1943 ];

甚至可以把这3种数据类型混在一起存入一个数组:

var lennon = [ "John", 1940, false ];

数组元素还可以是变量:

var name = "John";
beatles[0] = name;

这将把beatles数组的第一个元素赋值为“John”。

数组元素的值还可以是另一个数组的元素。下面两条语句将把beatles数组的第二个元素赋值为“Paul”:

var names = [ "Ringo", "John", "George", "Paul" ];
beatles[1] = names[3];

事实上,数组还可以包含其他的数组!数组中的任何一个元素都可以把一个数组作为它的值:

var lennon = [ "John", 1940, false ];
var beatles = [];
beatles[0] = lennon;

现在,beatles数组的第一个元素的值是另外一个数组。要想获得那个数组里的某个元素的值,需要使用更多的方括号。beatles[0][0]的值是“John”,beatles[0][1]的值是1940,beatles[0][2]的值是false

这是一种功能相当强大的存储和获取信息的方式,但如果不得不记住每个下标数字的话(尤其是需要从零开始数的时候),编程工作将是一种非常痛苦和麻烦的体验。幸好还有几种办法可以填充数组。首先看看一种更可读的填充数组的方式,然后介绍存放数据的首选方式:将数据保存为对象。

关联数组

beatles数组是传统数组的典型例子:每个元素的下标是一个数字,每增加一个元素,这个数字就依次增加1。第一个元素的下标是0,第二个元素的下标是1,依次类推。

如果在填充数组时只给出了元素的值,这个数组就将是一个传统数组,它的各个元素的下标将被自动创建和刷新。

可以通过在填充数组时为每个新元素明确地给出下标来改变这种默认的行为。在为新元素给出下标时,不必局限于使用整数数字。你可以用字符串:

var lennon = Array();
lennon["name"] = "John";
lennon["year"] = 1940;
lennon["living"] = false;

这样的数组叫做关联数组。由于可以使用字符串来代替数字值,因而代码更具有可读性。但是,这种用法并不是一个好习惯,不推荐大家使用。本质上,在创建关联数组时,你创建的是Array对象的属性。在JavaScript中,所有的变量实际上都是某种类型的对象。比如,一个布尔值就是一个Boolean类型的对象,一个数组就是一个Array类型的对象。在上面这个例子中,你实际上是给lennon数组添加了nameyearliving三个属性。理想情况下,你不应该修改Array对象的属性,而应该使用通用的对象(Object)。

2.2.6 对象

与数组类似,对象也是使用一个名字表示一组值。对象的每个值都是对象的一个属性。例如,前一节的lennon数组也可以创建成下面这个对象:

var lennon = Object();
lennon.name = "John";
lennon.year = 1940;
lennon.living = false;

与使用Array类似,创建对象使用Object关键字。它不使用方括号和下标来获取元素,而是像任何JavaScript对象一样,使用点号来获取属性。要了解与Object有关的更多内容,请参考本章2.7节。

创建对象还有一种更简洁的语法,即花括号语法:

{ propertyName:value, propertyName:value }

比如,lennon对象也可以写成下面这样:

var lennon = { name:"John", year:1940, living:false };

属性名与JavaScript变量的命名规则有相同之处,属性值可以是任何JavaScript值,包括其他对象。

用对象来代替传统数组的做法意味着可以通过元素的名字而不是下标数字来引用它们。这大大提高了脚本的可读性。

下面,我们将创建一个新的beatles数组,并用刚才创建的lennon对象来填充它的第一个元素。

var beatles = Array();
beatles[0] = lennon;

现在,不需要使用那么多数就可以获得想要的元素。我们不能使用beatles[0][0] 而是使用 beatles[0].name得到值“John”。

在此基础上,还可以做进一步的改进:把beatles数组也声明为对象而不是传统数组。这样一来,我们就可以用“drummer”或“bassist”等更有意义且更容易记忆的字符串值——而不是一些枯燥乏味的整数——作为下标去访问这个数组里的元素了:

var beatles = {};
beatles.vocalist = lennon;

现在,beatles.vocalist.name的值是“John”,beatles.vocalist.year的值是1940,beatles.vocalist.living的值是false。

2.3 操作

此前给出的示例都非常简单,只是创建了一些不同类型的变量而已。要用JavaScript做一些有用的工作,还需要能够进行计算和处理数据。也就是需要完成一些操作(operation)。

算术操作符

加法是一种操作,减法、除法和乘法也是。这些算术操作(arithmetic operation)中的每一种都必须借助于相应的操作符(operator)才能完成。操作符是JavaScript为完成各种操作而定义的一些符号。你其实已经见过一种操作符了,它就是刚才在进行赋值时使用的等号(=)。加法操作符是加号(+),减法操作符是减号(-),除法操作符是斜杠(/),乘法操作符是星号(*)。

下面是一个简单的加法操作:

1 + 4

还可以把多种操作组合在一起:

1 + 4 * 5

为避免产生歧义,可以用括号把不同的操作分隔开来:

1 + (4 * 5)
(1 + 4) * 5

变量可以包含操作:

var total = (1 + 4) * 5;

不仅如此,还可以对变量进行操作:

var temp_fahrenheit = 95;
var temp_celsius = (temp_fahrenheit - 32) / 1.8;

JavaScript还提供了一些非常有用的操作符作为各种常用操作的缩写。例如,如果想给一个数值变量加上1,可以使用如下所示的语句:

year = year + 1;

也可以使用++操作符来达到同样的目的:

year++;

类似地,--操作符将对一个数值变量的值进行减1操作。

加号(+)是一个比较特殊的操作符,它既可以用于数值,也可以用于字符串。把两个字符串合二为一是一种很直观易懂的操作:

var message = "I am feeling " + "happy";

像这样把多个字符串首尾相连在一起的操作叫做拼接(concatenation)。这种拼接也可以通过变量来完成:

var mood = "happy";
var message = "I am feeling " + mood;

甚至可以把数值和字符串拼接在一起。因为JavaScript是一种弱类型语言,所以这种操作是允许的。此时,数值将被自动转换为字符串:

var year = 2005;
var message = "The year is " + year;

请记住,如果把字符串和数值拼接在一起,其结果将是一个更长的字符串;但如果用同样的操作符来“拼接”两个数值,其结果将是那两个数值的算术和。请对比下面两条alert语句的执行结果:

alert ("10" + 20);
alert (10 + 20);

第一条alert语句将返回字符串"1020",第二条alert语句将返回数值30。

图2-4是对字符串"10"和数值20进行拼接的结果。

图 2-4

图2-5是对数值10和数值20进行加法运算的结果。

图 2-5

另一个非常有用的快捷操作符是+=,它可以一次完成“加法和赋值”(或“拼接和赋值”)操作:

var year = 2010;
var message = "The year is ";
message += year;

执行完上面这些语句后,变量message的值将是“The year is 2010”。可以用如下所示的alert对话框来验证这一结果:

alert(message);

这次对字符串和数值进行拼接操作的结果如图2-6所示。

图 2-6

2.4 条件语句

此前介绍的语句都是相对比较简单的声明或运算,而脚本的真正威力体现在它们还可以根据人们给出的各种条件做出决策。JavaScript使用条件语句(conditional statement)来做判断。

在解释脚本时,浏览器将依次执行这个脚本中的各条语句,我们可以在这个脚本中用条件语句来设置一个条件,只有满足了这一条件才能让更多的语句得到执行。最常见的条件语句是if语句,下面是if语句的基本语法:

if (condition) {
  statements;
}

条件必须放在if后面的圆括号中。条件的求值结果永远是一个布尔值,即只能是truefalse。花括号中的语句——不管它们有多少条,只有在给定条件的求值结果是true的情况下才会执行。因此,在下面这个例子中,alert消息永远也不会出现:

if (1 > 2) {
  alert("The world has gone mad!");
}

因为1不可能大于2,所以上面这个条件的值永远是false

在这条if语句中,我们有意把所有的东西都放在花括号里的。这并不是JavaScript的一项语法要求,我们这么做只是为了让代码更容易阅读。

事实上,if语句中的花括号本身并不是必不可少的。如果if语句中的花括号部分只包含着一条语句的话,那就可以不使用花括号,而且这条if语句的全部内容可以写在同一行上:

if (1 > 2) alert("The world has gone mad!");

不过,因为花括号可以提高脚本的可读性,所以在if语句中总是使用花括号是个好习惯。

if语句可以有一个else子句。包含在else子句中的语句会在给定条件为假时执行:

if (1 > 2) {
  alert("The world has gone mad!");
} else {
  alert("All is well with the world");
}

因为给定条件“1>2”的值为假(false),所以我们将看到如图2-7所示的结果。

图 2-7

2.4.1 比较操作符

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,而不是在比较它们是否相等。因为这个赋值操作总会成功1,所以这个条件语句的结果将永远是true

1此处原文有误,赋值运算并非总是返回真值:if(a = false) {alert('hello, world');} 中的alert语句就不会执行。——审校者注

下面才是进行“等于”比较的正确做法:

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.");
}

相等操作符==并不表示严格相等,这一点很容易让人犯糊涂。例如,比较false与一个空字符串会得到什么结果?

var a = false;
var b = "";
if (a == b) {
  alert("a equals b");
}

这个条件语句的求值结果是true,为什么?因为相等操作符==认为空字符串与false的含义相同。要进行严格比较,就要使用另一种等号(===)。这个全等操作符会执行严格的比较,不仅比较值,而且会比较变量的类型:

var a = false;
var b = "";
if (a === b) {
  alert("a equals b");
}

这一次,条件表达式的求值结果就是false了。因为即使可以认为false与空字符串具有相同的含义,但Boolean和String可不是一种类型。

当然,对于不等操作符!=也是如此。如果想比较严格不相等,就要使用!==。

2.4.2 逻辑操作符

JavaScript允许把条件语句里的操作组合在一起。例如,如果想检查某个变量,不妨假设这个变量的名字是num,它的值是不是在5~10之间,我将需要进行两次比较操作。首先,比较这个变量是否大于或等于5;然后,比较这个变量是否小于或等于10。这两次比较操作称为逻辑比较(operand)。下面是把这两个逻辑比较组合在一起的具体做法:

if ( num >= 5 && num <= 10 ) {
  alert("The number is in the right range.");
}

这里使用了“逻辑与”操作符,它由两个“&”字符构成(&&),是一个逻辑操作符。

逻辑操作符的操作对象是布尔值。每个逻辑操作数返回一个布尔值true或者是false。“逻辑与”操作只有在它的两个操作数都是true时才会是true

“逻辑或”操作符由两个垂直线字符构成(||)。只要它的操作数中有一个是true,“逻辑或”操作就将是true。如果它的两个操作数都是true,“逻辑或”操作也将是true。只有当它的两个操作数都是false时,“逻辑或”操作才会是false

if ( num > 10 || num < 5 ) {
  alert("The number is not in the right range.");
}

JavaScript还提供了一个“逻辑非”操作符,它由一个感叹号(!)单独构成。“逻辑非”操作符只能作用于单个逻辑操作数,其结果是把那个逻辑操作数所返回的布尔值取反。如果那个逻辑操作数所返回的布尔值是true,“逻辑非”操作符将把它取反为false

if ( !(1 > 2) ) {
  alert("All is well with the world");
}

请注意,为避免产生歧义,上面这条语句把逻辑操作数放在了括号里,因为我想让“逻辑非”操作符作用于括号里的所有内容。

可以用“逻辑非”操作符把整个条件语句的结果颠倒过来。在下面的例子里,我特意使用了一对括号来确保“逻辑非”操作符将作用于两个逻辑操作数的组合结果:

if ( !(num > 10 || num < 5) ) {
  alert("The number IS in the right range.");
}

2.5 循环语句

if语句或许是最重要、最有用的条件语句了,它的唯一不足是无法完成重复性的操作。在if语句里,包含在花括号里的代码块只能执行一次。如果需要多次执行同一个代码块,就必须使用循环语句。

循环语句可以让我们反复多次地执行同一段代码。循环语句分为几种不同的类型,但它们的工作原理几乎一样:只要给定条件仍能得到满足,包含在循环语句里的代码就将重复地执行下去;一旦给定条件的求值结果不再是true,循环也就到此为止。

2.5.1 while循环

while循环与if语句非常相似,它们的语法几乎完全一样:

while (condition) {
  statements;
}

while循环与if语句唯一的区别是:只要给定条件的求值结果是true,包含在花括号里的代码就将反复地执行下去。下面是一个while循环的例子:

var count = 1;
while (count < 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的值,这个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,尽管循环控制条件的求值结果是false

2.5.2 for循环

for循环来重复执行一些代码也很方便,它类似于while循环。事实上,for循环只是刚才介绍的while循环的一种变体。如果仔细观察上一小节里的while循环的例子,就会发现它们都可以改写为如下所示的样子:

initialize;
while (condition) {
  statements;
  increment;
}

for循环不过是进一步改写为如下所示的紧凑形式而已:

for (initial condition; test condition; alter condition) {
  statements;
}

for循环来重复执行一些代码的好处是循环控制结构更加清晰。与循环有关的所有内容都包含在for语句的圆括号部分。

可以把上一小节里的例子改写为如下所示的for循环:

for (var count = 1; count < 11; count++ ) {
  alert (count);
}

与循环有关的所有内容都包含在for语句的圆括号里。现在,当我们把一些代码放在花括号中间的时候,我们清楚地知道那些代码将被执行10次。

for循环最常见的用途之一是对某个数组里的全体元素进行遍历处理。这往往需要用到数组的array.length属性,这个属性可以告诉我们在给定数组里的元素的个数。一定要记住数组下标从0而不是1开始。下面的例子中,数组有4个元素。count变量对于数组中每个元素都是从0开始按1递增。数到4时,测试条件失败,循环终止,3是从数组中检索到的最后一个下标。

var beatles = Array("John","Paul","George","Ringo");
for (var count = 0 ; count < beatles.length; count++ ) {
  alert(beatles[count]);
}

运行这段代码,将看到4条alert消息,它们分别对应着Beatles乐队的四位成员。

2.6 函数

如果需要多次使用同一段代码,可以把它们封装成一个函数。函数(function)就是一组允许在你的代码里随时调用的语句。事实上,每个函数实际上是一个短小的脚本。

作为一种良好的编程习惯,应该先对函数做出定义再调用它们。

下面是一个简单的示例函数:

function shout() {
  var beatles = Array("John","Paul","George","Ringo");
  for (var count = 0 ; count < beatles.length; count++ ) {
    alert(beatles[count]);
  }
}

这个函数里的循环语句将依次弹出对话框来显示Beatles乐队成员的名字。现在,如果想在自己的脚本里执行这一动作,可以随时使用如下的语句来调用这个函数:

shout();

每当需要反复做一件事时,都可以利用函数来避免重复键入大量的相同内容。不过,函数的真正威力体现在,你可以把不同的数据传递给它们,而它们将使用这些数据去完成预定的操作。我们把传递给函数的数据称为参数(argument)。

定义一个函数的语法:

function name(arguments) {
  statements;
}

JavaScript提供了许多内建函数,在前面多次出现过的alert就是一例。这个函数需要我们提供一个参数,它将弹出一个对话框来显示这个参数的值。

在定义函数时,你可以为它声明任意多个参数,只要用逗号把它们分隔开来就行。在函数的内部,你可以像使用普通变量那样使用它的任何一个参数。

下面是一个需要传递两个参数的函数。如果把两个数值传递给这个函数,这个函数将对它们进行乘法运算:

function multiply(num1,num2) {
  var total = num1 * num2;
  alert(total);
}

在定义了这个函数的脚本里,我们可以从任意位置去调用这个函数,如下所示:

multiply(10,2);

把数值10和2传递给multiply()函数的结果如图2-8所示。

图 2-8

这将产生这样一种视觉效果:屏幕上会立刻弹出一个显示乘法运算结果(20)的alert对话框。如果这个函数能把结果返回给调用这个函数的语句往往会更有用。这很容易做到:函数不仅能够(以参数的形式)接收数据,还能够返回数据。

我们完全可以创建一个函数并让它返回一个数值、一个字符串、一个数组或一个布尔值。这需要用到return语句:

function multiply(num1,num2) {
  var total = num1 * num2;
  return total;
}

下面这个函数只有一个参数(一个华氏温度值),它将返回一个数值(同一温度的摄氏温度值):

function convertToCelsius(temp) {
  var result = temp - 32;
  result = result / 1.8;
  return result;
}

函数的真正价值体现在,我们还可以把它们当做一种数据类型来使用,这意味着可以把一个函数的调用结果赋给一个变量:

var temp_fahrenheit = 95;
var temp_celsius = convertToCelsius(temp_fahrenheit);
alert(temp_celsius);

把华氏温度值95转换为摄氏温度值的结果如图2-9所示。

图 2-9

在这个例子里,变量temp_celsius的值将是35,这个数值由convertToCelsius函数返回。

你一定想了解应该如何命名变量和函数。在命名变量时,我用下划线来分隔各个单词;在命名函数时,我从第二个单词开始把每个单词的第一个字母写成大写形式(也就是所谓的驼峰命名法)。我这么做是为了能够一眼看出哪些名字是变量,哪些名字是函数。与变量的情况一样,JavaScript语言也不允许函数的名字里包含空格。驼峰命名法可以在不违反这一规定的前提下,把变量和函数的名字以一种既简单又明确的方式区分开来。

变量的作用域

前面讲过,作为一种好的编程习惯,在第一次对某个变量赋值时应该用var对其做出声明。当在函数内部使用变量时,就更应该这么做。

变量既可以是全局的,也可以是局部的。在谈论全局变量和局部变量之间的区别时,我们其实是在讨论变量的作用域(scope)。

全局变量(global variable)可以在脚本中的任何位置被引用。一旦你在某个脚本里声明了一个全局变量,就可以从这个脚本中的任何位置——包括函数内部——引用它。全局变量的作用域是整个脚本。

局部变量(local variable)只存在于声明它的那个函数的内部,在那个函数的外部是无法引用它的。局部变量的作用域仅限于某个特定的函数。

因此,我们在函数里既可以使用全局变量,也可以使用局部变量。这很有用,但它也会导致一些问题。如果在一个函数的内部不小心使用了某个全局变量的名字,即使本意是想使用一个局部变量,JavaScript也会认为是在引用那个全局变量。

还好,可以用var关键字明确地为函数变量设定作用域。

如果在某个函数中使用了var,那个变量就将被视为一个局部变量,它只存在于这个函数的上下文中;反之,如果没有使用var,那个变量就将被视为一个全局变量,如果脚本里已经存在一个与之同名的全局变量,这个函数就会改变那个全局变量的值。

我们来看下面这个例子:

function square(num) {
  total = num * num;
  return total;
}
var total = 50;
var number = square(20);
alert(total);

这些代码将不可避免地导致全局变量total的值发生变化,如图2-10所示。

图 2-10

全局变量total的值变成了400。我的本意是让square()函数只把它计算出来的平方值返回给变量number,但因为未把这个函数内部的total变量明确地声明为局部变量,这个函数把名字同样是total的那个全局变量的值也改变了。

把这个函数写成如下所示的样子才是正确的:

function square(num) {
  var total = num * num;
  return total;
}

现在,全局变量total变得安全了,再怎么调用square()函数也不会影响到它。

请记住,函数在行为方面应该像一个自给自足的脚本,在定义一个函数时,我们一定要把它内部的变量全都明确地声明为局部变量。如果你总是在函数里使用var关键字来定义变量,就能避免任何形式的二义性隐患。

2.7 对象

对象(object)是一种非常重要的数据类型,但此前我们还没有认真对待它。对象是自包含的数据集合,包含在对象里的数据可以通过两种形式访问——属性(property)和方法(method):

  • 属性是隶属于某个特定对象的变量;
  • 方法是只有某个特定对象才能调用的函数。

对象就是由一些属性和方法组合在一起而构成的一个数据实体。

在JavaScript里,属性和方法都使用 “点”语法来访问:

Object.property
Object.method()

你已经见过如何用moodage等变量来存放诸如“心情”和“年龄”之类的值。如果它们是某个对象的属性——这里不妨假设那个对象的名字是Person,我们就必须使用如下所示的记号来使用它们:

Person.mood
Person.age

假如Person对象还关联着一些诸如walk()sleep()之类的函数,这些函数就是这个对象的方法,而我们必须使用如下所示的记号来访问它们:

Person.walk()
Person.sleep()

把这些属性和方法全部集合在一起,我们就得到了一个Person对象。

为了使用Person对象来描述一个特定的人,需要创建一个Person对象的实例(instance)。实例是对象的具体个体。例如,你和我都是人,都可以用Person对象来描述;但你和我是两个不同的个体,很可能有着不同的属性(例如,你和我的年龄可能不一样)。因此,你和我对应着两个不同的Person对象——它们虽然都是Person对象,但它们是两个不同的实例。

为给定对象创建一个新实例需要使用new关键字,如下所示:

var jeremy = new Person;

上面这条语句将创建出Person对象的一个新实例jeremy。我们就可以像下面这样利用Person对象的属性来检索关于jeremy的信息了:

jeremy.age
jeremy.mood

对象、属性、方法和实例等概念比较抽象,为了让大家对这些概念有一个直观的认识,我在这里用虚构的Person对象作为例子。JavaScript里并没有 Person 对象。我们可以利用JavaScript来创建自己的对象——术语为用户定义对象(user-defined object)。这是一个相当高级的主题,我们眼下还无需对它做进一步讨论。

在电视上的烹饪节目里,只要镜头一转,厨师就可以端出一盘美味的菜肴并向大家介绍说:“这是我刚做好的”。JavaScript与这种节目里的主持人颇有几分相似:它提供了一系列预先定义好的对象,这些可以拿来就用的对象称为内建对象(native object)。

2.7.1 内建对象

你其实已经见过一些内建对象了,数组就是其中一种。当我们使用new关键字去初始化一个数组时,其实是在创建一个Array对象的新实例:

var beatles = new Array();

当需要了解某个数组有多少个元素时,利用Array对象的length属性来获得这一信息:

beatles.length;

Array对象只是诸多JavaScript内建对象中的一种。其他例子包括Math对象和Date对象,它们分别提供了许多非常有用的方法供人们处理数值和日期值。例如,Math对象的round方法可以把十进制数值舍入为一个与之最接近的整数:

var num = 7.561;
var num = Math.round(num);
alert(num);

Date对象可以用来存储和检索与特定日期和时间有关的信息。在创建Date对象的新实例时,JavaScript解释器将自动地使用当前日期和时间对它进行初始化:

var current_date = new Date();

Date对象提供了getDay()getHours()getMonth()等一系列方法,以供人们用来检索与特定日期有关的各种信息。例如,getDay()方法可以告诉我们给定日期是星期几:

var today = current_date.getDay();

在编写JavaScript脚本时,内建对象可以帮助我们快速、简单地完成许多任务。

2.7.2 宿主对象

除了内建对象,还可以在JavaScript脚本里使用一些已经预先定义好的其他对象。这些对象不是由JavaScript语言本身而是由它的运行环境提供的。具体到Web应用,这个环境就是浏览器。由浏览器提供的预定义对象被称为宿主对象(host object)。

宿主对象包括FormImageElement等。我们可以通过这些对象获得关于网页上表单、图像和各种表单元素等信息。

本书没有收录这几个宿主对象的例子。另一种宿主对象也能用来获得网页上的任何一个元素的信息,它就是document对象。在本书的后续内容里,我们将向大家介绍document对象的许多属性和方法。

2.8 小结

在本章中,我们介绍了JavaScript语言的基础知识。在本书的后续章节中,我们会用到这里介绍的许多术语:语句、变量、数组和函数等。这些概念有的现在还不太容易理解,但我相信你在看过它们在脚本里的实际用途后,就能彻底搞清楚了。在后面的学习里,如果需要重温这些术语的含义,随时可以返回到本章来。

本章只对“对象”做了一个概念性的介绍。如果你对它的理解还不够全面深入,别着急。我们将在下一章进一步探讨document对象。我们将先向大家介绍一些与这个对象相关联的属性和方法,它们都是由W3C的标准DOM提供的。

在下一章中,我们将介绍基于DOM的基本编程思路,并演示如何使用它的一些功能非常强大的方法。

目录