第5章
引用类型
本章内容
1.使用对象
2.创建并操作数组
3.理解基本的js类型
4.使用基本类型和基本包装类型
引用类型的值(对象)是引用类型的一个实例。在ECMAScript中,引用类型是一种数据结构,用于将数据和功能组织在一起,它也常被称为类,但这种称呼并不妥当。尽管ECMAScript从技术上讲是一门面向对象的语言,但它不具备传统的面向对象语言所支持的类和接口等基本结构。引用类型有时候也被称为对象定义,因为它们描述的是一类对象所具有的属性和方法。
虽然引用类型与类看起来相似,但它们并不是相同的概念。为避免混淆,本书不适用类这个概念。
如前所述,对象是某个特定引用类型的实例,新对象是使用new操作符后跟一个构造函数来创建的。构造函数本身就是一个函数,只不过该函数就是出于创建新对象的目的而定义的。请看下面这行代码:
var person = new Object ();
这行代码创建了Object引用类型的一个新实例。然后把该实例保存在了变量person中,使用的构造函数是Object,它只为新对象定义了默认的属性和方法。ECMAScript提供了很多原生引用类型(例如Object),以便开发人员用以实现常见的计算任务。
5.1 Object类型
到目前为止,我们看到的大多数引用类型值都是Object类型的实例。而且,Object也是ES中使用最多的一个类型。虽然Object的实例不具备多少功能,但对于在应用程序中存储和传输数据而言,它们确实是非常理想的选择。
创建Object实例的方式有两种。第一种是使用new操作符后跟Object构造函数,如下例所示:
var person = new Object ();
person.name = "Amy" ;
person.age = "23" ;
另一种方式是使用对象字面量表示法。对象字面量是对象定义的一种简写形式,目的在于简化创建大量属性的对象的过程。下面这个例子使用了对象字面量语法定义了与前面那个例子中相同的person对象。
var person = {
name: "Amy" ,
age: 23
}
在这个例子中,组边的花括号({)表示对象字面量的开始,因为它出现在了表达式上下文(expression context)中。ES中的表达式上下文指的是该上下文期待一个值(表达式)。复制操作符表示后面是一个值,所以左花括号在这里表示一个表达式的开始。同样的花括号,如果出现在一个语句上下文(statement context)中,例如跟在if语句条件后面,则表示一个语句块的开始。
然后我们定义了name属性,之后的一个冒号,再后面是这个属性的值。在对象字面量中,使用逗号来分隔不同的属性,因此,“Amy”后面是一个逗号。但是在age属性的值23的后面不能添加逗号,因为age是这个对象的最后一个属性,在最后一个属性后面加逗号,会在IE7及更早版本和Opera中导致错误。
在使用对象字面量语法时,属性名也可以使用字符串,如下面这个例子所示:
var person = {
"name" : "Amy" ,
"age" : 23 ,
5 : true
}
这个例子会创建一个对象,包含三个属性:name,age和5。但是这里的数值属性名会自动转换为字符串。
另外,使用对象字面量语法时,如果留空其花括号,则可以定义只包含默认属性和方法的对象。如下所示:
var person = {};
person.name = "Amy" ,
person.age = 23 ;
这个例子与本节前面的例子是等价的,只不过看起来似乎有点奇怪。关于对象字面量的语法,我们推荐,只在考虑对象属性名的可读性时使用。
在通过对象字面量定义对象时,实际上不会调用Object构造函数(Firefox2及更早版本会调用Object构造函数;但Firefox3之后就不会了)
虽然可以使用前面介绍的任何一种方法来定义对象,但开发人员更青睐对象字面量语法,因为这种语法要求的代码量少,而且能够给人封装数据的感觉。实际上,对象字面量也是向函数传递大量可选参数的首选方式,例如:
function displayInfo (args) {
var output = "" ;
if (typeof args.name == "string" ){
out += "Name: " + args.name + "\n" ;
}
if (typeof args.age == "number" ){
output += "Age: " + args.age +"\n"
}
alert(output);
}
displayInfo({
name: "Amy" ,
age: 23
})
displayInfo({
name: "shieh"
})
在这个例子中,函数displayInfo()接受一个名为args的参数。这个参数可能带有一个名为name或age的属性,也可能这两个属性都有或者都没有。在这个函数内部,我们通过typeof操作符来检测每个属性是否存在,然后再基于相应的属性来构建一条要显示的消息。然后我们调用了两次这个函数,每次都使用一个对象字面量来制定不同的数据。这两次调用传递的参数虽然不同,但函数都能正常执行。
这种传递参数的牧师最合适需要向函数传入大量可选参数的情形。一般来讲,命名参数虽然容易处理,但在有多个可选参数的情况下就会显示不够灵活。最好的做法是对那些必须值使用命名参数,而使用对象字面量来封装多个可选参数。
一般来说,访问对象属性时使用的都是点表示法。这也是很多面向对象语言中通用的语法。不过,在js中也可以使用方括号表示法来访问对象的属性。在使用方括号语法时,应该将要访问的属性以字符串的形式放在方括号中,如下面的例子所示:
console.log (args.name );
console.log (args[name ]);
从功能上看,这两种访问对象属性的方法没有任何区别。但是方括号语法的优点主要是可以通过变量来访问属性,例如:
var propertyName = "Amy" ;
console .log(person[propertyName]);
如果属性名中包含会导致语法错误的字符,或者属性名使用关键字或保留字,也可以使用方括号表示法。例如:
person["first name" ] = "Amy"
由于“first name”中包含一个空格,所以不能使用点表示法来访问它。然而属性名中是可以包含非字母非数字的,这时候就可以使用方括号表示法来访问它们。
通常,除非必须使用变量来访问属性,否则我们建议使用点表示法。
5.2 Array类型
除了object之外,Array类型恐怕是ES中最常用的类型了。而且EC中的数组与其他多数语言中的数组有着相当大的区别。虽然ES数组与其他语言中的数组都是数据的有序列表,但与其他语言不同的是:ES数组的每一项可以保存任何类型的数据。也就是说,可以用数组的第一个位子来保存字符串,第二个位置来保存数值,用第三个位置来保存对象,一次类推。而且ES数组的大小是可以动态调整的,即可以随着数据的添加自动增长,以容纳新增数据。
创建数组的基本方式有两种。第一种是使用Array构造函数,如下面的代码所示。
var colors = new Array ();
如果预先知道数组要保存的项目数量,也可以给构造函数传递该数量,而该数量会自动变成length属性的值。例如,下面的代码将创建length值为20的数组。
var color = new Array (20 );
也可以向Array构造函数传递数组中应该包含的项。以下代码创建了一个包含3个字符串值得数组:
var colors = new Array ("red" ,"blue" ,"green" );
当然,给构造函数传递一个值也可以创建数组。但这时候问题就复杂一点了。因为如果传递的数值,则会按照该数值创建包含给定项的数组;而如果传递的是其他类型的参数,则会创建包含那个值的只有一项的数组。下面就两个例子:
var colors = new Array (3 );
var names = new Array ("Amy" );
另外,在使用Array构造函数时也可以省略new操作符。如下面的例子所示,省略new操作符的结果相同
var colors = Array (3 );
var names = Array ("Amy" );
创建数组的第二种基本方式是使用数组字面量表示法。数组字面量由一对包含数组项的方括号表示,多个数组项之间以逗号隔开,如下所示:
var colors = ['red','blue','green']
var name = []
var values = [1 ,2 ,] //不要这样。这样会创建一个包含2 或3 项的数组
var options = [,,,,,] //不要这样!这样会创建一个包含5 或6 项的数组。
以上代码的第一行创建了一个包含3个字符串的数组。第二行使用一对空方括号创建了一个空数组。第三行展示了在数组字面量的最后一项添加逗号的结果:在IE8及之前的版本中,values会成为一个包含3个项目且每项的值分别为1、2和undefined的数组;在其他浏览器中,values会成为一个包含2项目值分别为1,2的数组。原因是在IE8及之前版本中的ES实现在数组字面量方面存在bug。
由于这个bug导致的另一种情况如最后一行代码所示,该代码可能会创建包含5项的数组(在IE9+、Firefox、Opera、Safari、Chrome中),也可能会创建包含6项的数组(在IE8及更早版本中)。在像这种省略值的情况下,每一项都将获得undefined值;这个结果与调用Array构造函数时传递项数在逻辑上是相同的。但是由于IE的实现与其他浏览器不一致,因此我们强烈建议不要使用这种语法。
与对象一样,在使用数组字面量表达法时,也不会调用Array构造函数(Firefox3及更早的版本除外)
在读取和设置数组的值时,要使用方括号并提供相应值的基于0的数字索引,如下所示:
var colors = ["red","blue","green"];//定义一个字符串数组
console.log(colors[0]); //显示第一项
colors[2] = "black" ;// 修改第三项
colors[3] = "brown"; //新增第四项
方括号中的索引值表示要访问的值。如果索引小于数组中的项数,则返回对应项的值,就像这个例子中的colors[0]会显示“red”一样。设置数组的值也使用相同的语法,但会替换制定位置的值。如果设置某个值的索引超过了数组现有项数,如这个例子中的colors[3]所示,数组就会自动增加到该索引值+1的长度。(就这个例子而言,索引是3,因此数组长度就是4)
数组的项数保存在其length属性中,这个属性始终会返回0或更大的值,如下面这个例子所示:
var colors = ["red" ,"blue" ,"green" ];
var names = [];
console.log(colors.length); //3
console.log(names.length); //0
数组的length属性很有特点——它不是只读的。因此,通过设置这个属性,可以从数组的末尾移除项或向数组中添加新项。请看下面的例子:
var colors = ["red" ,"blue" ,"green" ];
colors.length = 2 ;
console.log (colors[2 ]);
这个例子中的数组colors一开始有3个值。将其length属性设置为2会移除最后一项(位置为2的那一项),结果再访问colors[2]就会显示undefined了。如果将其length属性设置为大于数组项数的值,则新增的每一项都会取得undefined值,如下所示:
var colors = ["red" ,"blue" ,"green" ];
colors.length = 4 ;
console.log (colors[3 ]);
在此,虽然colors数组包含3个项,但把它的length属性设置成了4。这个数组不存在位置3,所以访问这个位置的值就得到了特殊值undefined。
利用length属性也可以方便地在数组末尾添加新项,如下所示:
var colors = ["red" ,"blue" ,"green" ];
colors[colors.length ] = "black" ;
colors[colors.length ] = "brown" ;
由于数组最后一项的索引值始终是length-1,因此下一新项的位置就是length。每当在数组末尾添加一项后,其length属性都会自动更新以反应这一变化。换句话说,上面例子第二行中的colors[colors.length]为位置3添加了一个值,最后一行的colors[colors.length]则为位置4添加了一个值。当把一个值放在超出当前数组大小的位置上时,数组就会重新计算其长度值,即长度值等于最后一项索引加1,如下面的例子所示:
var colors = ["red" ,"blue" ,"green" ];
colors[99 ] = "black" ;
colors[colors.length] = "brown" ;
在这个例子中,我们向colors数组的位置99插入了一个值,结果数组新长度length就是100.而位置3到98实际上都是不存在的,所以访问它们都将返回undefined。
数组做多可以包含4 294 967 295个项,这几乎已经能够满足任何编程需求了。如果想添加的项数超过这个上限值,就会发生异常。而创建一个初始大小与这个上限值接近的数组,则可能会导致运行时间超长的脚本错误。
5.2.1 检测数组
自从ES3做出规定以后,就出现了确定某个对象是不是数组的经典问题,对于一个网页,或者一个全局作用域而言,使用instanceof操作符就能得到满意的结果
if (value instanceof Array ){
}
instanceof操作符的问题在于,它假定只有一个全局执行环境。如果网页中包含多个框架,那实际上就存在两个以上的全局执行环境,从而存在两个以上不同版本的Array构造函数。如果你从一个框架向另一个框架传入数据,那么传入的数据与在第二个框架中原生创建的数组分别具有各自不同的构造函数。
为了解决这个问题,ES5新增了Array.isArray()方法。这个方法的目的是确定某个值到底是不是数据,而不管它是在哪个全局执行环境中创建的。这个方法的用法如下
if (Array.isArray(value )){
}
支持Array.isArray()方法的浏览器有IE9+,Firefox4+,Safari5+,Opera10.5+和Chrome。要在商未实现这个方法的浏览器中准确检测数组,请参考22.1.1节。
5.2.2转换方法
如前所述,所有对象都具有toLocalString(),toString()和valueOf()方法。其中,调用数组的toString()方法会返回由数组中每个值得字符串形式拼接而成的一个以逗号分隔的字符串。而调用valueOf()返回的还是数组。实际上,为了创建这个字符串会调用数组每一项的toString()方法。来看下面这个例子:
var colors = ["red" ,"blue" ,"green" ];
console.log (colors.toString());
console.log (colors.valueOf());
alert(colors);
在这里,我们首先显式地调用了toString()方法,以便返回数组的字符串表示,每个值得字符串表示拼接成了一个字符串,中间以逗号分隔。接着调用valueOf()方法,而最后一行代码直接将数组传递给了alert().由于alert()要接收字符串参数,所以它会在后台调用toString()方法,由此会得到与直接调用toString方法相同的结果。