JavaScript 网页编程从入门到精通 (清华社"视频大讲堂"大系·网络开发视频大讲堂)
上QQ阅读APP看书,第一时间看更新

4.3 引用型数据

在JavaScript中,引用型数据主要包括:Object、Function、Array。值类型数据也可以被包装成引用型对象,如String、Number、Boolean。下面将简单介绍对象、函数和数组,更详细的讲解请参阅后面章节。

4.3.1 数组

数组(Array)是有序数据的集合,集合内每个元素的值通过下标访问,如图4-1所示。元素的类型没有限制,可以是任意类型的数据,如数值、字符串、布尔型、对象、数组、函数等。下标值是一个从0开始的连续正整数。

图4-1 数据结构模型

【示例1】获取数组元素值的方法是通过下标来定位。

      var a = [[1,2], {x:1, y:2}, function(){alert("我是数组元素")}]
      alert(a[0]);  //返回第一个元素的值,显示为数组结构
      a[2]();       //返回第三个元素的值,返回一个函数体,然后通过小括号执行运算,弹出一个提示对话框

提示:在JavaScript中还有一种特殊的数组结构:关联数组。关联数组是以字符串为下标来定位元素。

【示例2】JavaScript仅支持一维数组,但是JavaScript对于数组元素所包含的数据类型没有限制。用户可以通过为数组元素传递数组来模拟多维数组的结构。

      var a = [
          [11,12,13],
          [21,22,23]
      ];                                            //模拟二维数组结构

定义数组有多种方法,常用方法如下所示。

通过构造函数Array()创建数组

【示例3】在下面代码中使用Array()创建数组,然后为数组中每个元素赋值。

      var a=new Array();                               //使用构造函数构造数组结构体
      a[0]=0;                                          //为数组元素赋值
      a[1] = "1";
      a[2] = true;

在构造数组时,可以直接在构造函数中传递值。

【示例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层的复杂结构数组。

      var a = [
          1,
          [
            2,
            [
                3,
                [
                    4,5,6
                ]
            ]
          ]
      ];

4.3.2 对象

对象(Object)是无序数据的集合。如果说数组是线性数据结构,那么对象应该是离散数据结构,对象包含的数据没有顺序,放在前或放在后没有必然的联系,也不会影响对数据的存取操作。

在对象内,多个成员之间通过逗号进行分隔,每个成员都被标识了一个名称,成员名称与值之间通过冒号分隔,也称为名值对,因此对象也是名/值对的集合。这些命名的成员常被称为对象的属性,如果其值是一个函数,则也称为方法。

定义对象结构有多种方法,常用方法如下所示。

通过构造函数创建对象

【示例1】下面代码使用new运算符构造多个对象。

      var o=new Object();                              //创建普通对象
      var d=new Date();                                //创建时间对象
      var r=new RegExp();                              //创建正则表达式对象

创建对象之后,可以使用点号运算符为其定义属性。

      var o=new Object();                              //创建普通对象
      o.a="string";                                    //定义属性a,值为字符串"string"
      o.b=true;                                        //定义属性b,值为布尔值true

通过对象直接量定义对象

对象直接量通过大括号语法来定义,大括号包含的是一个名/值对列表,名与值之间通过冒号隔开,而成员之间通过逗号分隔。

【示例2】下面代码使用对象直接量定义一个对象,其包含两个属性a和b。

      var o={                                       //对象直接量
          a:1,                                      //定义属性
          b:true                                    //定义属性
      }

变量名是标识符,而属性名是一个字符串标签,对于上面示例中定义的对象直接量,也可以这样来表示:

      var o={                                        //对象直接量
          "a":1,                                     //定义属性
          "b":true                                   //定义属性
      }

但是变量名就不能够使用字符串表示。在构造函数内也不能使用字符串标签来命名属性名,因为此时属性名是合法的标识符。

      var o=function(){                               //构造函数
          this.a=1;                                   //定义属性
          this.b=true;                                //定义属性
      }

对象的属性值可以是任意类型数据,如值类型数据、数组、对象、函数等。

【示例3】如果属性值是函数,则该属性就成为对象的方法,读取这个特殊的属性值时,就必须附加小括号运算符。

      var o={                                       //对象直接量
          a:function(){                             //属性值为函数
            return 1;
          }
      }
      alert(o.a());                                 //附加小括号读取属性值,即调用方法

【示例4】如果属性值是对象,则可以设计连续使用点号运算符引用内层对象的属性值。

      var o={                                       //对象直接量
          a:{                                       //属性值为对象
            b:1
          }
      }
      alert(o.a.b);                                 //连续使用点号运算符读取内层对象的属性值

【示例5】如果属性值是数组,则必须使用数组下标来读取某个元素的值。

      var o={                                            //对象直接量
          a:[1,2,3]                                      //属性值为数组
      }
      alert(o.a[0]);                                     //使用下标来读取属性包含的元素值

【示例6】可以使用关联数组来访问对象属性,即通过字符串下标来读取指定属性的值。

      var o={                                           //对象直接量
          a : 1
      }
      alert(o["a"]);                                    //使用关联数组来读取对象的属性值

4.3.3 函数

在JavaScript中,函数就是被封装的可执行的一段代码。一次定义,可以多次调用。

      function exec(){                                  //封装可执行代码的结构
          var sum = 0;
          for(var i = 0; i < 100; i ++ ){
            sum += i;
          }
          document.write(sum);
      }
      exec();                                           //调用函数,实际上是执行一次封装的代码块

函数也可以是一个表达式,运算结果就是函数的返回值。如果没有返回值,则约定返回值为undefined。

      (function(){
          //函数体
      }() == undefined ) &&
      alert("没有返回值")

上面代码实际上只是一个表达式。函数作为逻辑真运算的一个运算元,虽然没有返回值,但是它的返回值是undefined,所以最终这个表达式执行计算之后,会弹出一个提示对话框,提示“没有返回值”。

【示例1】可以把函数作为一个值进行传递。

      var a=function(){                                //把函数作为值赋值给变量a
          return 1;
      }
      alert(a());                                       //计算变量a,实际上就是调用匿名函数

上面这个没有名称的函数,被称为匿名函数或函数直接量。用户可以把函数作为值赋值给对象的属性,这个属性就变成了方法。

【示例2】本示例演示了把函数作为值传递给对象的属性,这个属性就变成了一个方法。

      var o = {
          alert:function(x){                             //把函数传递给对象属性
            alert("温馨提示:\n\n   "+x);
          }
      }
      o.alert("你吃饭了吗?");                             //定义你的提示对话框

JavaScript把函数视为一个独立的作用域,函数外无法访问内部私有变量,只能够通过函数返回值读取内部变量的值。

【示例3】构造函数是函数的一种特殊类型,构造函数通过this关键字定义属性,然后通过运算符new创建实例。

      function f(){                                    //构造函数
          this.a =1;
          this.b = function(){
            return this.a + this.a;
          };
      }
      var f1 = new f();
      var a=f1.a;                                      //返回1,即函数包含的数据

于是构造函数就成为一类数据,即类。

      alert(typeof f1);                                  //返回object

JavaScript对函数的解析机制是不同的:对于使用function语句声明的函数,JavaScript解释器会在预编译期就解析函数,而对于匿名函数则直到执行期才按表达式运算进行解析。

【示例4】下面是使用function语句声明两个同名函数f,声明之后马上进行调用,代码如下:

      function f(){                                     //声明函数f
          return 1;
      }
      alert(f());                                       //返回2
      function f(){                                     //声明函数f
          return 2;
      }
      alert(f());                                       //返回2

如果按代码从上到下的一般执行顺序,则第一次调用函数应该返回值为1,第二次调用函数应该返回值为2。但是,上面示例并不是这样。原来,JavaScript解释器在预编译时就会把所有使用function语句声明的函数进行处理,如果发现同名函数,则后面的函数体会覆盖前面的函数体。所以,当在执行期时,就会看到两次调用函数f时,返回的值都是2。

如果把第一个函数改为匿名函数,则会发现两次调用函数返回值都为1。

      var f=function(){                                 //定义匿名函数f
          return 1;
      }
      alert(f());                                       //返回1
      function f(){                                     //声明函数f
          return 2;
      }
      alert(f());                                       //返回1

对于function语句创建的函数,JavaScript解释器不仅对函数名按变量标识符进行索引,而且对于函数体也提前进行处理。于是,在预编译期,同名的变量被后来的同名函数所覆盖。但是,在执行期,第一行初始化变量f值为一个匿名函数,于是又覆盖了变量f在预编译建立的索引,即指向一个函数体。所以,两次调用函数最后都返回匿名函数的返回值1。

如果把第二个函数改为匿名函数,则两次调用函数的返回结果又不相同。

      function f(){                                     //声明函数f
          return 1;
      }
      alert(f());                                       //返回1
      var f=function(){                                 //定义匿名函数f
          return 2;
      }
      alert(f());                                       //返回2

这次返回值的不同,与上面分析的原因都是相同的。因为在第一次调用函数f时,它指向的还是在预编译期索引的声明函数体,当第二次调用函数f时,该变量f已经被匿名函数所覆盖。

如果我们把两个函数都修改为匿名函数,则JavaScript在预编译期没有处理函数,仅是建立变量f的索引。当在执行期,才按顺序处理每一个匿名函数。

      var f=function(){                                //定义匿名函数f
          return 1;
      }
      alert(f());                                      //返回1
      var f=function(){                                //定义匿名函数f
          return 2;
      }
      alert(f());                                      //返回2

提示:JavaScript解释器在预编译期处理函数时,是按代码块分别执行的,也就是说每块JavaScript脚本是分隔开的,这样就可以避免在逻辑上出现混乱。所谓代码块,就是被<script>标签分隔的JavaScript脚本。

【示例5】在下面代码中,把两个被声明的同名函数放在不同的代码段中,则在预编译时,不会出现相互覆盖:

      <script>
      //JavaScript脚本段1
      function f(){                                    //声明函数f
          return 1;
      }
      alert(f());                                      //返回1
      </script>
      <script>
      //JavaScript脚本段2
      function f(){                                    //声明函数f
          return 2;
      }
      alert(f());                                      //返回2
      </script>

但是,同处于一个文档中的JavaScript脚本,即使它们分别位于不同的代码块中,但是它们都属于同一个作用域,相互之间是可以通信和调用的。

      <script>
      //JavaScript脚本段1
      function f(){                                    //声明函数f
          return 1;
      }
      alert(f());                                      //返回1
      </script>
      <script>
      //JavaScript脚本段2
      alert(f());                                      //返回1
      </script>

总之,在JavaScript脚本中,function是一个值,是一种数据类型,也是一段代码封装容器。