数组是一段线性分配的内存,它通过整数去计算偏移并访问其中的元素。数组可以是很快的数据结构。不幸的是,JavaScript的数组不是这样的。它提供了一种拥有类似数组特性的对象。它把数组的下标转变成字符串,用其作为属性。它明显地比一个真正的数组慢,但它可以更方便地使用。属性的检索和更新的方式与对象一模一样,除了有一个可以用整数作为属性名的特性外。数组有它们自己的定义格式。数组也有一套非常有用的内置方法,我将在后续描述它们。
数组定义
数组定义提供了一种非常方便地创建新数组的表示法。一个数组定义是在一对方括号中包围零个或多个用逗号分隔的值的表达式。数组定义可以出现在任何表达式可以出现的地方。数组的第一个值将获得属性名’0’,第二个值将获得属性名’1’,依次类推:
var empty=[];
var numbers=[‘zero’,’one’,’two’,’three’,’four’,’five’,’six’,’seven’,’eight’,’nine’];
empty[1] //undefined
numbers[1] //’one’
empty.length //0
numbers.length //10
而以下的对象定义将产生一个相似的结果:
var numbers_object={
‘0’:‘zero’,’1’:’one’,’2’:’two’,’3’:’three’,
’4’:’four’,’5’:’five’,’6’:’six’,’7’:’seven’,
’8’:’eight’,’9’:’nine’
};
numbers和numbers_object都是包含有10个属性的对象,并且那些属性刚好有相同的名字和值。但是它们也有一些显著的不同。numbers继承自Array.prototype,而numbers_object继承自Object.prototype,所以numbers继承了大量有用的方法。同时,numbers也有一个诡异的length属性,而numbers_object则没有。
在大多数语言中,一个数组的所有元素都要求是相同的类型。JavaScript允许数组包含任意混合类型的值:
var misc=[‘string’,98.6,true,false,null,undefined,[‘nested’,’array’],{object:true},NaN,Infinity];
misc.length //10
长度
每个数组都有一个length属性。和大多数其他语言不同,JavaScript数组的length是没有上界的。如果你用大于或等于当前length的数字作为下标来保存一个元素,那么length将增大,而不会发生数组边界越界错误。
length属性的值是这个数组的最大整数属性名加上1.它不一定等于数组里的属性的个数:(默然说话:我的天!那在一些特殊情况下,想要通过一个i的自增来遍历一个数组将是不可靠的了!)
var myArray=[];
myArray.length; //0
myArray[10000]=true;
alert(myArray.length); //10001
//myArray只包含一个属性
[]后缀下标运算符将它的表达式转换成一个字符串,如果该表达式有toString方法,就使用该方法的值。这个字符串将被用作属性名。如果这个字符串看起来像一个大于等于这个数组当前的length且小于4 294 967 295的正整数,那么这个数组的length就会被重新设置为新的下标加1。
你可以直接设置length的值。设置更大的length对数组无影响。而把length设小将导致所有下标大于等于新length的属性被删除(默然说话:经验证,对length的设置只影响整数下标,对于字符串下标的元素无影响):
numbers.length=3; //[‘zero’,’one’,’two’]
通过把下标指定为一个数组的当前length,可以附加一个新元素到该数组的尾部。
numbers[numbers.length]=’shi’; //[‘zero’,’one’,’two’,’shi’]
有时用push方法可以更方便地完成同样的事情:
numbers.push(‘go’); //[‘zero’,’one’,’two’,’shi’,’go’]
删除
由于JavaScript的数组其实就是对象,所以delete运算符可以用来从数组中移除元素:
delete numbers[2]; //[‘zero’,’one’,undefined,’shi’,’go’]
不幸的是,那样会在数组中遗留一个空洞。这是因为排在被删除元素之后的元素保留了它们最初的名字(下标)。而你通常想要的是递减后面每个元素的名字(下标)。
幸运的是,JavaScript数组有一个splice方法。它可以对数组做个手术,删除一些元素并将它们替换为其他的元素。第一个参数是数组中的一个序号。第二个参数是要删除的元素个数。任何额外的参数会在序号那个点的位置被插入到数组中:
numbers.splice(2,1); //[‘zero’,’one’,’shi’,’go’]
枚举
因为JavaScript的数组其实就是对象,所以for in语句可以用来遍历一个数组的所有属性。但for in无法保证属性的顺序,而大多数的数组应用都期望按照阿拉伯数字顺序来产生元素。此外,可能从原型链中得到意外属性的问题依旧存在。常规for语句可以避免这些问题。(默然说话:但如果数组的情况比较特殊,那在遍历时就需要小心了)
混淆的地方
在JavaScript编程中,一个常见的错误是在需要使用数组时使用对象,而在需要使用对象时使用了数组。其实规则很简单:当属性名是小而连续的整数时,你应该使用数组。否则,使用对象。
JavaScript使用typeof运算符报告数组的类型是’object’,这没有什么意义,也说明JavaScript本身对于数组和对象的区别是混乱的。
JavaScript在区别数组和对象上没有一个好的机制。可以通过定义我们自己的is_array函数来避开这个缺陷:
var is_array=function(value){
return value && typeof value===’object’ && value.constructor===Array;
}
不幸的是,它在识别从不同的窗口或帧里构造的数组时会失败。如果想要准确地检测那些外部的数组,我们不得不做更多的工作:
var is_array=function(value){
return value && typeof value===’object’ &&
typeof value.length===’number’ &&
tyeof value.splice===’function’ &&
!(value.propertyIsEnumerable(‘length’));
}
因为有了这样的一个测试,就有可能写一个函数,当传递的是单一值时只做一件事,而在传递一组值时要做很多的事情。
方法
JavaScript提供了一套作用于数组的方法。这些方法是被储存在Array.prototype中的函数。在第3章中,我们看到Object.rototype是可以被扩充的。同样,Array.prototype也可以被扩充。
举例来说,假设我们想要给array增加一个方法,它将允许我们对数组进行计算:
Array.method(‘reduce’,function(f,value){
var i;
for(i=0;i<tis.length;i+=1){
value=f(this[i],value);
}
return [...]