第七章:数组
2017.02.27
数组
数组是值的有序集合。每个值叫做一个元素,而每个元素在数组中有一个位置,以数字表示,称为索引。
JavaScript数组是无类型的:数组元素可以是任意类型,并且同一个数组中的不同元素也可能有不同的类型。
索引是基于零的32位数值:第一个元素的索引是0,最大可能的索引是2^32-2(4294967294)。数组最大能容纳4294967295个元素。JavaScript数组是动态的:根据需要它们会增长或缩短,并且在创建数组时无须声明一个固定的大小或者在数组大小变化时无须重新分配空间。
JavaScript数组可能是稀疏的:数组元素的索引不一定要连续,它们之间可以有空缺。每个JavaScript数组都有一个length属性。针对非稀疏数组,该属性就是数组元素的个数。针对稀疏数组,length比所有元素的索引要大。
数组是对象的特殊形式,数组索引实际上和碰巧是整数的属性名差不多。数组的实现是经过优化的,用数字索引访问数组元素一般来说比访问常规的对象属性要快很多。
数组继承自Array.prototype中的属性,它定义了一套丰富的数组操作方法。
创建数组
使用数组直接量是创建数组最简单的方法,在方括号中将数组元素用逗号隔开即可。
数组直接量中的值不一定要是常量:它们可以是任意的表达式。
如果省略数组直接量中的某个值,省略的元素将被赋予undefined。
数组直接量的语法允许有可选的结尾的逗号,故[,,]只有两个元素而非三个。
var empty=[];
var primes=[1,2,3,4,5];
var misc=[1.1,true,"a",[1,{x:1,y:2}],{x:3,y:4}];
var base=1024;var table=[base,base+1,base+3];
var count=[1,,3];//三个元素,1 undefined 3
var undefs=[,,];//两个元素,undefined undefined
使用构造函数Array()是创建数组的另一种方法,可以用三种方式调用构造函数:
//调用时没有参数,创建一个没有任何元素的空数组,等同于数组直接量[].
var a=new Array();
//调用时有一个参数,指定长度。数组中没有存储值,数组的索引属性也为定义。
var a=new Array(10);
a; //[ , , , , , , , , , ];9个逗号
a.length; //10
for (i in a) console.log(i);//什么都不打印
//显示指定两个或多个数组元素或者数组的一个非数值元素。
var a=new Array(5,4,3,2,1,"testing","testing");
> var a=new Array(1)
undefined
> a
[ ]
> var a=new Array("test")
undefined
> a
[ 'test' ]
> var a=new Array(5,4,3,2,1,"testing","testing");
undefined
> a
[ 5, 4, 3, 2, 1, 'testing', 'testing' ]
数组元素的读和写
使用[]操作符来访问数组中的一个元素。数组的引用位于方括号的左边。方括号中是一个返回非负整数值的任意表达式。使用该语法既可以读又可以写数组的一个元素。
var a=["world"];
var value=a[0];
a[1]=3.14;
i=2;
a[i]=3;
a[i+1]="hello";
a[a[i]]=a[0];
素组是对象的特殊形式。数组的特别之处在于,当使用小于232-2的非负整数作为属性名的时候数组会自动维护其length属性值。所有的索引都是属性名,但是只有0-232-2之间的整数属性名才是索引。所有的数组都是对象,可以为其创建任意名字的属性。但如果使用的属性是数组的索引,数组的特殊行为就是将根据需要更新它们的length属性值。
注意:可以使用负数或非整数来索引数组。这种情况下,数值转化为字符串,字符串作为属性名来用。如果使用非负整数的字符串,他就当做数组索引,当使用一个浮点数和一个整数相等时情况也是一样的。
a[-1.23]=true;//创建名为"-1.23"的属性
a["1000"]=0;//这是数组的第1001个元素
a[1.000];//和a[1]相等
> var a=[]
undefined
> a
[]
> a["x"]=1;
1
> a
[ x: 1 ]
> a.y=3
3
> a
[ x: 1, y: 3 ]
> a[0]=100
100
> a
[ 100, x: 1, y: 3 ]
> a[1]
undefined
> a.x
事实上,数组索引仅仅是对象属性名的一种特殊类型。这意味着JavaScript数组没有”越界“错误的概念。当时图查询热河对象中不存在的属性时,不会报错,只会得到undefined值。
在ES5中,数组可以定义元素的getter和setter方法。如果一个数组集成了元素或使用了元素的getter和setter方法,你应该期望它使用非优化的代码路径:访问这种数组的元素的时间会与常规对象属性的查找时间相近。
稀疏数组
稀疏数组就是包含从0开始的不连续索引的数组。通常,数组的length属性值代表数组中元素的个数,如果数组是稀疏的,length属性值大于元素的个数。可以用Array()构造函数或简单地指定数组的索引值大于当前数组来创建稀疏数组。
a=new Array(5);//数组没有元素,length为5
a=[];//创建一个空数组,length为0
a[1000]=0;//赋值添加一个元素,设置length为1001
也可以用delete产生稀疏数组。
足够稀疏的数组通常在实现上比稠密的数组更慢,内存利用率更高,在这样的数组中查找元素的时间与常规属性的查找时间一样长。
数组长度
每个JavaScript数组都有一个length属性。针对非稀疏数组,该属性就是数组元素的个数。针对稀疏数组,length比所有元素的索引要大。
在数组中,找不到一个元素的索引值大于或等于它的长度。
设置length属性为一个小鱼当前长度的非负整数n时,当前数组中那些索引值大于或等于n的元素将被删除。
var a=[1,2,3,4,5]
a.length;//5
a.length=3;//现在为[1,2,3]
a.length=0;//现在为[]
a.length=5;//长度为5,但没有元素,就像new Array() ;[undefined × 5]
在ES5中,可以用Object.defineProperty()让数组的length属性只读。
a=[1,2,3];
Object.defineProperty(a,"length",{writable:false});
a.length=0;//无效
a.length;//3
数组元素的添加和删除
a=[];//空数组
a[0]="zero";
a[1]="one";
删除元素不会修改数组length属性。也不会将元素从高索引处一下来补充已删除属性留下的空白。删除元素会使数组变成稀疏数组。
a=[1,2,3]
delete a[0];//true
a;//[undefined × 1, 2, 3]
a.length;//3
delete a[2];//tru
a;//[undefined × 1, 2, undefined × 1]
a.length;//3
push()
使用push()方法在数组末尾增加一个或多个元素。
a=[];
a.push("zero");//a=["zero"]
a.push("one","two");//a=["zero","one","two"];
pop()
使用pop()方法从数组尾部减少一个元素并返回元素的值。
数组遍历
var keys=Object.keys(o);
var values=[];
for(var i=0,len=keys.length;i<len;i++){
if (!a[i]) continue;//跳过null,undefined,不存在的元素
if(!a.hasOwnProperty(i)) continue;//跳过继承的属性
var key=keys[i];
values[i]=o[key];
}
【求数组平方和】
var data=[1,2,3,4,5];
var sum=0;
data.forEach(function(x){
sum+=x*x;
});
sum;//55
多维数组
数组的数组。
var table=new Array(10);
for (var i=0;i<table.length;i++){
table[i]=new Array(10);
}
for(var row=0;row<table.length;row++){
for(col=0;col<table[row].length;col++){
table[row][col]=row*col;
}
}
table[5][7];//35
数组方法
join()
Array.join()方法将数组中所有元素都转化为字符串并连接在一起,返回最后生成的字符串。可以指定一个可选的字符串在生成的字符串中来分隔数组的各个元素。如果不指定分隔符,默认用逗号。
var a=[1,2,3];
a.join();//"1,2,3"
a.join(" ");//"1 2 3"
a.join("");//"123"
var b=new Array(10);
b.join('-');//"---------":9个连字符
reverse()
Array.reverse()方法将数组中的元素颠倒顺序,返回逆序的数组。采用替换,不通过重新排列元素创建新数组,而是在原来的数组中重新排列它们。
var a=[1,2,3];
a.reverse();//[3,2,1]
sort()
Array.sort()方法将数组中的元素排序并返回排序后的数组。当不带参数调用sort()时,数组元素以字母表顺序排序(必要时临时转化为字符串进行比较)。
var a=["banana","apple","peach"];
a.sort();//[ 'apple', 'banana', 'peach' ]
如果包含undefined元素,会排到数组的尾部。
> var a=['a',,,,'b']
undefined
> a.sort()
[ 'a', 'b', , , ]
按照其他方式排序需要传递一个比较函数。该函数决定了它的两个参数在排好序的数组中的先后顺序。假设第一个参数应该在前,比较函数应该返回一个小于0的数值。繁殖,假设第一个参数应该灾后,函数应该返回一个大于0的数值。假设两个值相等(顺序无关紧要),函数应该返回0。
【对数字按照数值大小进行排序】
var a=[33,4,1111,222];
a.sort();//[ 1111, 222, 33, 4 ]
a.sort(function(a,b){return a-b;});//[ 4, 33, 222, 1111 ] 从小到大
a.sort(function(a,b){return b-a;});//[ 1111, 222, 33, 4 ] 从大到小
【对字符串执行不区分大小写的字母表排序】
a=["ant","Bug","cat","Dog"]
a.sort();//[ 'Bug', 'Dog', 'ant', 'cat' ]
a.sort(function(s,t){
var a=s.toLowerCase();
var b=t.toLowerCase();
if(a>b)return 1;
if(a<b)return -1;
return 0;
});//[ 'ant', 'Bug', 'cat', 'Dog' ]
concat()
Array.concat()方法创建并返回一个新数组,它的元素包括调用concat()的原始数组的元素和concat()的每个参数。如果这些参数中的任何一个自身是数组,则连接的是数组的元素,而非数组本身。
concat()不会递归扁平化数组的数组。concat()也不会修改调用的数组。
var a=[1,2,3];
a.concat(4,5);//[ 1, 2, 3, 4, 5 ]
a.concat([4,5]);//[ 1, 2, 3, 4, 5 ]
a.concat([4,5],[6,7]);//[ 1, 2, 3, 4, 5, 6, 7 ]
a.concat(4,[5,[6,7]]);//[ 1, 2, 3, 4, 5, [ 6, 7 ] ]
slice()
Array.slice()方法返回指定数组的一个片段或子数组。它的两个参数分别制定了片段的开始和结束的位置。
返回的数组包含第一个参数指定的位置和所有到但不包含第二个参数指定的位置之间的所有数组元素。
如果只制定一个参数,返回的数组将包含从开始位置到数组结尾的所有元素。
如果参数中出现负数,则表示相对于数组中的最后一个元素的位置。例如,-1指定最后一个元素,-3指定倒数第三个元素。
slice()不会修改调用的数组。
var a=[1,2,3,4,5];
a.slice(0,3);//[ 1, 2, 3 ]
a.slice(3);//[ 4, 5 ]
a.slice(1,-1);//[ 2, 3, 4 ]
a.slice(-3,-2);//[ 3 ]
splice()
Array.splice()方法是在数组中插入或删除元素的通用方法。
splice会修改调用的数组。
从数组中删除数组、插入元素到数组中或者同时完成这两种操作。在插入或删除点之后的数组元素会根据需要增加或减小他们的索引值,因此数组仍然保持连续。
splice()第一个参数指定了插入和(或)删除的起始位置。第二个参数制定了应该从数组中删除的元素的个数。如果省略第二个参数,从起始点开始到数组结尾的所有元素都将被删除。splice()返回一个由删除元素组成的数组,或者如果没有删除元素就返回一个空数组。
var a=[1,2,3,4,5,6,7,8];
a.splice(4);//返回[5,6,7,8],a是[1,2,3,4]
a.splice(1,2);//返回[2,3],a是[1,4]
a.splice(1,1);//返回[4],a是[1]
splice()的前两个参数指定需要删除的数组元素。紧随其后的任意个数的参数指定了需要插入到数组中的元素,从第一个参数指定的位置开始插入。
var a=[1,2,3,4,5];
a.splice(2,0,'a','b');//返回[],a是[1,2,'a','b',3,4,5]
a.splice(2,2,[1,2],3);//返回['a','b'],a是[1,2,[1,2],3,3,4,5]
注意:区别于concat(),splice()会插入数组本身而非数组的元素。
push()
push()和pop()允许将数组当做栈来使用。
push()方法在数组的尾部添加一个或多个元素,并返回数组新的长度。
pop()方法删除数组的最后一个元素,减小数组长度并返回删除的值。
两个方法都修改并替换原始数组。
组合使用push()和pop()能够用JavaScript数组实现先进后出的栈。
var stack=[];
stack.push(1,2);// stack:[1,2],返回2
stack.pop();//stack:[1],返回2
stack.push(3);// stack:[1,3],返回2
stack.pop();//stack:[1],返回3
stack.push([4,5]);//stack:[1,[4,5]],返回2
stack.pop();//stack:[1],返回[4,5]
stack.pop();//stack:[],返回1
unshift()和shift()
unshift()/shift()在数组的头部插入删除操作。
unshift()在数组头部添加一个或多个元素,并将已存在的元素移动到更高索引的位置来获得足够的空间,最后返回数组新的长度。
shift()删除数组的第一个元素并将其返回,然后所有随后的元素下移一个位置来填补数组头部的空缺。
var a=[];
a.unshift(1);//a:[1],返回1
a.unshift(22);//a:[22,1],返回2
a.shifit();//a:[1],返回22
a.unshift(3,[4,5]);//a:[3,[4,5],1],返回3
a.shift();//a:[[4,5],1],返回3
a.shift();//a:[1],返回[4,5]
a.shift();//a:[],返回1
toString()/toLocaleString()
数组的toString()将其每个元素都转化为字符串,并且出书用逗号分割的字符串列表。输出不包括方括号或其他任何形式的包裹数组值的分隔符。
[1,2,3].toString() //'1,2,3'
['a','b','c'].toString() //'a,b,c'
[1,[2,'c']].toString() //'1,2,c'
toLocaleString()是toString()方法的本地化版本。调用元素的toLocaleString()方法将每个数组元素转化为字符串,并且使用本地话分隔符讲这些字符串连接起来生成最终的字符串。
【ES5中的方法如下】
forEach()
从头到尾遍历数组,为每个元素调用指定的函数。
传递的函数作为forEach()的第一个参数。然后forEach()使用三个参数调用该函数:数组元素、元素的索引和数组本身。如果只关心数组元素的值,可以编写只有一个参数的函数——额外的参数将被忽略。
var data=[1,2,3,4,5];
var sum=0;
data.forEach(function(value){sum+=value;});//累加
sum;//15
data.forEach(function(v,i,a){a[i]=v+1});
data;//[2,3,4,5,6]
forEach()方法无法再所有元素都传递给调用的函数之前终止遍历。如果要提前终止,必须把forEach()方法放在一个try块中,并能抛出一个异常。如果forEach调用的函数抛出foreach.break异常,循环会提前终止。
function foreach(a,f,t){
try{a.forEach(f,t);}
catch(e){
if(e===foreach.break) return;
else throw e;
}
}
foreach.break=new Error("StopIteration");
map()
将调用的数组的每个元素传递给指定的函数,并返回一个数组,它包含该函数的返回值。
a=[1,2,3];
b=a.map(function(x){return x*x;});//b是[1,4,9]
传递给map()的函数调用方式和传递给forEach()的函数的调用方式一样。传递给map()的函数应该有返回值。map()返回新数组,不修改调用的数组。如果是稀疏数组,返回的也是相同方式的稀疏数组:具有相同的长度,相同的确实元素。
filter()
返回的数组元素是调用的数组的一个子集。传递的参数用来逻辑判断:函数返回true或false。如果返回值为true或是能转化为true的值,那么传递给判定函数的元素就是这个子集的成员,它被添加到一个座位返回值的数组中。
a=[5,4,3,2,1];
smallvalues=a.filter(function(x){return x<3}); //[2,1]
everyother=a.filter(function(x,i){return i%2==0});//[5,3,1]
filter()会跳过稀疏数组中缺少的元素,返回的数组总是稠密的。为了压缩稀疏数组的空缺,代码如下:
var dense=sparse.filter(function(){return true;});
压缩空缺并删除undefined和null:
a=a.filter(function(x){return x!=undefined&&x!=null;});
every()和some()
数组的逻辑判定:对数组元素应用指定的函数进行判定,返回true或false。
every()就像数学中的"针对所有"的量词∀:当且仅当针对数组中的所有元素调用判定函数都返回true,它才返回true。
a=[1,2,3,4,5];
a.every(function(x){return x<10;});//true,所有值都<10
a.every(function(x){return x%2===0});//false,不是所有制都是偶数
some()就像数学中的"存在"的量词∃:当数组中至少有一个元素调用判定函数返回true,他就返回true。
a=[1,2,3,4,5];
a.some(function(x){return x%2===0});//true:存在偶数值
a.some(isNaN);false:a不包含非数值元素
对于空数组,every()返回true,some()返回false。
some()在判定函数第一次返回true后就返回true。
every()在判定函数第一次返回false后就返回false。
reduce()和reduceRight()
reduce()和reduceRight()方法使用指定的函数将数组元素进行组合,生成单个值。这在数学编程中是常见的操作,也可以成为”注入“,”折叠“。
var a=[1,2,3,4,5];
var sum=a.reduce(function(x,y){return x+y},0);//数组求和
var product=a.reduce(function(x,y){return x*y},0);//数组求积
var max=a.reduce(function(x,y){return x>y?x:y});//求最大值
reduce()第一个参数是执行化简操作的函数,化简函数的任务就是用某种方法将两个值组合或化简为一个值,并返回化简后的值。第二个参数是可选的 传递给函数的初始值。
第一个参数的函数参数:第一个参数是到目前为止的化简操作累积的结果,第二到四个参数是数组元素、元素的索引和数组本身。第一次调用时,第一个参数就是一个初始值,就是传递给reduce的第二个参数。接下来的调用中,他就是上一次化简函数的返回值。
空数组不带初始值参数调用reduce()将导致类型错误异常。如果调用的时候只有一个值,数组只有一个元素并且没有指定初始值,或者有一个空数组并且制定一个初始值,reduce()将返回那个值而不会调用化简函数。
reduceRight()按照数组索引从高到低(从右到左)处理数组,而不是从高到低。
var a=[2,1,4];
a.reduceRight(function(accumulator,value){
return Math.pow(value,accumulator);
});
使用reduce()计算任意数目的对象的并集:
function extend(o,p){for(prop in p) {o[prop]=p[prop];};return o}
function union(o,p){return extend(extend({},o),p);}
var objects=[{x:1},{y:2},{z:3}];
var merged=objects.reduce(union);//{x:1,y:2,z:3}
当拥有同名属性时.
var objects=[{x:1,a:1},{y:2,a:2},{z:3,a:3}];
var leftunion=objects.reduce(union);//{ x: 1, a: 3, y: 2, z: 3 }
var rightunion=objects.reduceRight(union);//{ z: 3, a: 1, y: 2, x: 1 }
indexOf()/lastIndexOf()
搜索整个数组中具有给定值的元素,返回找到的第一个元素的索引或者没有找到返回-1。indexof从头至尾搜索,lastIndexOf()反向搜索,
第一个参数是需要搜索的值,第二个参数是可选的:指定数组中的一个索引,从那里开始搜索。省略从头搜索(indexOf)或从尾搜索(lastIndexOf)。第二个参数可以是负数,代表相对于数组尾部的偏移量。
a=[0,1,2,3,1,2,3,0,1];
a.indexOf(1);//1
a.indexOf(1,2);//4
a.lastIndexOf(1);//8
a.lastIndexOf(1,-2);//4
a.indexOf(-1);//-1
数组类型
Array.isArray()判断是否是数组。
Array.isArray([]);//true
Array.isArray({});//false
类数组对象
数组特性:
- 自动跟新length属性。
- 设置length为较小值将截断数组。
- 从Array.prototype继承一些方法
- 类属性为”Array“
【判断是否是类数组对象】
function isArrayLike(o){
if(o&&typeof o==="object" &&isFinite(o.length)&&o.length==Math.floor(o.length)&&o.length<4294967296){
return true;
}else{
return false;
}
}
var a={"0":"a","1":"b","2":"c",length:3};
Array.prototype.join.call(a,"+");//"a+b+c"
Array.prototype.slice.call(a,0);//["a","b","c"];
Array.prototype.map.call(a,function(x){return x.toUpperCase();});//["A","B","C"]
作为数组的字符串
字符串的行为类似于只读的数组。
var s="test";
s.charAt(0);//"t"
s[1];//"e"
s="JavaScript"
Array.prototype.join.call(s," ");//'J a v a S c r i p t'
Array.prototype.filter.call(s,function(x){
return x.match(/[^aeiou]/);
}).join("");//'JvScrpt'
字符串是不可变的。把它们当做数组看待时,它们只读。push(),sort(),reverse(),splice()等会修改数组,使用这些方法会导致错误。
Array.prototype.push.call(s,"~");
//TypeError: Cannot assign to read only property 'length' of object '[object String]'