首页 » 学习JavaScript数据结构与算法(第2版) » 学习JavaScript数据结构与算法(第2版)全文在线阅读

《学习JavaScript数据结构与算法(第2版)》2.7 JavaScript的数组方法参考

关灯直达底部

在JavaScript里,数组是可修改的对象,这意味着创建的每个数组都有一些可用的方法。数组很有趣,因为它们十分强大,并且相比其他语言中的数组,JavaScript中的数组有许多很好用的方法。这样就不用再为它开发一些基本功能了,例如在数据结构的中间添加或删除元素。

下面的表格中详述了数组的一些核心方法,其中的一些我们已经学习过了。

方法名

描述

concat

连接2个或更多数组,并返回结果

every

对数组中的每一项运行给定函数,如果该函数对每一项都返回true,则返回true

filter

对数组中的每一项运行给定函数,返回该函数会返回true的项组成的数组

forEach

对数组中的每一项运行给定函数。这个方法没有返回值

join

将所有的数组元素连接成一个字符串

indexOf

返回第一个与给定参数相等的数组元素的索引,没有找到则返回

1

lastIndexOf

返回在数组中搜索到的与给定参数相等的元素的索引里最大的值

map

对数组中的每一项运行给定函数,返回每次函数调用的结果组成的数组

reverse

颠倒数组中元素的顺序,原先第一个元素现在变成最后一个,同样原先的最后一个元素变成了现在的第一个

slice

传入索引值,将数组里对应索引范围内的元素作为新数组返回

some

对数组中的每一项运行给定函数,如果任一项返回true,则返回true

sort

按照字母顺序对数组排序,支持传入指定排序方法的函数作为参数

toString

将数组作为字符串返回

valueOf

toString类似,将数组作为字符串返回

我们已经学过了pushpopshiftunshiftsplice方法。下面来看表格中提到的方法。在本书接下来的章节里,编写数据结构和算法时会大量用到这些方法。

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)

  1. every方法迭代

    我们要尝试的第一个方法是everyevery方法会迭代数组中的每个元素,直到返回false

    numbers.every(isEven);  

    在这个例子里,数组numbers的第一个元素是1,它不是2的倍数(1是奇数), 因此isEven 函数返回false,然后every执行结束。

  2. some方法迭代

    下一步,我们来看some方法。它和every的行为类似,不过some方法会迭代数组的每个元素,直到函数返回true

    numbers.some(isEven);  

    在我们的例子里,numbers数组中第一个偶数是2(第二个元素)。第一个被迭代的元素是1,isEven会返回false。第二个被迭代的元素是2,isEven返回true——迭代结束。

  3. forEach方法迭代

    如果要迭代整个数组,可以用forEach方法。它和使用for循环的结果相同:

    numbers.forEach(function(x){  console.log((x % 2 == 0));});  
  4. 使用mapfilter方法

    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]

  5. 使用reduce方法

    最后是reduce方法。reduce方法接收一个函数作为参数,这个函数有四个参数:previousValuecurrentValueindexarray。这个函数会返回一个将被叠加到累加器的值,reduce方法停止执行后会返回这个累加器。如果要对一个数组中的所有元素求和,这就很有用,比如:

    numbers.reduce(function(previous, current, index){  return previous + current;});  

    输出将会是120。

     JavaScript的Array类还有另外两个重要方法:mapreduce。这两个方法名是自解释的,这意味着map方法会依照给定函数对值进行映射,而reduce方法会依照函数规约数组包含的值。这三个方法(mapfilterreduce)是我们要在第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循环来迭代数组的新做法,以及可以从数组实例得到的迭代器对象。

在后面的主题中,我们会演示所有的新功能。

  1. 使用forEach和箭头函数迭代

    箭头函数可以简化使用forEach迭代数组元素的做法。代码例子如下:

    numbers.forEach(function (x) {  console.log(x % 2 == 0);});  

    这段代码可以简化如下:

    numbers.forEach(x => {  console.log((x % 2 == 0));});  
  2. 使用for...of循环迭代

    你已经学过用for循环和forEach方法迭代数组。ES6还引入了迭代数组值的for...of循环,来看看它的用法:

    for (let n of numbers) {  console.log((n % 2 == 0) ? 'even' : 'odd');}  

     可以访问https://goo.gl/qHYAN1运行上面的示例。

  3. 使用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查看和运行示例。

    • 数组的entrieskeysvalues方法

      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属性为undefineddone属性为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查看和运行示例。

  4. 使用from方法

    Array.from方法根据已有的数组创建一个新数组。比如,要复制numbers数组,可以这样做:

    et numbers2 = Array.from(numbers);  

    还可以传入一个用来过滤值的函数,例子如下:

    let evens = Array.from(numbers, x => (x % 2 == 0));  

    上面的代码会创建一个evens数组,其中只包含numbers数组中的偶数。

     访问https://goo.gl/n4rOY4查看和运行示例。

  5. 使用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查看和运行示例。

  6. 使用fill方法

    fill方法用静态值填充数组。以下面的代码为例:

    let numbersCopy = Array.of(1, 2, 3, 4, 5, 6);  

    numbersCopy数组的length6,也就是有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填充到数组索引35的位置(不包括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查看和运行示例。

  7. 使用copyWithin方法

    copyWithin方法复制数组中的一系列元素到同一数组指定的起始位置。看看下面这个例子:

    let copyArray = [1, 2, 3, 4, 5, 6];  

    假如我们想把456三个值复制到数组前三个位置,得到[4, 5, 6, 4, 5, 6]这个数组。可以用下面的代码达到目的:

    copyArray.copyWithin(0, 3);  

    假如我们想把45两个值(位置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。也就是说返回的是负数,就说明ab小,这样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会用它排序数组。在例子里,我们声明了一个用来比较数组元素的函数,使数组按升序排序。

  1. 自定义排序

    我们可以对任何对象类型的数组排序,也可以创建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)

  2. 字符串排序

    假如有这样一个数组:

    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不在数组里)。

  1. ECMAScript 6——findfindIndex方法

    看看下面这个例子:

    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));  

    findfindIndex方法接收一个回调函数,搜索一个满足回调函数条件的值。上面的例子里,我们要从数组里找一个13的倍数。

    findfindIndex的不同之处在于,find方法返回第一个满足条件的值,findIndex方法则返回这个值在数组里的索引。如果没有满足条件的值,find会返回undefined,而findIndex返回-1

     访问https://goo.gl/2vAaCh查看和运行示例。

  2. ECMAScript 7——使用includes方法

    如果数组里存在某个元素,includes方法会返回true,否则返回false。使用includes方法的例子如下:

    console.log(numbers.includes(15));console.log(numbers.includes(20));  

    例子里的includes(15)返回trueincludes(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 输出数组为字符串

现在,我们学习最后两个方法:toStringjoin

如果想把数组里所有元素输出为一个字符串,可以用toString方法:

console.log(numbers.toString);  

12345678910111213141510这些值都会在控制台中输出。

如果想用一个不同的分隔符(比如-)把元素隔开,可以用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/-