在JavaScript中,引用型数据主要包括:Object、Function、Array。值类型数据也可以被包装成引用型对象,如String、Number、Boolean。下面将简单介绍对象、函数和数组,更详细的讲解请参阅后面章节。
数组(Array)是有序数据的集合,集合内每个元素的值通过下标访问,如图4-1所示。元素的类型没有限制,可以是任意类型的数据,如数值、字符串、布尔型、对象、数组、函数等。下标值是一个从0开始的连续正整数。
图4-1 数据结构模型
【示例1】 获取数组元素值的方法是通过下标来定位。
提示: 在JavaScript中还有一种特殊的数组结构:关联数组。关联数组是以字符串为下标来定位元素。
【示例2】 JavaScript仅支持一维数组,但是JavaScript对于数组元素所包含的数据类型没有限制。用户可以通过为数组元素传递数组来模拟多维数组的结构。
定义数组有多种方法,常用方法如下所示。
☑ 通过构造函数Array()创建数组
【示例3】 在下面代码中使用Array()创建数组,然后为数组中每个元素赋值。
在构造数组时,可以直接在构造函数中传递值。
【示例4】 在下面这个新创建的数组中,第一个元素是数组类型数据,第二个元素是对象类型数据,第三个元素是函数类型的数据。
var a=new Array([1,2],{x:1,y:2},function(){alert("我是数组元素")})
也可以直接使用构造函数创建一个指定数组长度的空数组。
【示例5】 下面代码将创建一个包含3个未定义元素的新数组。
var a=new Array(3);
☑ 通过数组直接量定义数组
所谓数组直接量就是通过中括号语法直接包含一组数据,或者也可以称之为数组常量。中括号内每个元素序列通过逗号语法分隔。
【示例6】 使用数组直接量定义上面的数组。当然,数组结构也是可以嵌套的,通过下面代码大家可以看到这一特点。
var a=[[1,2],{x:1,y:2},function(){alert("我是数组元素")}];
数组可以包含任意形式的表达式,这样就可以把表达式存储起来,避免现在被运算,当需要时再调出执行运算。
【示例7】 在下面这行代码中,数组的第一个元素值为一个简单的算术表达式,第二个元素值为一个比较运算表达式,第三个元素值为一个条件表达式。
var a=[(3-2),(3<2),(true)?1:0];
使用数组直接量可以创建空数组,定义的方法是在逗号之间省去元素的值即可,此时元素的默认值为undefined。
【示例8】 下面代码定义了包含5个元素的空数组。
var a=[,,,,];
与对象直接量一样,数组直接量也可以嵌套。
【示例9】 下面的变量a是一个嵌套了4层的复杂结构数组。
对象(Object)是无序数据的集合。如果说数组是线性数据结构,那么对象应该是离散数据结构,对象包含的数据没有顺序,放在前或放在后没有必然的联系,也不会影响对数据的存取操作。
在对象内,多个成员之间通过逗号进行分隔,每个成员都被标识了一个名称,成员名称与值之间通过冒号分隔,也称为名值对,因此对象也是名/值对的集合。这些命名的成员常被称为对象的属性,如果其值是一个函数,则也称为方法。
定义对象结构有多种方法,常用方法如下所示。
☑ 通过构造函数创建对象
【示例1】 下面代码使用new运算符构造多个对象。
创建对象之后,可以使用点号运算符为其定义属性。
☑ 通过对象直接量定义对象
对象直接量通过大括号语法来定义,大括号包含的是一个名/值对列表,名与值之间通过冒号隔开,而成员之间通过逗号分隔。
【示例2】 下面代码使用对象直接量定义一个对象,其包含两个属性a和b。
变量名是标识符,而属性名是一个字符串标签,对于上面示例中定义的对象直接量,也可以这样来表示:
但是变量名就不能够使用字符串表示。在构造函数内也不能使用字符串标签来命名属性名,因为此时属性名是合法的标识符。
对象的属性值可以是任意类型数据,如值类型数据、数组、对象、函数等。
【示例3】 如果属性值是函数,则该属性就成为对象的方法,读取这个特殊的属性值时,就必须附加小括号运算符。
【示例4】 如果属性值是对象,则可以设计连续使用点号运算符引用内层对象的属性值。
【示例5】 如果属性值是数组,则必须使用数组下标来读取某个元素的值。
【示例6】 可以使用关联数组来访问对象属性,即通过字符串下标来读取指定属性的值。
在JavaScript中,函数就是被封装的可执行的一段代码。一次定义,可以多次调用。
函数也可以是一个表达式,运算结果就是函数的返回值。如果没有返回值,则约定返回值为undefined。
上面代码实际上只是一个表达式。函数作为逻辑真运算的一个运算元,虽然没有返回值,但是它的返回值是undefined,所以最终这个表达式执行计算之后,会弹出一个提示对话框,提示“没有返回值”。
【示例1】 可以把函数作为一个值进行传递。
上面这个没有名称的函数,被称为匿名函数或函数直接量。用户可以把函数作为值赋值给对象的属性,这个属性就变成了方法。
【示例2】 本示例演示了把函数作为值传递给对象的属性,这个属性就变成了一个方法。
JavaScript把函数视为一个独立的作用域,函数外无法访问内部私有变量,只能够通过函数返回值读取内部变量的值。
【示例3】 构造函数是函数的一种特殊类型,构造函数通过this关键字定义属性,然后通过运算符new创建实例。
于是构造函数就成为一类数据,即类。
alert(typeof f1); //返回object
JavaScript对函数的解析机制是不同的:对于使用function语句声明的函数,JavaScript解释器会在预编译期就解析函数,而对于匿名函数则直到执行期才按表达式运算进行解析。
【示例4】 下面是使用function语句声明两个同名函数f,声明之后马上进行调用,代码如下:
如果按代码从上到下的一般执行顺序,则第一次调用函数应该返回值为1,第二次调用函数应该返回值为2。但是,上面示例并不是这样。原来,JavaScript解释器在预编译时就会把所有使用function语句声明的函数进行处理,如果发现同名函数,则后面的函数体会覆盖前面的函数体。所以,当在执行期时,就会看到两次调用函数f时,返回的值都是2。
如果把第一个函数改为匿名函数,则会发现两次调用函数返回值都为1。
对于function语句创建的函数,JavaScript解释器不仅对函数名按变量标识符进行索引,而且对于函数体也提前进行处理。于是,在预编译期,同名的变量被后来的同名函数所覆盖。但是,在执行期,第一行初始化变量f值为一个匿名函数,于是又覆盖了变量f在预编译建立的索引,即指向一个函数体。所以,两次调用函数最后都返回匿名函数的返回值1。
如果把第二个函数改为匿名函数,则两次调用函数的返回结果又不相同。
这次返回值的不同,与上面分析的原因都是相同的。因为在第一次调用函数f时,它指向的还是在预编译期索引的声明函数体,当第二次调用函数f时,该变量f已经被匿名函数所覆盖。
如果我们把两个函数都修改为匿名函数,则JavaScript在预编译期没有处理函数,仅是建立变量f的索引。当在执行期,才按顺序处理每一个匿名函数。
提示: JavaScript解释器在预编译期处理函数时,是按代码块分别执行的,也就是说每块JavaScript脚本是分隔开的,这样就可以避免在逻辑上出现混乱。所谓代码块,就是被<script>标签分隔的JavaScript脚本。
【示例5】 在下面代码中,把两个被声明的同名函数放在不同的代码段中,则在预编译时,不会出现相互覆盖:
但是,同处于一个文档中的JavaScript脚本,即使它们分别位于不同的代码块中,但是它们都属于同一个作用域,相互之间是可以通信和调用的。
总之,在JavaScript脚本中,function是一个值,是一种数据类型,也是一段代码封装容器。