第 5 章 初探JavaScript
JavaScript过得挺不容易。出生不顺,青春期更满是苦涩。直到近年来,它才开始树立起一种实用的灵活语言的形象。JavaScript能做的事很多,尽管它还称不上完善,但也值得认真对待。
提示
没有一些编程经验,懂得变量、函数和对象等概念的话,可以去看一下lifehacker.com这个大众化网站上的系列文章,它没有预设编程知识门槛,所有例子都以JavaScript写就,非常方便。其使用说明参见:http:/lifehacker.com/5744113/learn-to-code-the-full-beginners-guide。
准备使用JavaScript
在HTML文档中定义脚本有几种方法可供选择。既可以定义内嵌脚本(脚本是HTML文档的一部分),也可以定义外部脚本(脚本包含在另一个文件中,通过一个URL引用)。这两种方法都要用到script元素。简洁起见,这里使用的是内嵌脚本。代码清单5-1所示为这种脚本的一个例子。
代码清单1 一段简单的内嵌脚本
这段小脚本的作用是在文档中加入单词Hello。script元素位于文档中其他内容之后,这样一来在脚本执行之前浏览器就已经对其他元素进行了解析。
图(用JavaScript向HTML文档中添加内容)显示了这个例子在浏览器中显示的结果。
使用语句
JavaScript的基本元素是语句。一条语句代表着一条命令,通常以分号(;)结尾。实际上分号用不用都可以,不过加上分号可让代码更易阅读,并且可以在一行书写几条语句。代码清单2示范了脚本程序中的几条语句。
代码清单2 使用JavaScript语句
浏览器依次执行每条语句。本例做的只是输出两条信息。结果如下(读者看到的输出结果有可能都在一行):
定义和使用函数
如果像代码清单2中那样在script元素中直接定义语句,那么浏览器一遇到这些语句就会执行它们。也可以把几条语句包装在一个函数中,浏览器只有在遇到一条调用该函数的语句时才会执行它。如代码清单3所示。
代码清单3 定义JavaScript函数
函数所含语句被包围在一对大括号({和})之间,称为代码块。这个代码清单定义了一个名为huangzihan的函数,其代码块中只含有一条语句。JavaScript是区分大小写的语言,因此function这个关键字必须小写。只有在浏览器遇到下面这样一条调用huangzihan函数的语句时,该函数中的语句才会执行。
huangzihan();
定义带参数的函数
与大多数语言一样,JavaScript中也可以为函数定义参数,如代码清单4所示。
代码清单4 定义带参数的函数
这里为huangzihan函数添加了两个参数:huangzihan_name和huangzihan_weather。JavaScript是门弱类型语言,所以定义函数的时候不必声明参数的数据类型。调用带参数的函数时要像这样提供相应的值作为参数:
huangzihan("黄子涵","晴天");
这个代码清单的输出结果如下:
调用函数时提供的参数数目不必与函数定义中的参数数目相同。如果提供的参数值更少,那么所有未提供值的参数的值均为undefined。如果提供的参数值更多,那么多出的值会被忽略。其结果是,要想定义两个同名但参数数目不同的函数,然后让JavaScript根据调用函数时提供的参数值数目确定所调用的函数是不可能的。要是定义了两个同名的函数,那么第二个定义将会取代第一个。
定义会返回结果的函数
可以用return关键字从函数中返回结果,代码清单5示范了一个这样的函数。
代码清单5 从函数中返回结果
本例中的函数定义了一个参数并用它生成一个简单的结果。脚本中的最后一条语句调用了这个函数并将结果作为参数传递给document.writeln函数。如下所示:
document.writeln(huangzihan("黄子涵"));
注意,定义这个函数时不用声明它会返回结果,也不用声明结果的数据类型。代码清单的输出结果如下所示:
使用变量和类型
定义变量要使用var关键字,在定义的同时还可以像在一条单独的语句中那样为其赋值。定义在函数中的变量称为局部变量,只能在该函数范围内使用。直接在script元素中定义的变量称为全局变量,可以在任何地方使用————包括在其他脚本中。代码清单6示范了局部变量和全局变量的用法。
代码清单6 使用局部变量和全局变量
JavaScript是一种弱类型语言,但这不代表它没有类型,而是指不用明确明变量的类型以及可随心所欲地用同一变量表示不同类型的值。JavaScript根据赋给变量的值确定其类型,还可以根据使用场景在类型间自由转换。代码清单6的输出结果如下:
使用基本类型
JavaScript定义了一小批基本类型(primitive type),它们包括字符串类型(string)、数值类型(number)和布尔类型(boolean)。这看起来可能有点少,但JavaScript的这三种类型具有很大的灵活性。
字符串类型
string类型的值可以用夹在一对双引号或单引号之间的一串字符表示,如代码清单7所示。
代码清单7 定义字符串变量
表示字符串时使用的引号要匹配。例如,字符串前端用单引号而后端用双引号是不行的。
布尔类型
boolean类型有两个值:true和false。
代码清单8 定义布尔变量
数值类型
整数和浮点数(也称实数)都用number类型表示,代码清单9示范了其用法。
代码清单9 定义数值变量
定义number类型变量时不必言明所用的是哪种数值,只消写出需要的值即可,JavaScript会耐情处理。上例先后使用了一个整数、一个浮点数和一个以0x开头的十六进制数。
创建对象
JavaScript支持对象的概念。有多种方法可以用来创建对象,代码清单10是一个简单的例子。
代码清单10 创建对象
此例通过调用
new Object()
的方式创建了一个对象,然后将其赋给一个名为huangzihan的变量。在此之后,即可像这样通过赋值的方式定义其属性:
huangzihan.huangzihan_name = "黄子涵";
在这条语句之前,对象并没有一个名为huangzihan_name的属性。这条语句执行之后就有了这个属性,而且其值已被设置为黄子涵。像这样用变量名后加一句点再加属性名的方式就能获取该属性的值:
使用对象字面量
用对象字面量的方式可以一口气定义一个对象及其属性,代码清单11示范了其做法。
代码清单11 使用对象字面量
从代码清单中可以看到,属性的名称和值之间以冒号(:)分隔,而各个属性之间又以逗号(,)分隔。
将函数用做方法
对象可以添加属性,也能添加函数。属于一个对象的函数称为其方法。代码清单12示范了如何以这种方式添加方法。
代码清单12 为对象添加方法
此例将一个函数变成了一个名为huangzihan_printMessages的方法。注意,在方法内部使用对象属性时要用到this关键字。函数作为方法使用时,其所属对象会以关键字this的形式作为一个参数被传给它。上述代码清单的输出结果如下所示:
提示
在创造和管理对象方面JavaScript还有许多招数可用,不过在讨论HTML5的时候还用不着这些特性。
使用对象
创建对象之后,可以用来做许多事
读取和修改属性值
对象最显而易见的操作是读取或修改属性值。这些操作有两种不同的语法形式,如代码清单13所示。
代码清单13 读取和修改对象属性值
第一种形式大多数程序员都很熟悉,前面的例子用的也是这种形式。其做法是像这样用句点将对象名和属性名连接在一起:
huangzihan.huangzihan_name = "黄子涵";
第二种形式类似数组索引,如下所示:
huangzihan["huangzihan_weather"] = "晴天";
在这种形式中,属性名作为一个字符串放在一对方括号之间。这种存取属性值的办法非常方便,这是因为此法可用变量表示属性名。如下所示:
var huangzihan = {
huangzihan_name: "黄子涵";
huangzihan_weather: "晴天";
};
var huangzihan_propName = "weather";
huangzihan[huangzihan_propName] = "雨天";
在此基础上才能枚举对象属性,下面就来谈这个话题。
枚举对象属性
要枚举对象属性可以使用for...in语句。代码清单14示范了其用法。
代码清单14 枚举对象属性
for...in循环代码块中的语句会对huangzihan_myData对象的每一个属性执行一次。在每一次迭代过程中,所要处理的属性名会被赋给huangzihan_prop变量。例中使用类数组索引语法(即使用方括号[和])获取对象的属性值。代码清单的输出结果如下所示(格式上已做调整,以便阅读):
从中可以看到,作为方法定义的那个函数也被枚举出来了。JavaScript在处理函数方面非常灵活,方法本身也被视为对象的属性,这就是其结果。
增删属性和方法
就算是用对象字面量生成的对象,也可以为其定义新属性。代码清单15即为一例。
代码清单15 为对象添加新属性
上例中为对象添加了一个名为huangzihan_myData.dayOfWeek的新属性。这里使用的是圆点表示法(用句点将对象和属性的名称连接在一起),不过用类数组索引表示法也没什么不可以。
读者看到此处可能会猜到:通过将属性值设置为一个函数也能为对象添加新方法。代码清单16即是一例。
代码清单16 为对象添加新方法
对象的属性和方法可以用delete关键字删除,如代码清单17所示。
代码清单17 删除对象的属性
判断对象是否具有某个属性
可以用in表达式判断对象是否具有某个属性,如代码清单18所示。
代码清单18 检查对象是否具有某个属性
此例分别用一个已有的和一个没有的属性进行测试。hasName变量的值会是true,而hasDate变量的值会是false。
使用JavaScript运算符
JavaScript定义了大量标准运算符。表()罗列了最常用的一些运算符。
常用的JavaScript运算符
相等和等同运算符
相等和等同运算符需要特别说明一下。相等运算符会尝试将操作数转换为同一类型以便判断是否相等。只要明白其工作方式,这就是一个很方便的特性。代码清单19示范了相等运算符的用法。
代码清单19 使用相等运算符
这段脚本的输出结果如下:
此例中JavaScript先将两个操作数转换为同一类型再对其进行比较————从本质上讲,相等运算符测试两个值是否相等,不管其类型。如果想判断值和类型是否都相同,那么应该使用的是等同运算符(===,由三个等号组成。相等运算符是由两个等号组成),如代码清单20所示。
代码清单20 使用等同运算符
此例中等同运算符判定两个变量不一样。这个运算符不会进行类型转换,这段脚本的输出结果如下所示:
提示
代码清单19和代码清单20使用了if条件语句。这个语句先对一个条件进行评估,要是结果为true,就执行代码块中的语句。if语句还可以加上一个else子句,子句所含代码块中的语句会在条件为false的情况下执行。
JavaScript基本类型(指字符串和数值等内置类型)的比较是值的比较,而JavaScript对象的比较则是引用的比较。代码清单21展示了JavaScript处理对象的相等和等同测试的方式。
代码清单21 对象的相等和等同测试
这段脚本的结果如下:
代码清单22对基本类型变量做了同样的测试。
代码清单22 基本类型的相等和等同测试
其结果如下:
显式类型转换
字符串连接运算符(+)比加法运算符(也是+)优先级更高。这可能会引起混乱,这是因为JavaScript在计算结果时会自动进行类型转换,其结果未必跟预期一样。代码清单23即是一例。
代码清单23 字符串连接运算符的优先权
其结果如下:
第二个结果正是混乱所在。原想的可能是加法运算,而在运算符优先级别和过分热心的类型转换这两个因素的共同作用下,结果却被诠释成了字符串连接运算。为了避免这种局面,可以对值的类型进行显式转换,以确保执行的是正确的运算。表(数值到字符串的常用转换方法)列出了一些最常用的类型转换方法。
将数值转换为字符串
如果想把多个数值类型变量作为字符串连接起来,可以用toString方法将数值转换为字符串,如代码清单24所示。
代码清单24 使用Number.toString方法
注意此例中先把数值放在括号中然后才调用toString方法。这是因为要想调用number类型定义的toString方法,必须先让JavaScript将字面量转换为一个number类型的值。例中还示范了与调用toString方法等效的另一种做法,即调用String函数并将要转换的数值作为参数传递给它。这两种做法的作用都是将number类型的值转换为string类型,因此+这个运算符会被用来进行字符串连接而不是加法运算。这段脚本的输出结果如下所示:
将数值转换为字符串还有一些别的方法,它们可以对转换方式施加更多控制。所有这些方法在表(数值到字符串的常用转换方法)中都有简要说明,它们都是number类型定义的方法。
数值到字符串的常用转换方法
方法 | 说明 | 返回 |
---|---|---|
toString() | 以十进制形式表示数值 | 字符串 |
toString(2) | 以二进制、八进制和十六进制形式表示数值 | 字符串 |
toString(8) | 以二进制、八进制和十六进制形式表示数值 | 字符串 |
toString(16) | 以二进制、八进制和十六进制形式表示数值 | 字符串 |
toString(n) | 以小数点后有n位数字的形式表示实数 | 字符串 |
toExponential(n) | 以指数表示法表示数值。尾数的小数点前后分别有1位数字和n位数字 | 字符串 |
toPrecision(n) | 用n位有效数字表示数值,在必要的情况下使用指数表示法 | 字符串 |
将字符串转换为数值
与前述需求相反,有时需要把字符串转换为数值,以便进行加法运算而不是字符串连接。这可以用Number函数办到,如代码清单25所示。
代码清单25 将字符串转换为数值
其输出结果如下:
Number函数解析字符串值的方式很严格,在这方面parseInt和parseFloat函数更为灵活,后面这两个函数还会忽略数字字符后面的非数字字符。这三个函数的说明见表(字符串到数值的常用转换函数)。
字符串到数值的常用转换函数
函数 | 说明 |
---|---|
Number( |
通过分析指定字符串,生成一个整数或实数值 |
parseInt( |
通过分析指定字符串,生成一个整数值 |
parseFloat( |
通过分析指定字符串,生成一个整数或实数值 |
使用数组
JavaScript数组的工作方式与大多数编程语言中的数组类似。代码清单26示范了如何创建和填充数组。
代码清单26 创建和填充数组
例中通过调用
new Array()
创建一个新的数组。这是一个空数组,它被赋给变量huangzihan。后面的语句给数组中的几个索引位置设置了值。
此例有几处需要说明一下。首先,创建数组的时候不需要声明数组中元素的个数。JavaScript数组会自动调整大小以便容纳所有元素。第二,不必声明数组所含数据的类型。JavaScript数组可以混合包含各种类型的数据。例中分别把一个数值、一个字符串和一个布尔值赋给了不同的数组元素。
使用数组字面量
使用数组字面量,可以在一条语句中创建和填充数组,如代码清单27所示。
代码清单27 使用数组字面量
此例通过在一对方括号([和])之间指定所需数组元素的方式创建了一个新数组,并将其赋给变量huangzihan。
读取和修改数组内容
要读取指定索引位置的数组元素值,应使用一对方括号([和])并将索引值放在方括号间,如代码清单28所示。JavaScript数组的索引值从0开始。
代码清单28 读取指定索引位置的数组元素值
要修改JavaScript数组中指定位置的数据,只消将新值赋给该索引位置的数组元素即可。与普通变量一样,改变数组元素的数据类型没有任何问题。代码清单29示范了如何修改数组内容。
代码清单29 修改数组内容
此例将一个字符串赋给了数组第0位的元素,该元素原来保存的是一个数值。
枚举数组内容
可以用for循环枚举数组内容。代码清单30示范了如何使用循环语句显示一个简单数组的内容。
代码清单30 枚举数组内容
JavaScript中的循环语句的工作方式与大多数语言中的类似。要确定数组中的元素个数可以使用其length属性。代码清单的输出结果如下:
使用内置的数组方法
JavaScript中的Array对象定义了许多方法。表(常用数组方法)罗列了一些最常用的方法。
常用数组方法
方法 | 说明 | 返回 |
---|---|---|
concat( |
将数组和参数所指数组的内容合并为一个新数组。可指定多个数组 | 数组 |
join( |
将所有数组元素连接为一个字符串。各元素内容用参数指定的字符分隔 | 字符串 |
pop() | 把数组当做栈使用,删除并返回数组的最后一个元素 | 对象 |
push( |
把数组当做栈使用,将指定的数据添加到数组中 | void |
reverse() | 就地反转数组元素的次序 | 数组 |
shift() | 类似pop,但操作的是数组的第一个元素 | 对象 |
slice( |
返回一个子数组 | 数组 |
sort() | 就地对数组元素排序 | 数组 |
unshift( |
类似push,但新元素被插到数组的开头位置 | void |
处理错误
JavaScript用try...catch语句处理错误。代码清单31示范了这个语句的用法。
代码清单31 异常处理
这段脚本中的问题很常见:试图使用未恰当初始化的变量。可能会引发错误的代码被包装在try子句中。如果没有发生错误,那么这些语句会正常执行,catch子句会被忽略。
但是如果有错误发生,那么try子句中语句的执行将立即停止,控制权转移到catch子句中。发生的错误由一个Error对象描述,它会被传递给catch子句。表(Error对象)显示了Error对象定义的属性。
Error对象
属性 | 说明 | 返回 |
---|---|---|
message | 对错误条件的说明 | 字符串 |
name | 错误的名称,默认为Error | 字符串 |
number | 该错误的错误代号(如果有的话) | 数值 |
catch子句提供了一个从错误中恢复或在错误发生后进行一些清理工作的机会。如果不管是否发生错误都执行一些语句,那么可以加上一条finally子句并将它们置于其中,如代码清单32所示。
代码清单32 使用finally子句
比较undefined和null值
JavaScript中有两个特殊值:undefined和null,在比较它们的时候需要留心。在读取未赋值的变量或试图读取对象没有的属性时得到的就是undefined值。代码清单33示范了JavaScript中undefined的用法。
代码清单33 特别的undefined值
清单的输出如下:
JavaScript怪就怪在又定义了一个特殊值null,这个值与undefined略有不同。后者是在未定义值的情况下得到的值,而前者则用于表示已经赋了一个值但该值不是一个有效的object、string、number或boolean值(也就是说所定义的是一个无值[no value])。为了澄清这个问题,代码清单34先后使用了undefined和null以展示其不同效果。
代码清单34 使用undefined和null
例中创建了一个对象,然后试图读取其huangzihan_weather属性,而该属性在这段代码的开头部分尚未定义:
document.writeln("Var:" + huangzihan.huangzihan_weather);
document.writeln("Prop:" + ("huangzihan_weather" in huangzihan));
此时huangzihan_weather属性还不存在,因此表达式huangzihan.huangzihan_weather的值为undefined,而且用in关键字判断对象是否具有这个属性时得到的结果是false。这两条语句的输出如下:
Var:undefined Prop:false
例中随后给huangzihan_weather属性赋了一个值,其效果是将该属性添加到对象中:
huangzihan.huangzihan_weather = "晴天";
document.writeln("Var:" + huangzihan.huangzihan_weather);
document.writeln("Prop:" + ("huangzihan_weather" in huangzihan));
现在重新读取该属性的值并看看对象是否有了该属性。不出所料,对象的确定义了该属性且值为晴天:
Var:晴天 Prop:true
接下来,把该属性的值设置为null:
huangzihan.huangzihan_weather = null;
其作用很明确:对象依然具有该属性,但程序员表示它没有值。重复前面的测试,结果如下:
Var:null Prop:true
检查变量或属性是否为undefined或null
如果想检查某属性是否为null或undefined(不管是哪一个),那么只要像代码清单35这样使用if语句和逻辑非运算符(!)即可:
代码清单35 检查属性是否为null或undefined
这种办法借助了JavaScript实施的类型转换,有了这种转换,例中检查的值会被当做布尔值处理。如果变量或属性为null或undefined,则转换而得的布尔值为false。
区分null和undefined
在比较两个值时,所用办法应视需要而定。如果想同等对待undefined值和null值,那么应该使用相等运算符(
==
),让JavaScript进行类型转换。此时值为undefined的变量会被认为与值为nu11的变量相等。如果要区分null和undefined,则应使用等同运算符(===)。代码清单36包括了这两种比较。
代码清单36 null和undefined值的相等和等同比较
常用的JavaScript工具
有很多工具可用来简化JavaScript编程工作,其中有两个特别值得一提。
使用JavaScript调试器
现代浏览器都配备了精良的JavaScript调试器(或以插件的方式支持这一功能,如Mozilla Firefox的插件Firebug)。它们可以用来设置断点、探查错误和逐句执行脚本。在遇到脚本方面的问题时首先应该想到的就是向调试器求助。在碰到特别难缠的问题时,Firefox上的Firebug在对付复杂情况时显得更为强大。
使用JavaScript库
使用JavaScript最简便的方式是使用某种JavaScript工具包或库。这种工具包多如牛毛,有两种值得专门推荐一下。第一种,是jQuery。jQuery及其配套程序库jQueryUI非常流行,其开发非常活跃,具有许多很有用的特性。有了jQuery,JavaScript开发工作要轻松、惬意得多。
推荐的另一种工具包,是jQuery的主要竞争对手Dojo。其功能堪比jQuery,支持完善,而且应用广泛。jQuery和Dojo可以分别从jquery.com和http://dojotoolkit.org下载。