数组简介(了解)

  数组是值的有序集合。每个值叫做一个元素,而每个元素在数组中有一个位置,以数字表示,称为索引。
  JavaScript 数组是无类型的:数组元素可以是任意类型,并且同一个数组中的不同元素也可能有不同的类型。数组的元素甚至也可能是对象或其他数组,这允许创建复杂的数据结构,如对象的数组和数组的数组。
  JavaScript 数组的索引是基于零的 32 位数值:第一个元素的索引为 0,最大可能的索引为 4294967294(2^32-2),数组最大能容纳 4294967295 个元素。
  JavaScript 数组是动态的:根据需要它们会增长或缩减,并且在创建数组时无须声明一个固定的大小或者在数组大小变化时无须重新分配空间。
  JavaScript 数组可能是稀疏的:数组元素的索引不一定要连续的,它们之间可以有空缺。
  每个 JavaScript 数组都有一个 length 属性。针对非稀疏数组,该属性就是数组元素的个数。针对稀疏数组,length 比所有元素的索引要大。

创建数组(掌握)

数组直接量

  使用数组直接量是创建数组最简单的方法,在方括号中将数组元素用逗号隔开即可。

var empty=[]//没有元素的数组
var primes=[235711]//有5个数值的数组
var misc=[1.1true"a"]//3个不同类型的元素和结尾的逗号
1
2
3

  如果省略数组直接量中的某个值,省略的元素将被赋子予 undefined 值:

var count=[1,,3]//数组有3个元素,中间的那个元素值为undefined
var undefs=[,,]//数组有2个元素,都是undefined
1
2

  数组直接量的语法允许有可选的结尾的逗号,故[,,]只有两个元素而非三个。

调用构造函数 Array()

  • 调用时没有参数:
var a=new Array();
1

  该方法创建一个没有任何元素的空数组,等同于数组直接量[]。

  • 调用时有一个数值参数,它指定长度:
var a=new Array10);
1

  该技术创建指定长度的数组。当预先知道所需元素个数时,这种形式的 Array()构造函数可以用来预分配一个数组空间。注意,数组中没有存储值,甚至数组的索引属性“0”、“1”等还未定义。

  • 显式指定两个或多个数组元素或者数组的一个非数值元素:
var a =new Array54321"testing,testing");
1

  以这种形式,构造函数的参数将会成为新数组的元素。使用数组字面量比这样使用 Array()构造函数要简单多了。

数组元素的读和写(掌握)

  使用[]操作符来访问数组中的一个元素。数组的引用位于方括号的左边。方括号中是一个返回非负整数值的任意表达式。使用该语法既可以读又可以写数组的一个元素。

var a=["world"]//从一个元素的数组开始
var value=a[o]//读第0个元素
a[1]=3.14//写第1个元素
i=2;
a[i]=3//写第2个元素
a[i+1]="hello"//写第3个元素
a[a[i]]=a[o]//读第0个和第2个元素,写第3个元素
1
2
3
4
5
6
7

  所有的索引都是属性名,但只有在 0~2^32-2 之间的整数属性名才是索引。所有的数组都是对象,可以为其创建任意名字的属性。但如果使用的属性是数组的索引,数组的特殊行为就是将根据需要更新它们的 length 属性值。
  注意,可以使用负数或非整数来索引数组。这种情况下,数值转换为字符串,字符串作为属性名来用。如果凑巧使用了是非负整数的字符串,它就当做数组索引,而非对象属性,当使用的一个浮点数和一个整数相等时情况也是一样的:

a[-1.23]=true//这将创建一个名为“-1.23"的属性
a["1000"]=0//这是数组的第1001个元素
a[1.000]//和a[1]相等
1
2
3

  既然数组是对象,那么它们可以从原型中继承元素。在 ECMAScript5 中,数组可以定义元素的 getter 和 setter 方法。如果一个数组确实继承了元素或使用了元素的 getter 和 setter 方法,你应该期望它使用非优化的代码路径:访问这种数组的元素的时间会与常规对象属性的查找时间相近。

稀疏数组(掌握)

  稀疏数组就是包含从 0 开始的不连续索引的数组。通常,数组的 length 属性值代表数组中元素的个数。如果数组是稀疏的,length 属性值大于元素的个数。可以用 Array()构造函数或简单地指定数组的索引值大于当前的数组长度来创建稀疏数组。

a=new Array5);//数组没有元素,但是a.length是5
a=[]//创建一个空数组,length=o
a[1000]=0//赋值添加一个元素,但是设置1ength为1001
1
2
3

  注意,当在数组直接量中省略值时不会创建稀疏数组。省略的元素在数组中是存在的,其值为 undefined。这和数组元素根本不存在是有一些微妙的区别的。可以用 in 操作符检测两者之间的区别:

var al=[,,,]//数组是[undefined,undefined,undefined]
var a2=new Array3);//该数组根本没有元素
0 in al//=>true:a1在索引0处有一个元素
0 in a2//=>false:a2在索引0处没有元素
1
2
3
4

  需要注意的是,当省略数组直接量中的值时(使用连续的逗号,比如[1,,3]),这时所得到的数组也是稀疏数组,省略掉的值是不存在的:

var al=[]//此数组没有元素,长度是1
var a2=[undefined]//此数组包含一个值为undefined的元素
0 in a1//=>false:al在索引0处没有元素
0 in a2//=>true:a2在索引0处有一个值为undefined的元素
1
2
3
4

数组长度(掌握)

  每个数组有一个 length 属性,就是这个属性使其区别于常规的 JavaScript 对象。针对稠密(也就是非稀疏)数组,1ength 属性值代表数组中元素的个数。其值比数组中最大的索引大 1:

[].length//=>0:数组没有元素
[a','b','c'].length//=>3:最大的索引为2,length为3
1
2

  当数组是稀疏的时,1ength 属性值大于元素的个数。而且数组长度保证大于它每个元素的索引值。或者,换一种说法,在数组中(无论稀疏与否)肯定找不到一个元素的索引值大于或等于它的长度。
  为了维持此规则不变化,数组有两个特殊的行为。第一个如同上面的描述:如果为一个数组元素赋值,它的索引 i 大于或等于现有数组的长度时,length 属性的值将设置为 i+1。
  第二个特殊的行为就是设置 length 属性为一个小于当前长度的非负整数 n 时,当前数组中那些索引值大于或等于 n 的元素将从中删除:

a=[12345]//从5个元素的数组开始
a.length=3//现在a为[1,2,3]
a.length=0//删除所有的元素。a为[]
a.length=5//长度为5,但是没有元素,就像new Array(5)
1
2
3
4

  还可以将数组的 length 属性值设置为大于其当前的长度。实际上这不会向数组中添加新的元素,它只是在数组尾部创建一个空的区域。
  在 ECMAScript5 中,可以用 Object.defineProperty()让数组的 length 属性变成只读的

a=[123]//从3个元素的数组开始
Object.defineProperty(a,“1ength",
                    {writable:false});//让length属性只读
a.length=0//a不会改变
1
2
3
4

  类似地,如果让一个数组元素不能配置,就不能删除它。如果不能删除它,length 属性不能设置为小于不可配置元素的索引值。

数组元素的添加和删除(掌握)

添加

  为新索引赋值:

a=[]//开始是一个空数组
a[o]="zero"//然后向其中添加元素
a[1]="one"
1
2
3

  也可以使用 push()方法在数组末尾增加一个或多个元素:

a=[]//开始是一个空数组
a.push("zero"//在末尾添加一个元素。a=["zero"]
a.push("one""two"//再添加两个元素。a=["zero","one","two"]
1
2
3

  在数组尾部压入一个元素与给数组 a[a.length]赋值是一样的。可以使用 unshift()方法在数组的首部插入一个元素,并且将其他元素依次移到更高的索引处。

删除

  可以像删除对象属性一样使用 delete 运算符来删除数组元素:

a=[123]delete a[1]//a在索引1的位置不再有元素
1 in a//=>false:数组索引1并未在数组中定义
a.length//=>3:delete操作并不影响数组长度
1
2
3
4

  删除数组元素与为其赋 undefined 值是类似的(但有一些微妙的区别)。注意,对一个数组元素使用 delete 不会修改数组的 length 属性,也不会将元素从高索引处移下来填充已删除属性留下的空白。如果从数组中删除一个元素,它就变成稀疏数组。
  上面我们看到,也可以简单地设置 length 属性为一个新的期望长度来删除数组尾部的元素。
  数组有 pop()方法,使减少长度 1 并返回被删除元素的值。还有一个 shift()方法,从数组头部删除一个元素。和 delete 不同的是 shift()方法将所有元素下移到比当前索引低 1 的地方。
  最后,splice()是一个通用的方法来插入、删除或替换数组元素。它会根据需要修改 length 属性并移动元素到更高或较低的索引处。

数组遍历(掌握)

  在嵌套循环或其他性能非常重要的上下文中,可以看到这种基本的数组遍历需要优化,数组的长度应该只查询一次而非每次循环都要查询:

forvar i=o,len=keys.length;i<len;i++{
//循环体仍然不变
}
1
2
3

  这些例子假设数组是稠密的,并且所有的元素都是合法数据。否则,使用数组元素之前应该先检测它们。如果想要排除 nu11、undefined 和不存在的元素,代码如下:

forvar i=o;i<a.length;i++{
    if!a[i]continue//跳过null、undefined和不存在的元素
    //循环体
}
1
2
3
4

  如果只想跳过 undefined 和不存的元素,代码如下:

forvar i=0;i<a.length;i++{
    if(a[i]===undefinedcontinue//跳过undefined+不存在的元素
    //循环体
}
1
2
3
4

  最后,如果只想跳过不存在的元素而仍然要处理存在的 undefined 元素,代码如下:

forvar i=o;i< a.length;i++{
    if(!(i in a))continue//跳过不存在的元素
//循环体
}
1
2
3
4

  还可以使用 for/in 循环处理稀疏数组。循环每次将一个可枚举的属性名(包括数组索引)赋值给循环变量。不存在的索引将不会遍历到:

forvar index in sparseArray){
    var value=sparseArray[index]//此处可以使用索引和值做一些事情
}
1
2
3
4

  for/in 循环能够枚举继承的属性名,如添加到 Array.prototype 中的方法。由于这个原因,在数组上不应该使用 for/in 循环,除非使用额外的检测方法来过滤不想要的属性。如下检测代码取其一即可:

forvar i in a){
    if!a.hasownProperty(i))continue//跳过继承的属性
    //循环体
}
1
2
3
4
forvar i in a){
    //跳过不是非负整数的i
    if(String (Math.floor(Math.abs(Number(i))))!==i)continue}
1
2
3
4

  ECMAScript 规范允许 for/in 循环以不同的顺序遍历对象的属性。通常数组元素的遍历实现是升序的,但不能保证一定是这样的。特别地,如果数组同时拥有对象属性和数组元素,返回的属性名很可能是按照创建的顺序而非数值的大小顺序。如何处理这个问题的实现各不相同,如果算法依赖于遍历的顺序,那么最好不要使用 for/in 而用常规的 for 循环。
  ECMAScript5 定义了一些遍历数组元素的新方法,按照索引的顺序按个传递给定义的一个函数。这些方法中最常用的就是 forEach()方法:



var data=[12345]//这是需要遍历的数组
var sumOfSquares=0//要得到数据的平方和
data.forEach(function(x){//把每个元素传递给此函数
                sumOfSquares+=x*x;//平方相加
});
SumOf5quares //=)55:1+4+9+16+25
1
2
3
4
5
6
7
8

多维数组(掌握)

  JavaScript 不支持真正的多维数组,但可以用数组的数组来模似。访问数组的数组中的元素,只要简单地使用两次[]操作符即可。这里有一个具体的例子,它使用二维数组作为一个九九乘法表:

//创建一个多维数组
var table=new Array10);//表格有10行
for(vari=o;i<table.length;i++{
    table[i]=new Array10);//每行有10列
}
//初始化数组
forvar row=0;row< table.length;row++{
    for(col=o;col<table[row].length;col++{
        table[row][col]=row*col;
    }
}
//使用多维数组来计算(查询)5*7
var product=table[5][7]//35
1
2
3
4
5
6
7
8
9
10
11
12
13

数组方法(掌握)

ECMAScript 3

join()转化为字符串

  Array.join()方法将数组中所有元素都转化为字符串并连接在一起,返回最后生成的字符串。可以指定一个可选的字符串在生成的字符串中来分隔数组的各个元素。如果不指定分隔符,默认使用逗号。如以下代码所示:

var a=[123]//创建一个包含三个元素的数组
a.join();//=>"1,2,3"
a.join("");//=>"123"
a.join("");//=>"123"
var b=new Array10);//长度为10的空数组
b.join('-'//=-:9个连字号组成的字符串
1
2
3
4
5
6

  Array.join()方法是 String.split()方法的逆向操作,后者是将字符串分割成若干块来创建一个数组。

reverse()颠倒顺序

  Array.reverse()方法将数组中的元素颠倒顺序,返回逆序的数组。它采取了替换;换句话说,它不通过重新排列的元素创建新的数组,而是在原先的数组中重新排列它们。

var a=[123];
a.reverse().join()//=>"3,2,1",并且现在的a是[3,2,1]
1
2

sort()元素排序

  Array.sort()方法将数组中的元素排序并返回排序后的数组。当不带参数调用 sort()时,数组元素以字母表顺序排序(如有必要将临时转化为字符串进行比较):

var a = new Array('banana', 'cherry', 'apple');
a.sort();
var s = a.join(','); //s=="apple, banana, cherry"
1
2
3

  如果数组包含 undefined 元素,它们会被排到数组的尾部。
  为了按照其他方式而非字母表顺序进行数组排序,必须给 sort()方法传递一个比较函数。该函数决定了它的两个参数在排好序的数组中的先后顺序。假设第一个参数应该在前,比较函数应该返回一个小于 0 的数值。反之,假设第一个参数应该在后,函数应该返回一个大于 0 的数值。并且,假设两个值相等(也就是说,它们的顺序无关紧要),函数应该返回 0。因此,例如,用数值大小而非字母表顺序进行数组排序,代码如下:

var a=[3341111222];
a.sort();//字母表顺序:1111,222,33,4
a.sort(function(a,b){//数值顺序:4,33,222,1111
        return a-b;//根据顺序,返回负数、0、正数
        });
a.sort(function(a,b){return b-a};//数值大小降序
1
2
3
4
5
6

  另外一个数组元素排序的例子,也许需要对一个字符串数组执行不区分大小写的字母表排序,比较函数首先将参数都转化为小写字符串(使用 toLowerCase()方法),再开始比较:

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-1if(a>b)return 1return o;
        });//=>I'ant',Bug','cat',0og']
1
2
3
4
5
6
7
8
9

concat()连接数组

  Array.concat()方法创建并返回一个新数组,它的元素包括调用 concat()的原始数组的元素和 concat()的每个参数。如果这些参数中的任何一个自身是数组,则连接的是数组的元素,而非数组本身。但要注意,concat()不会递归扁平化数组的数组。concat()也不会修改调用的数组。下面有一些示例:

var a=[123];
a.concat(45//返回[1,2,3,4,5]
a.concat([45]);//返回[1,2,3,4,5]
a.concat([45][67]//返回[1,2,3,4,5,6,7]
a.concat(4[5[67]]//返回[1,2,3,4,5,[6,7]]
1
2
3
4
5

slice()截取指定元素

  Array.slice()方法返回指定数组的一个片段或子数组。它的两个参数分别指定了片段的开始和结束的位置。返回的数组包含第一个参数指定的位置和所有到但不含第二个参数指定的位置之间的所有数组元素。
  如果只指定一个参数,返回的数组将包含从开始位置到数组结尾的所有元素。如参数中出现负数,它表示相对于数组中最后一个元素的位置。例如,参数-1 指定了最后一个元素,而-3 指定了倒数第三个元素。注意,slice()不会修改调用的数组。下面有一些示例:

var a=[12345];
a.slice(o,3);//返回[1,2,3]
a.slice(3);//返回[4,5]
a.slice(1-1);//返回[2,3,4]
a.slice(-3-2);//返回[3]
1
2
3
4
5

splice()插入或删除指定元素

  Array.splice()方法是在数组中插入或删除元素的通用方法。不同于 slice()和 concat(),splice()会修改调用的数组。
  splice()能够从数组中删除元素、插入元素到数组中或者同时完成这两种操作。在插入或删除点之后的数组元素会根据需要增加或减小它们的索引值,因此数组的其他部分仍然保持连续的。
  splice()的第一个参数指定了插入和(或)删除的起始位置。第二个参数指定了应该从数组中删除的元素的个数。如果省略第二个参数,从起始点开始到数组结尾的所有元素都将被删除。splice()返回一个由删除元素组成的数组,或者如果没有删除元素就返回一个空数组。

var a=[12345678];
a.splice(4);//返回[5,6,7,8];a是[1,2,3,4]
a.splice(12);//返回[2,3];a是[1,4]
a.splice(11);//返回[4];a是[1]
1
2
3
4

  splice()的前两个参数指定了需要删除的数组元素。紧随其后的任意个数的参数指定了需要插入到数组中的元素,从第一个参数指定的位置开始插入。

var a=[12345];
a.splice(20'a''b');//返回[];a是[1,2,a','b',3,4,5]
a.splice(22[12]3);//返回['a’,'b];a是[1,2,[1,2],3,3,4,5]
1
2
3

  注意,区别于 concat(),splice()会插入数组本身而非数组的元素。

push()和 pop()数组尾部的插入或删除

  push()和 pop()方法允许将数组当做栈来使用。push()方法在数组的尾部添加一个或多个元素,并返回数组新的长度。
  pop()方法则相反:它删除数组的最后一个元素,减小数组长度并返回它删除的值。注意,两个方法都修改并替换原始数组而非生成一个修改版的新数组。组合使用 push()和 pop()能够用 JavaScript 数组实现先进后出的栈。例如:

var stack=[]//stack:[]
stack.push(12);//stack:[1,2]返回2
stack.pop();//stack:[1]返回2
stack.push(3);//stack:[1,3]返回2
stack.pop();//stack:[1]返回3
stack.push([45]);//stack:[1,[4,5]]返回2
stack.pop()//stack:[1]返回[4,5]
stack.pop();//stack:[]返回1
1
2
3
4
5
6
7
8

unshift()和 shift()数组头部的插入或删除

  unshift()在数组的头部添加一个或多个元素,并将已存在的元素移动到更高索引的位置来获得足够的空间,最后返回数组新的长度。
  shift()删除数组的第一个元素并将其返回,然后把所有随后的元素下移一个位置来填补数组头部的空缺。

var a=[]//a:[]
a.unshift(1);//a:[1]返回:1
a.unshift(22);//a:[22,1]返回:2
a.shift();//a:[1]返回:22
a.unshift(3[45]);//a:[3,[4,5],1]返回:3
a.shift();//a:[[4,5],1]返回:3
a.shift();//a:[1]返回:[4,5]
a.shift();//a:[]返回:1
1
2
3
4
5
6
7
8

  注意,当使用多个参数调用 unshift()时它的行为令人惊讶。参数是一次性插入的(就像 splice()方法)而非一次一个地插入。这意味着最终的数组中插入的元素的顺序和它们在参数列表中的顺序一致。而假如元素是一次一个地插入,它们的顺序应该是反过来的。

toString()和 toLocaleString()

  数组和其他 JavaScript 对象一样拥有 toString()方法。针对数组,该方法将其每个元素转化为字符串(如有必要将调用元素的 toString()方法)并且输出用逗号分隔的字符串列表。注意,输出不包括方括号或其他任何形式的包裹数组值的分隔符。例如:

[123].toString()//生成‘1,2,3’
["a""b""c"].toString()//生成‘a,b,c'
[1[2'c']].tostring()//生成’1,2,c'
1
2
3

  注意,这里与不使用任何参数调用 join()方法返回的字符串是一样的。
  toLocaleString()是 toString()方法的本地化版本。它调用元素的 toLocaleString()方法将每个数组元素转化为字符串,并且使用本地化(和自定义实现的)分隔符将这些字符串连接起来生成最终的字符串。

ECMAScript 5

  在开始详细介绍之前,很有必要对 ECMAScript5 中的数组方法做一个概述。首先,大多数方法的第一个参数接收一个函数,并且对数组的每个元素(或一些元素)调用一次该函数。如果是稀疏数组,对不存在的元素不调用传递的函数。
  在大多数情况下,调用提供的函数使用三个参数:数组元素、元素的索引和数组本身。通常,只需要第一个参数值,可以忽略后两个参数。
  大多数 ECMAScript5 数组方法的第一个参数是一个函数,第二个参数是可选的。如果有第二个参数,则调用的函数被看做是第二个参数的方法。也就是说,在调用函数时传递进去的第二个参数作为它的 this 关键字的值来使用。被调用的函数的返回值非常重要,但是不同的方法处理返回值的方式也不一样。
  ECMAScript5 中的数组方法都不会修改它们调用的原始数组。当然,传递给这些方法的函数是可以修改这些数组的。

forEach()数组遍历

  forEach()方法从头至尾遍历数组,为每个元素调用指定的函数。如上所述,传递的函数作为 forEach()的第一个参数。然后 forEach()使用三个参数调用该函数:数组元素、元素的索引和数组本身。如果只关心数组元素的值,可以编写只有一个参数的函数—一额外的参数将忽略:

var data=[12345]//要求和的数组
//计算数组元素的和值
var sum=0//初始为0
data.forEach(function(value){ sum += value;});//将每个值累加到sum上
sum//=>15
//每个数组元素的值自加1
data.forEach(function(v,i,a){ a[i]=v+1});
data//=>[2,3,4,5,6]
1
2
3
4
5
6
7
8

  注意,forEach()无法在所有元素都传递给调用的函数之前终止遍历。也就是说,没有像 for 循环中使用的相应的 break 语句。如果要提前终止,必须把 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");
1
2
3
4
5
6
7

map()遍历并返回新数组

  map()方法将调用的数组的每个元素传递给指定的函数,并返回一个数组,它包含该函数的返回值,例加。

a=[123];
b=a.map(function(x){return x*x;});//b是[1,4,9]
1
2

  传递给 map()的函数的调用方式和传递给 forEach()的函数的调用方式一样。但传递给 map()的函数应该有返回值。
  注意,map()返回的是新数组:它不修改调用的数组。如果是稀疏数组,返回的也是相同方式的稀疏数组:它具有相同的长度,相同的缺失元素。

filter()遍历并返回运算为 true 的元素数组

  fliter()方法返回的数组元素是调用的数组的一个子集。传递的函数是用来逻辑判定的:该函数返回 true 或 false。调用判定函数就像调用 forEach()和 map()一样。如果返回值为 true 或能转化为 true 的值,那么传递给判定函数的元素就是这个子集的成员,它将被添加到一个作为返回值的数组中。

a=[54321];
smallvalues =a.filter(function(x){ return x<3});//[2,1]
everyother=a.filter(function(x,i){return i%2==0 });//[5,3,1]
1
2
3

  注意,filter()会跳过稀疏数组中缺少的元素,它的返回数组总是稠密的。为了压缩稀疏数组的空缺,代码如下:

var dense=sparse.filter(function(){ return true});
1

  甚至,压缩空缺并删除 undefined 和 null 元素,可以这样使用 filter():

a=a.filter(function(x){return x!==undefined 8& x!=null});
1

every()和 some()遍历并判断元素返回 true 或 false

  every()和 some()方法是数组的逻辑判定:它们对数组元素应用指定的函数进行判定,返回 true 或 false。
  every()方法就像数学中的“针对所有”的量词 V:当且仅当针对数组中的所有元素调用判定函数都返回 true,它才返回 true:

a=[12345];
a.every(function(x){return x<10}//=>true:所有的值<10
a.every(function(x){ returnx%2===0}//=>false:不是所有的值都是偶数
1
2
3

  some()方法就像数学中的“存在”的量词:当数组中至少有一个元素调用判定函数返回 true,它就返回 true;并且当且仅当数值中的所有元素调用判定函数都返回 false,它才返回 false:

a=[12345];
a.some(function(x){ return x%2===0}//=>true:a含有偶数值
a.some(isNaN)//=>false:a不包含非数值元素
1
2
3

  注意,一旦 every()和 some()确认该返回什么值它们就会停止遍历数组元素。some()在判定函数第一次返回 true 后就返回 true,但如果判定函数一直返回 false,它将会遍历整个数组。every()恰好相反:它在判定函数第一次返回 false 后就返回 false,但如果判定函数一直返回 true,它将会遍历整个数组。
  注意,根据数学上的惯例,在空数组上调用时,every()返回 true,some()返回 false。

reduce()和 reduceRight()遍历元素进行组合并生成单个值

reduce()

  reduce()和 reduceRight()方法使用指定的函数将数组元素进行组合,生成单个值。这在函数式编程中是常见的操作,也可以称为“注入”和“折叠”。举例说明它是如何工作的:

var a=[12345]
var sum=a.reduce(function(x,y){ return x+y}0);//数组求和
var product=a.reduce(function(x,y){return x*y}1);//数组求积
var max=a.reduce(function(x,y){ return(x>y)?x:y;});//求最大值
1
2
3
4

  reduce()需要两个参数。第一个是执行化简操作的函数。化简函数的任务就是用某种方法把两个值组合或化简为一个值,并返回化简后的值。在上述例子中,函数通过加法、乘法或取最大值的方法组合两个值。第二个(可选)的参数是一个传递给函数的初始值。
  可能已经注意到了,上面第三次调用 reduce()时只有一个参数:没有指定初始值。当不指定初始值调用 reduce()时,它将使用数组的第一个元素作为其初始值。这意味着第一次调用化简函数就使用了第一个和第二个数组元素作为其第一个和第二个参数。在上面求和与求积的例子中,可以省略初始值参数。
  reduce()使用的函数与 forEach()和 map()使用的函数不同。比较熟悉的是,数组元素、元素的索引和数组本身将作为第 2~4 个参数传递给函数。第一个参数是到目前为止的化简操作累积的结果。
  第一次调用函数时,第一个参数是一个初始值,它就是传递给 reduce()的第二个参数。在接下来的调用中,这个值就是上一次化简函数的返回值。
  在空数组上,不带初始值参数调用 reduce()将导致类型错误异常。如果调用它的时候只有一个值——数组只有一个元素并且没有指定初始值,或者有一个空数组并且指定一个初始值—reduce()只是简单地返回那个值而不会调用化简函数。

reduceRight()

  reduceRight()的工作原理和 reduce()一样,不同的是它按照数组索引从高到低(从右到左)处理数组,而不是从低到高。如果化简操作的优先顺序是从右到左,你可能想使用它,例如:

var a=[234]
//计算2 ^(3^4)。乘方操作的优先顺序是从右到左
var big=a.reduceRight(function(accumulator,value){
                        return Math.pow(value,accumulator);
                    });
1
2
3
4
5

  注意,reduce()和 reduceRight()都能接收一个可选的参数,它指定了化简函数调用时的 this 关键字的值。可选的初始值参数仍然需要占一个位置。如果想让化简函数作为一个特殊对象的方法调用,请参看 Function.bind()方法。
  为了简单起见,到目前位置所展示的例子都是数值的,但数学计算不是 reduce()和 reduceRight()的唯一意图。

indexOf()和 lastindexOf()搜索数组返回索引

  indexOf()和 lastIndexOf()搜索整个数组中具有给定值的元素,返回找到的第一个元素的索引或者如果没有找到就返回-1。indexOf()从头至尾搜索,而 lastIndexOf()则反向搜索。

a=[o,1210];
a.indexOf(1//=>1:a[1]是1
a.lastIndexOf(1//=>3:a[3]是1
a.indexOf(3//=>-1:没有值为3的元素
1
2
3
4

  不同于本节描述的其他方法,indexOf()和 1astIndexOf()方法不接收一个函数作为其参数。第一个参数是需要搜索的值,第二个参数是可选的:它指定数组中的一个索引,从那里开始搜索。如果省略该参数,indexOf()从头开始搜索,而 lastIndexOf()从末尾开始搜索。第二个参数也可以是负数,它代表相对数组末尾的偏移量,例如,-1 指定数组的最后一个元素。
  如下函数在一个数组中搜索指定的值并返回包含所有匹配的数组索引的一个数组。它展示了如何运用 indexof()的第二个参数来查找除了第一个以外匹配的值。

//在数组中查找所有出现的x,并返回一个包含匹配索引的数组
function findall(a,x){
    var results=[]//将会返回的数组
    len=a.length,//待搜索数组的长度
    pos=0//开始搜索的位置
    while(pos<len){//循环搜索多个元素..
        pos=a.indexoOf(x,pos);//搜索
    if(pos===-1break//未找到,就完成搜索
        results.push(pos);//否则,在数组中存储索引
        pos=pos+1//并从下一个位置开始搜索
        return results;//返回包含索引的数组
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13

数组类型(掌握)

  我们在本章中到处都可以看见数组是具有特殊行为的对象。给定一个未知的对象,判定它是否为数组通常非常有用。在 ECMAScript5 中,可以使用 Array.isArray()函数来做这件事情:

Array.isArray([]); //=>true
Array.isArray({}); //=>false
1
2

  instanceof 操作符只能用于简单的情形:使用 instanceof 的问题是在 Web 浏览器中有可能有多个窗口或窗体(frame)存在。每个窗口都有自己的 JavaScript 环境,有自己的全局对象。并且,每个全局对象有自己的一组构造函数。因此一个窗体中的对象将不可能是另外窗体中的构造函数的实例。窗体之间的混淆不常发生,但这个问题足已证明 instanceof 操作符不能视为一个可靠的数组检测方法。

[] instanceof
  Array(
    //=>true
    {}
  ) instanceof
  Array; //=>false
1
2
3
4
5
6

  解决方案是检查对象的类属性。对数组而言该属性的值总是“Array”,因此在 ECMAScript3 中 isArray()函数的代码可以这样书写:

var isArray=Function.isArray 1| function(o){
    return typeof o==="object"8&
    Object.prototype.tostring.call(o)==="[Object Array]"}
1
2
3
4

  实际上,此处类属性的检测就是 ECMAScript5 中 Array.isArray()函数所做的事情。获得对象类属性的技术使用了 Object.prototype.toString()方法。

类数组对象(掌握)

  一种常常完全合理的看法把拥有一个数值 length 属性和对应非负整数属性的对象看做一种类型的数组。实践中这些“类数组”对象实际上偶尔出现,虽然不能在它们之上直接调用数组方法或者期望 length 属性有什么特殊的行为,但是仍然可以用针对真正数组遍历的代码来遍历它们。结论就是很多数组算法针对类数组对象工作得很好,就像针对真正的数组一样。如果算法把数组看成只读的或者如果它们至少保持数组长度不变,也尤其是这种情况。
  以下代码为一个常规对象增加了一些属性使其变成类数组对象,然后遍历生成的伪数组的“元素”:

var a={}//从一个常规空对象开始
//添加一些属性,称为“类数组"
var i=0while(i<10{
    a[i]=i*i;
    i++}
a.length=i;
//现在,当做真正的数组遍历它
var total=0for(varj=0;j<a.length;j++)
    total+=a[j]
1
2
3
4
5
6
7
8
9
10
11
12

  Arguments 对象就是一个类数组对象。在客户端 JavaScript 中,一些 DOM 方法(如 document.getElementsByTagName())也返回类数组对象。下面有一个函数可以用来检测类数组对象:

//判定。是否是一个类数组对象
//字符串和函数有1ength属性,但是它们
//可以用typeof检测将其排除。在客户端Java5cript中,DOM文本节点
//也有1ength属性,需要用额外判断o.nodeType!=3将其排除
function isArraylike(o){
    if(o8&//o非null、undefined等
        typeof o==="object"8&//o是对象isFinite(o.length)8&//o.1ength是有限数值
        o.1ength>=0&&//o.length为非负值
        o.1ength===Math.floor(o.1ength)&&//o.1ength是整数
        o.length<4294967296//o.length<232
        return true//o是类数组对象
    else
        return false//否则它不是
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

  类似上述的类数组对象的检测方法针对字符串常常返回 false——它们通常最好当做字符串处理,而非数组。
  JavaScript 数组方法是特意定义为通用的,因此它们不仅应用在真正的数组而且在类数组对象上都能正确工作。在 ECMAScript5 中,所有的数组方法都是通用的。在 ECMAScript3 中,除了 toString()和 tolocaleString()以外的所有方法也是通用的。(concat()方法是一个特例:虽然可以用在类数组对象上,但它没有将那个对象扩充进返回的数组中。)既然类数组对象没有继承自 Array.prototype,那就不能在它们上面直接调用数组方法。尽管如此,可以间接地使用 Function.cal1 方法调用:

var a={"o""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"]:
1
2
3
4
5
6

  ECMAScript 5 数组方法是在 Firefox1.5 中引入的。由于它们的写法的一般性,Firefox 还将这些方法的版本在 Array 构造函数上直接定义为函数。使用这些方法定义的版本,上述例子就可以这样重写:

var a={"o""a",“1":"b",“2""c",length:3}//类数组对象
Array.join(a,"+")
Array.slice(a,0)
Array.map(a,function(x){ return x.toUpperCase();}
1
2
3
4

  当用在类数组对象上时,数组方法的静态函数版本非常有用。但既然它们不是标准的,不能期望它们在所有的浏览器中都有定义。可以这样书写代码来保证使用它们之前是存在的:

Array.join =
  Array.join ||
  function(a, sep) {
    return Array.prototype.join.call(a, sep);
  };
Array.slice =
  Array.slice ||
  function(a, from, to) {
    return Array.prototype.slice.call(a, from, to);
  };
Array.map =
  Array.map ||
  function(a, f, thisArg) {
    return Array.prototype.map.call(a, f, thisArg);
  };
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

作为数组的字符串(掌握)

  在 ECMAScript5(在众多最近的浏览器实现——包括 IE8——早于 ECMAScript 5)中,字符串的行为类似于只读的数组。除了用 charAt()方法来访问单个的字符以外,还可以使用方括号:

var s = 'test';
s.charAt(o); //=>"t"
s[1]; //=>"e"
1
2
3

  可索引的字符串的最大的好处就是简单,用方括号代替了 charAt()调用,这样更加简洁、可读并且可能更高效。不仅如此,字符串的行为类似于数组的事实使得通用的数组方法可以应用到字符串上。例如:

s="JavaScript"
Array.prototype.join.call(s,""//=)"JavaScript"
Array.prototype.filter.call(s,//过滤字符串中的字符
    function(x){
        return x.match(/[^aeiou]/);//只匹配非元音字母
    }.join(""//=)"jvScrpt"
1
2
3
4
5
6

  请记住,字符串是不可变值,故当把它们作为数组看待时,它们是只读的。如 push()、sort()、reverse()和 splice()等数组方法会修改数组,它们在字符串上是无效的。不仅如此,使用数组方法来修改字符串会导致错误:出错的时候没有提示。

TOC