返回首页 JavaScript 核心技术指南

数组

JavaScript 的数组也是一个比较有意思的主题,虽然名为数组(Array),但是根据数组对象上的方法来看,更像是将很多东西混在在一起的结果。而传统的程序设计语言如 C/Java 中,数组内的元素需要具有相同的数据类型,而作为弱类型的 JavaScript,则没有这个限制,事实上,JavaScript 的同一个数组中,可以有各种完全不同类型的元素。

方法 描述
concat() 连接两个或更多的数组,并返回结果。
join() 把数组的所有元素放入一个字符串。元素通过指定的分隔符进行分隔。
pop() 删除并返回数组的最后一个元素。
push() 向数组的末尾添加一个或更多元素,并返回新的长度。
reverse() 颠倒数组中元素的顺序。
shift() 删除并返回数组的第一个元素。
slice() 从某个已有的数组返回选定的元素。
sort() 对数组的元素进行排序。
splice() 删除元素,并向数组添加新元素。
unshift() 向数组的开头添加一个或更多元素,并返回新的长度。
valueOf() 返回数组对象的原始值。

可以看出,JavaScript 的数组对象比较复杂,包含有 pop,push 等类似与栈的操作,又有 slice, reverse,sort 这样类似与列表的操作。或许正因为如此,JavaScript 中的数组的功能非常强大。

数组的特性

数组包括一些属性和方法,其最常用的属性则为 length,length 表示数组的当前长度,与其他语言不同的是,这个变量并非只读属性,比如:

var array = new Array(1, 2, 3, 4, 5);  
print(array.length);  
array.length = 3;  
print(array.length);<span style="font-size: small;"><span style="font-size: 13px;">  
</span></span> 

运行结果为:

5
3
1,2,3

注意到最后的 print 语句的结果是”1,2,3”,原因是对 length 属性的修改会使得数组后边的元素变得不可用(如果修改后的 length 比数组实际的长度小的话),所以可以通过设置 length 属性来将数组元素裁减。

另一个与其他语言的数组不同的是,字符串也可以作为数组的下标(事实上,在 JavaScript 的数组中,数字下标最终会被解释器转化为字符串,也就是说,所谓的数字下标只不过是看着像数字而实际上是字符的属性名),比如:

var stack = new Array();  

stack['first'] = 3.1415926;  
stack['second'] = "okay then.";  
stack['third'] = new Date();  

for(var item in stack){  
    print(typeof stack[item]);  
}  

运行结果为:

number
string
object

在这个例子里,还可以看到不同类型的数据是如何存储在同一个数组中的,这么做有一定的好处,但是在某些场合则可能形成不便,比如我们在函数一章中讨论过的 sum 函数,sum 接受非显式的参数列表,使用这个函数,需要调用者必须为 sum 提供数字型的列表(当然,字符串无法做 sum 操作)。如果是强类型语言,则对 sum 传入字符串数组会被编译程序认为是非法的,而在 JavaScript 中,程序需要在运行时才能侦测到这一错误。

使用数组

数组的基本方法使用

数组有这样几种方式来创建:

var array = new Array();  
var array = new Array(10);//长度  
var array = new Array("apple", "borland", "cisco");

不过,运用最多的为字面量方式来创建,如果第三章中的 JSON 那样,我们完全可以这样创建数组:

var array = [];  
var array = ["one", "two", "three", "four"]; 

下面我们通过一些实际的小例子来说明数组的使用(主要方法的使用):

向数组中添加元素:

var array = [];  

array.push(1);  
array.push(2);  
array.push(3);  

array.push("four");  
array.push("five");  

array.push(3.1415926);  

前面提到过,JavaScript 的数组有列表的性质,因此可以向其中 push 不同类型的元素,接上例:

var len = array.length;  
for(var i = 0; i < len; i++){  
    print(typeof array[i]);  
} 

结果为:

number
number
number
string
string
number

弹出数组中的元素:

for(var i = 0; i < len; i++){  
    print(array.pop());  
}  
print(array.length);

运行结果如下,注意最后一个 0 是指 array 的长度为 0,因为这时数组的内容已经全部弹出:

3.1415926
five
four
3
2
1
0

join,连接数组元素为一个字符串:

array = ["one", "two", "three", "four", "five"];  

var str1 = array.join(",");  
var str2 = array.join("|");  

print(str1);  
print(str2);  

运行结果如下:

one,two,three,four,five
one|two|three|four|five

连接多个数组为一个数组:

var another = ["this", "is", "another", "array"];  
var another2 = ["yet", "another", "array"];  

var bigArray = array.concat(another, another2);

结果为:

one,two,three,four,five,this,is,another,array,yet,another,array

从数组中取出一定数量的元素,不影响数组本身:

print(bigArray.slice(5,9));  

结果为:

this,is,another,array

slice 方法的第一个参数为起始位置,第二个参数为终止位置,操作不影响数组本身。下面我们来看splice方法,虽然这两个方法的拼写非常相似,但是功用则完全不同,事实上,splice是一个相当难用的方法:

bigArray.splice(5, 2);  

bigArray.splice(5, 0, "very", "new", "item", "here");  

第一行代码表示,从 bigArray 数组中,从第 5 个元素起,删除 2 个元素;而第二行代码表示,从第 5 个元素起,删除0个元素,并把随后的所有参数插入到从第 5 个开始的位置,则操作结果为:

one,two,three,four,five,very,new,item,here,another,array,yet,another,array

我们再来讨论下数组的排序,JavaScript 的数组的排序函数 sort 将数组按字母顺序排序,排序过程会影响源数组,比如:

var array = ["Cisio", "Borland", "Apple", "Dell"];  
print(array);  
array.sort();  
print(array); 

执行结果为:

Cisio,Borland,Apple,Dell
Apple,Borland,Cisio,Dell

这种字母序的排序方式会造成一些非你所预期的小 bug,比如:

var array = [10, 23, 44, 58, 106, 235];  
array.sort();  
print(array); 

得到的结果为:

10,106,23,235,44,58

可以看到,sort 不关注数组中的内容是数字还是字母,它仅仅是按照字母的字典序来进行排序,对于这种情况,JavaScript 提供了另一种途径,通过给 sort 函数传递一个函数对象,按照这个函数提供的规则对数组进行排序。

function sorter(a, b){  
    return a - b;  
}  

var array = [10, 23, 44, 58, 106, 235];  
array.sort(sorter);  
print(array); 

函数 sorter 接受两个参数,返回一个数值,如果这个值大于 0,则说明第一个参数大于第二个参数,如果返回值为0,说明两个参数相等,返回值小于 0,则第一个参数小于第二个参数,sort 根据这个返回值来进行最终的排序:

10,23,44,58,106,235

当然,也可以简写成这样:

array.sort(function(a, b){return a - b;});//正序  
array.sort(function(a, b){return b - a;});//逆序  

删除数组元素

虽然令人费解,但是 JavaScript 的数组对象上确实没有一个叫做 delete 或者 remove 的方法,这就使得我们需要自己扩展其数组对象。一般来说,我们可以扩展 JavaScript 解释器环境中内置的对象,这种方式的好处在于,扩展之后的对象可以适用于其后的任意场景,而不用每次都显式的声明。而这种做法的坏处在于,修改了内置对象,则可能产生一些难以预料的错误,比如遍历数组实例的时候,可能会产生令人费解的异常。

数组中的每个元素都是一个对象,那么,我们可以使用 delete 来删除元素吗?来看看下边这个小例子:

var array = ["one", "two","three","four"];  
//数组中现在的内容为:  
//one,two,three,four  
//array.length == 4  
delete array[2]; 

然后,我们再来看看这个数组的内容:

one, two, undefined, four
//array.length == 4

可以看到,delete 只是将数组 array 的第三个位置上的元素删掉了,可是数组的长度没有改变,显然这个不是我们想要的结果,不过我们可以借助数组对象自身的 slice 方法来做到。一个比较好的实现,是来自于 jQuery 的设计者 John Resig:

//Array Remove - By John Resig (MIT Licensed)  
Array.prototype.remove = function(from, to) {  
    var rest = this.slice((to || from) + 1 || this.length);  
    this.length = from < 0 ? this.length + from : from;  
    return this.push.apply(this, rest);  
};  

这个函数扩展了 JavaScript 的内置对象 Array,这样,我们以后的所有声明的数组都会自动的拥有 remove 能力,我们来看看这个方法的用法:

var array = ["one", "two", "three", "four", "five", "six"];  
print(array);  
array.remove(0);//删除第一个元素  
print(array);  
array.remove(-1);//删除倒数第一个元素  
print(array);  
array.remove(0,2);//删除数组中下标为0-2的元素(3个)  
print(array);

会得到这样的结果:

one,two,three,four,five,six
two,three,four,five,six
two,three,four,five
five

也就是说,remove 接受两个参数,第一个参数为起始下标,第二个参数为结束下标,其中第二个参数可以忽略,这种情况下会删除指定下标的元素。当然,不是每个人都希望影响整个原型链(原因在下一个小节里讨论),因此可以考虑另一种方式:

//Array Remove - By John Resig (MIT Licensed)  
Array.remove = function(array, from, to) {  
    var rest = array.slice((to || from) + 1 || array.length);  
    array.length = from < 0 ? array.length + from : from;  
    return array.push.apply(array, rest);  
};  

其操作方式与前者并无二致,但是不影响全局对象,代价是你需要显式的传递需要操作的数组作为第一个参数:

var array = ["one", "two", "three", "four", "five", "six"];  
Array.remove(array, 0, 2);//删除0, 1, 2三个元素  
print(array); 

这种方式,相当于给JavaScript内置的Array添加了一个静态方法。

遍历数组

在对象与 JSON 这一章中,我们讨论了 for…in 这种遍历对象的方式,这种方式同样适用于数组,比如:

var array = [1, 2, 3, 4];  
for(var item in array){  
    print(array[item]);  
}

将会打印:

1
2
3
4

但是这种方式并不总是有效,比如我们扩展了内置对象 Array,如下:

Array.prototype.useless = function(){}  

然后重复执行上边的代码,会得到这样的输出:

1
2
3
4
function(){}

设想这样一种情况,如果你对数组的遍历做sum操作,那么会得到一个莫名其妙的错误,毕竟函数对象不能做求和操作。幸运的是,我们可以用另一种遍历方式来取得正确的结果:

for(var i = 0, len = array.length; i < len;i++){  
    print(array[i]);  
}  

这种 for 循环如其他很多语言中的写法一致,重要的是,它不会访问哪些下标不是数字的元素,如上例中的 function,这个 function 的下标为 useless,是一个字符串。从这个例子我们可以看出,除非必要,尽量不要对全局对象进行扩展,因为对全局对象的扩展会造成所有继承链上都带上“烙印”,而有时候这些烙印会成为滋生 bug 的温床。

上一篇: 函数 下一篇: 正则表达式