在JavaScript里,数组是可修改的对象,这意味着创建的每个数组都有一些可用的方法。数组很有趣,因为它们十分强大,并且相比其他语言中的数组,JavaScript中的数组有许多很好用的方法。这样就不用再为它开发一些基本功能了,例如在数据结构的中间添加或删除元素。
下面的表格中详述了数组的一些核心方法,其中的一些我们已经学习过了。
方法名
描述
concat
连接2个或更多数组,并返回结果
every
对数组中的每一项运行给定函数,如果该函数对每一项都返回true
,则返回true
filter
对数组中的每一项运行给定函数,返回该函数会返回true
的项组成的数组
forEach
对数组中的每一项运行给定函数。这个方法没有返回值
join
将所有的数组元素连接成一个字符串
indexOf
返回第一个与给定参数相等的数组元素的索引,没有找到则返回
1
lastIndexOf
返回在数组中搜索到的与给定参数相等的元素的索引里最大的值
map
对数组中的每一项运行给定函数,返回每次函数调用的结果组成的数组
reverse
颠倒数组中元素的顺序,原先第一个元素现在变成最后一个,同样原先的最后一个元素变成了现在的第一个
slice
传入索引值,将数组里对应索引范围内的元素作为新数组返回
some
对数组中的每一项运行给定函数,如果任一项返回true
,则返回true
sort
按照字母顺序对数组排序,支持传入指定排序方法的函数作为参数
toString
将数组作为字符串返回
valueOf
和toString
类似,将数组作为字符串返回
我们已经学过了push
、pop
、shift
、unshift
和splice
方法。下面来看表格中提到的方法。在本书接下来的章节里,编写数据结构和算法时会大量用到这些方法。
2.7.1 数组合并
考虑如下场景:有多个数组,需要合并起来成为一个数组。我们可以迭代各个数组,然后把每个元素加入最终的数组。幸运的是,JavaScript已经给我们提供了解决方法,叫作concat
方法:
var zero = 0;var positiveNumbers = [1,2,3];var negativeNumbers = [-3,-2,-1];var numbers = negativeNumbers.concat(zero, positiveNumbers);
concat
方法可以向一个数组传递数组、对象或是元素。数组会按照该方法传入的参数顺序连接指定数组。在这个例子里,zero
将被合并到nagativeNumbers
中,然后positiveNumbers
继续被合并。最后输出的结果是-3、-2、-1、0、1、2、3。
2.7.2 迭代器函数
有时我们需要迭代数组中的元素。前面我们已经学过,可以用循环语句来处理,例如for
语句。
JavaScript内置了许多数组可用的迭代方法。对于本节的例子,我们需要数组和函数。假如有一个数组,它值是从1到15,如果数组里的元素可以被2整除(偶数),函数就返回true
,否则返回false
:
var isEven = function (x) { // 如果x是2的倍数,就返回true console.log(x); return (x % 2 == 0) ? true : false;};var numbers = [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15];
return (x % 2 == 0) ? true : false
也可以写成return (x % 2== 0)
。
用
every
方法迭代我们要尝试的第一个方法是
every
。every
方法会迭代数组中的每个元素,直到返回false
。numbers.every(isEven);
在这个例子里,数组
numbers
的第一个元素是1,它不是2的倍数(1是奇数), 因此isEven
函数返回false
,然后every
执行结束。用
some
方法迭代下一步,我们来看
some
方法。它和every
的行为类似,不过some
方法会迭代数组的每个元素,直到函数返回true
:numbers.some(isEven);
在我们的例子里,
numbers
数组中第一个偶数是2(第二个元素)。第一个被迭代的元素是1,isEven
会返回false
。第二个被迭代的元素是2,isEven
返回true
——迭代结束。用
forEach
方法迭代如果要迭代整个数组,可以用
forEach
方法。它和使用for
循环的结果相同:numbers.forEach(function(x){ console.log((x % 2 == 0));});
使用
map
和filter
方法JavaScript还有两个会返回新数组的遍历方法。第一个是
map
:var myMap = numbers.map(isEven);
数组
myMap
里的值是:[false, true, false, true, false, true, false, true, false, true, false, true, false, true, false]
。它保存了传入map
方法的isEven
函数的运行结果。这样就很容易知道一个元素是否是偶数。比如,myMap[0]
是false
,因为1不是偶数;而myMap[1]
是true
,因为2是偶数。还有一个
filter
方法。它返回的新数组由使函数返回true
的元素组成:var evenNumbers = numbers.filter(isEven);
在我们的例子里,
evenNumbers
数组中的元素都是偶数:[2, 4, 6, 8, 10, 12, 14]
。使用
reduce
方法最后是
reduce
方法。reduce
方法接收一个函数作为参数,这个函数有四个参数:previousValue
、currentValue
、index
和array
。这个函数会返回一个将被叠加到累加器的值,reduce
方法停止执行后会返回这个累加器。如果要对一个数组中的所有元素求和,这就很有用,比如:numbers.reduce(function(previous, current, index){ return previous + current;});
输出将会是120。
JavaScript的
Array
类还有另外两个重要方法:map
和reduce
。这两个方法名是自解释的,这意味着map
方法会依照给定函数对值进行映射,而reduce
方法会依照函数规约数组包含的值。这三个方法(map
、filter
和reduce
)是我们要在第11章学习的JavaScript函数式编程的基础。
2.7.3 ECMAScript 6和数组的新功能
第1章提到过,ECMAScript 6(ES6或ES2015)和ECMAScript 7(ES7或ES2016)规范给JavaScript语言带来了新的功能。
下表列出了ES6和ES7新增的数组方法。
方法
描述
@@iterator
返回一个包含数组键值对的迭代器对象,可以通过同步调用得到数组元素的键值对
copyWithin
复制数组中一系列元素到同一数组指定的起始位置
entries
返回包含数组所有键值对的@@iterator
includes
如果数组中存在某个元素则返回true
,否则返回false
。ES7新增
find
根据回调函数给定的条件从数组中查找元素,如果找到则返回该元素
findIndex
根据回调函数给定的条件从数组中查找元素,如果找到则返回该元素在数组中的索引
fill
用静态值填充数组
from
根据已有数组创建一个新数组
keys
返回包含数组所有索引的@@iterator
of
根据传入的参数创建一个新数组
values
返回包含数组中所有值的@@iterator
除了这些新的方法,还有一种用for...of
循环来迭代数组的新做法,以及可以从数组实例得到的迭代器对象。
在后面的主题中,我们会演示所有的新功能。
使用
forEach
和箭头函数迭代箭头函数可以简化使用
forEach
迭代数组元素的做法。代码例子如下:numbers.forEach(function (x) { console.log(x % 2 == 0);});
这段代码可以简化如下:
numbers.forEach(x => { console.log((x % 2 == 0));});
使用
for...of
循环迭代你已经学过用
for
循环和forEach
方法迭代数组。ES6还引入了迭代数组值的for...of
循环,来看看它的用法:for (let n of numbers) { console.log((n % 2 == 0) ? 'even' : 'odd');}
可以访问https://goo.gl/qHYAN1运行上面的示例。
使用ES6新的迭代器(
@@iterator
)ES6还为
Array
类增加了一个@@iterator
属性,需要通过Symbol.iterator
来访问。代码如下:let iterator = numbers[Symbol.iterator];console.log(iterator.next.value); // 1console.log(iterator.next.value); // 2console.log(iterator.next.value); // 3console.log(iterator.next.value); // 4console.log(iterator.next.value); // 5
然后,不断调用迭代器的
next
方法,就能依次得到数组中的值。numbers
数组中有15个值,因此需要调用15次iterator.next.value
。数组中所有值都迭代完之后,
iterator.next.value
会返回undefined
。以上代码的输出和我们接下来要讲的
numbers.value
是一样的。访问https://goo.gl/L81UQW查看和运行示例。
数组的
entries
、keys
和values
方法ES6还增加了三种从数组中得到迭代器的方法。我们首先要学习的是
entries
方法。entries
方法返回包含键值对的@@iterator
,下面是使用这个方法的代码示例:let aEntries = numbers.entries; // 得到键值对的迭代器console.log(aEntries.next.value); // [0, 1] - 位置0的值为1console.log(aEntries.next.value); // [1, 2] - 位置1的值为2console.log(aEntries.next.value); // [2, 3] - 位置2的值为3
numbers
数组中都是数字,key
是数组中的位置,value
是保存在数组索引的值。使用集合、字典、散列表等数据结构时,能够取出键值对是很有用的。这个功能会在本书后面的章节中大显身手。
keys
方法返回包含数组索引的@@iterator
,下面是使用这个方法的代码示例:let aKeys = numbers.keys; // 得到数组索引的迭代器console.log(aKeys.next); // {value: 0, done: false }console.log(aKeys.next); // {value: 1, done: false }console.log(aKeys.next); // {value: 2, done: false }
keys
方法会返回numbers
数组的索引。一旦没有可迭代的值,aKeys.next
就会返回一个value
属性为undefined
,done
属性为true
的对象。如果done
属性的值为false
,就意味着还有可迭代的值。values
方法返回的@@iterator
则包含数组的值。使用这个方法的代码示例如下:let aValues = numbers.values;console.log(aValues.next); // {value: 1, done: false }console.log(aValues.next); // {value: 2, done: false }console.log(aValues.next); // {value: 3, done: false }
记住,当前的浏览器还没有完全支持ES6所有的新功能,因此,测试这些代码最好的办法是使用Babel。访问https://goo.gl/eojEGk查看和运行示例。
使用
from
方法Array.from
方法根据已有的数组创建一个新数组。比如,要复制numbers
数组,可以这样做:et numbers2 = Array.from(numbers);
还可以传入一个用来过滤值的函数,例子如下:
let evens = Array.from(numbers, x => (x % 2 == 0));
上面的代码会创建一个
evens
数组,其中只包含numbers
数组中的偶数。访问https://goo.gl/n4rOY4查看和运行示例。
使用
Array.of
方法Array.of
方法根据传入的参数创建一个新数组。以下面的代码为例:let numbers3 = Array.of(1);let numbers4 = Array.of(1, 2, 3, 4, 5, 6);
它和下面这段代码的效果一样:
let numbers3 = [1];let numbers4 = [1, 2, 3, 4, 5, 6];
我们也可以用这个方法复制已有的数组,比如:
let numbersCopy = Array.of(...numbers4);
上面的代码和
Array.from(numbers4)
的效果是一样的,区别只是用到了第1章讲过的展开操作符。展开操作符(...
)会把numbers4
数组里的值都展开成参数。访问https://goo.gl/FoJYNf查看和运行示例。
使用
fill
方法fill
方法用静态值填充数组。以下面的代码为例:let numbersCopy = Array.of(1, 2, 3, 4, 5, 6);
numbersCopy
数组的length
是6
,也就是有6个位置。再看下面的代码:numbersCopy.fill(0);
numbersCopy
数组所有位置的值都会变成0
([0, 0, 0, 0, 0, 0]
)。我们还可以指定开始填充的索引,如下:
numbersCopy.fill(2, 1);
上面的例子里,数组中从1开始的所有位置,值都是
2
([0, 2, 2, 2, 2, 2]
)。同样,也可以指定结束填充的索引:
numbersCopy.fill(1, 3, 5);
上面的例子里,我们会把
1
填充到数组索引3
到5
的位置(不包括5
),得到的数组为[0, 2, 2, 1, 1, 2]
。创建数组并初始化值的时候,
fill
方法非常好用,就像下面这样:let ones = Array(6).fill(1);
上面的代码创建了一个长度为
6
,所有的值都是1
的数组([1, 1, 1, 1, 1, 1]
)。访问https://goo.gl/sqiHSK查看和运行示例。
使用
copyWithin
方法copyWithin
方法复制数组中的一系列元素到同一数组指定的起始位置。看看下面这个例子:let copyArray = [1, 2, 3, 4, 5, 6];
假如我们想把
4
、5
、6
三个值复制到数组前三个位置,得到[4, 5, 6, 4, 5, 6]
这个数组。可以用下面的代码达到目的:copyArray.copyWithin(0, 3);
假如我们想把
4
、5
两个值(位置3和4)复制到位置1和2,可以这样做:copyArray = [1, 2, 3, 4, 5, 6];copyArray.copyWithin(1, 3, 5);
这种情况下,会把从位置3开始到位置5结束(不包括5)的元素复制到位置1,结果是得到数组
[1, 4, 5, 4, 5, 6]
。访问https://goo.gl/hZhBE1查看和运行示例。
2.7.4 排序元素
通过本书,我们能学到如何编写最常用的搜索和排序算法。其实,JavaScript里也提供了一个排序方法和一组搜索方法。让我们来看看。
首先,我们想反序输出数组numbers
(它本来的排序是1, 2, 3, 4,…15)。要实现这样的功能,可以用reverse
方法,然后数组内元素就会反序。
numbers.reverse;
现在,输出numbers
的话就会看到[15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1]
。然后,我们用sort
方法:
numbers.sort;
然而,如果输出数组,结果会是[1, 10, 11, 12, 13, 14, 15, 2, 3, 4, 5, 6, 7, 8, 9]
。看起来不大对,是吧?这是因为sort
方法在对数组做排序时,把元素默认成字符串进行相互比较。
我们可以传入自己写的比较函数。因为数组里都是数字,所以可以这样写:
numbers.sort(function(a, b){ return a-b;});
这段代码,在b
大于a
时,会返回负数,反之则返回正数。如果相等的话,就会返回0。也就是说返回的是负数,就说明a
比b
小,这样sort
就根据返回值的情况给数组做排序。
之前的代码也可以被表示成这样,会更清晰一些:
function compare(a, b) { if (a < b) { return -1; } if (a > b) { return 1; } // a必须等于b return 0;}numbers.sort(compare);
这是因为JavaScript的sort
方法接受compareFunction
作为参数,然后sort
会用它排序数组。在例子里,我们声明了一个用来比较数组元素的函数,使数组按升序排序。
自定义排序
我们可以对任何对象类型的数组排序,也可以创建
compareFunction
来比较元素。例如,对象Person
有名字和年龄属性,我们希望根据年龄排序,就可以这么写:var friends = [ {name: 'John', age: 30}, {name: 'Ana', age: 20}, {name: 'Chris', age: 25}]; function comparePerson(a, b){ if (a.age < b.age){ return -1 } if (a.age > b.age){ return 1 } return 0;} console.log(friends.sort(comparePerson));
在这个例子里,最后会输出
Ana(20), Chris(25), John(30)
。字符串排序
假如有这样一个数组:
var names =['Ana', 'ana', 'john', 'John'];console.log(names.sort);
你猜会输出什么?答案是这样的:
["Ana", "John", "ana", "john"]
既然a在字母表里排第一位,为何
ana
却排在了John
之后呢?这是因为JavaScript在做字符比较的时候,是根据字符对应的ASCII值来比较的。例如,A、J、a、j对应的ASCII值分别是65、75、97、106。虽然在字母表里a是最靠前的,但J的ASCII值比a的小,所以排在a前面。
想了解更多关于ASCII表的信息,请访问http://www.asciitable.com/。
现在,如果给
sort
传入一个忽略大小写的比较函数,将会输出["Ana", "ana", "John", "john"]
:names.sort(function(a, b){ if (a.toLowerCase < b.toLowerCase){ return -1 } if (a.toLowerCase > b.toLowerCase){ return 1 } return 0;});
假如对带有重音符号的字符做排序的话,我们可以用
localCompare
来实现:var names2 = ['Maève', 'Maeve'];console.log(names2.sort(function(a, b){ return a.localCompare(b);}));
最后输出的结果将是
["Maeve", "Maève"]
。
2.7.5 搜索
搜索有两个方法:indexOf
方法返回与参数匹配的第一个元素的索引,lastIndexOf
返回与参数匹配的最后一个元素的索引。我们来看看之前用过的numbers
数组:
console.log(numbers.indexOf(10));console.log(numbers.indexOf(100));
在这个示例中,第一行的输出是9
,第二行的输出是-1
(因为100不在数组里)。
下面的代码会返回同样的结果:
numbers.push(10);console.log(numbers.lastIndexOf(10));console.log(numbers.lastIndexOf(100));
我们往数组里加入了一个新的元素10
,因此第二行会输出15
(数组中的元素是1到15,还有10),第三行会输出-1
(因为100不在数组里)。
ECMAScript 6——
find
和findIndex
方法看看下面这个例子:
let numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15];function multipleOf13(element, index, array) { return (element % 13 == 0) ? true : false;}console.log(numbers.find(multipleOf13));console.log(numbers.findIndex(multipleOf13));
find
和findIndex
方法接收一个回调函数,搜索一个满足回调函数条件的值。上面的例子里,我们要从数组里找一个13的倍数。find
和findIndex
的不同之处在于,find
方法返回第一个满足条件的值,findIndex
方法则返回这个值在数组里的索引。如果没有满足条件的值,find
会返回undefined
,而findIndex
返回-1
。访问https://goo.gl/2vAaCh查看和运行示例。
ECMAScript 7——使用
includes
方法如果数组里存在某个元素,
includes
方法会返回true
,否则返回false
。使用includes
方法的例子如下:console.log(numbers.includes(15));console.log(numbers.includes(20));
例子里的
includes(15)
返回true
,includes(20)
返回false
,因为numbers
数组里没有20
。如果给
includes
方法传入一个起始索引,搜索会从索引指定的位置开始:let numbers2 = [7, 6, 5, 4, 3, 2, 1];console.log(numbers2.includes(4, 5));
上面的例子输出为
false
,因为数组索引5
之后的元素不包含4
。访问https://goo.gl/tTY9bc查看和运行示例。
2.7.6 输出数组为字符串
现在,我们学习最后两个方法:toString
和join
。
如果想把数组里所有元素输出为一个字符串,可以用toString
方法:
console.log(numbers.toString);
1
、2
、3
、4
、5
、6
、7
、8
、9
、10
、11
、12
、13
、14
、15
和10
这些值都会在控制台中输出。
如果想用一个不同的分隔符(比如-
)把元素隔开,可以用join
方法:
var numbersString = numbers.join('-');console.log(numbersString);
这将输出:
1-2-3-4-5-6-7-8-9-10-11-12-13-14-15-10
如果要把数组内容发送到服务器,或进行编码(知道了分隔符,解码也很容易),这会很有用。
有一些很棒的资源可以帮助你更深入地了解数组及其方法。
第一个是w3schools的数组页面:http://www.w3schools.com/js/js_arrays.asp。
第二个是w3schools的数组方法页面:http://www.w3schools.com/js/js_array_methods.asp。
Mozilla的数组及其方法的页面也非常棒,还有不错的例子:https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array(http://goo.gl/vu1diT)。
在JavaScript项目中使用数组时,也有一些很棒的类库。
Underscore:http://underscorejs.org/
Lo-Dash:http://lodash.com/-