数组的遍历及归并方法简要总结 reduce() reduceRight() forEach() map() filter() every() some()等

数组是出现频率最高的数据结构之一, 而遍历是对数组作的最多的操做. 能够用来对进行遍历的函数有不少, 并且每一个函数都有各自的适用情景, 要作根据不一样的需求中选择最合理的函数, 必须先对这些函数各自的特色有所了解.数组

本文涉及的数组遍历相关的函数

首先将文中所讨论的全部方法列出, 以便有总体印象. 这些方法能够分为两种, 分别是:数据结构

  • 数组的遍历方法, 共 7 个:函数

    1. forEach()
    2. map()
    3. every()
    4. some()
    5. filter()
    6. find()
    7. findIndex()
  • 数组的归并方法, 共 2 个:ui

    1. reduce()
    2. reduceRight()

下面依次讨论并比较所列出方法各自的功能和特色.this

数组的遍历方法

上面提到的 7 种数组的遍历方法都接受 2 个参数 (callback, thisArg):spa

  1. 第一个是回调函数 callback , 会对数组的每个元素都执行这个函数. 回调函数接受三个参数: 正在处理的当前元素( currentElement )、正在处理的当前元素的索引( currentIndex )、当前正在被操做的数组( currentArray ).code

  2. 第二个参数是 thisArg, 是给 callback 函数指定的 this. 这个参数是可选的, 若是不指定, 则默认是 undefined.索引

下面具体讨论每一个方法的特色.ip

forEach() 函数

这个函数是比较常见的, 它不返回任何值, 只对数组的每一个元素都执行回调函数. 就像上面说的, 回调函数的参数是数组里的元素、索引和这个数组自己, 例如访问数组的每个元素和索引:字符串

let array = ['a', 'b', 'c'];
array.forEach(function(curElement, curIndex, curArray){
    console.log('索引为 ' + curIndex + ' 的元素值是 ' + curElement);
});

// 索引为 0 的元素值是 a
// 索引为 1 的元素值是 b
// 索引为 2 的元素值是 c
复制代码

几个须要注意的状况

  • 当数组的某个位置没有值时, 则这个位置会被跳过. 但值是 undefinednull 的位置不会被当作空, 例如:
// 设置数组的第二个元素为空, 第 三、 4 个元素是 null 和 undefined
let array = ['a', , null, undefined, 'c'];  

array.forEach(function(curElement, curIndex, curArray){ // forEach 遍历这个数组
    console.log('索引为 ' + curIndex + ' 的元素值是 ' + curElement);
});

/* 输出的结果中代表跳过了 1 位置的元素, 可是并无 跳过值为 undefined 和 null 的位置 索引为 0 的元素值是 a 索引为 2 的元素值是 null 索引为 3 的元素值是 undefined 索引为 4 的元素值是 c */
复制代码
  • 遍历过程当中没有办法停止或者跳出 forEach() 循环, 除了经过抛出一个异常来退出。 若是确实须要这样作, 那么使用 forEach() 并非合适的方法, 其余几种遍历方法中有很是适合这个需求的.

map() 函数

map() 方法对数组的每一个元素都运行回调函数, 而后用回调函数的返回值组成一个新数组返回. 也就是说 map() 方法的回调函数须要明确指定返回值是什么. 这就和上面的 forEach() 方法不一样了: forEach() 的回调函数并不显式的返回任何值.

经过上面的描述能够知道 map() 方法通常用于对数组的每一个元素进行二次加工(映射), 可是又不但愿去改变原来数组, 因此返回一个新的数组.

例如, 想获得数组中的元素都乘上2以后的结果, 使用 map 方法就很合适了:

let oldArr = [1, 2, 3];
let newArr = oldArr.map(function(curElement){
    return curElement * 2;
});

console.log(newArr); 
// Array(3) [2, 4, 6], 能够看到 newArr 中的元素都是 oldArr 的元素乘上2以后的结果

console.log(oldArr);
// Array(3) [1, 2, 3] , 原来的数组并无被修改
复制代码

或者求每一个元素值的平方根

let numbers = [1, 4, 9];
let roots = numbers.map(Math.sqrt); // 将自带的函数做为回调函数传给 map 
// roots的值为[1, 2, 3], numbers的值仍为[1, 4, 9]
复制代码

注意: 回调函数的三个参数(curElement, curIndex, curArray)中只有第一个参数 curElement 是必需要指定的, 其余两个参数能够不指定, 可是实际上依然会被传入回调函数中. 这听起来很绕, 能够经过下面的一个在网上流传很广的题目来了解这个知识点.

一道关于 map 函数的著名题目

常常在网上见到这个题目露面, 并且答案看起来很诡异, 不过经过这个题目能够了解到 map 函数的一个知识点.

题目是: 下面这段代码会输出什么

console.log(["1", "2", "3"].map(parseInt));
复制代码

答案是并非想象中的 [1, 2, 3] , 而是 [1, NaN, NaN]. 下面来分析答案为何是这个.

先明确代码["1", "2", "3"].map(parseInt)中的主角都有哪些, 从后向前看:

  1. parseInt 函数: 这个函数的做用是解析出来一个字符串中的整数, 它接受两个参数: 要解析的字符串和基数, 这个基数表示想要以哪一个进制来解析这个字符串. 基数若是不指定, 则默认为 10, 即按照 十进制 来解析字符串中的整数. 在上面的代码中 parseInt 做为 map 的回调函数.

  2. map() 函数, 它会向回调函数传入三个参数, 即便在咱们只指定一个参数名的时候, 另外的两个参数也会隐式的向回调函数中传入.

  3. ["1", "2", "3"]数组, map 函数的调用者.

在明确了三个主角以后, 下面要作的就是理清主角之间的关系: 数组 --调用--> map函数, parseInt 做为 map 的回调函数被调用.

关键点来了, map 会向回调函数 parseInt 传入三个参数: 当前元素,当前索引和当前数组. 那么这时的 parseInt 函数就能够看作:

parseInt(curElement, curIndex, curArray)
复制代码

然而 parseInt 最多接受两个参数, 那么第三个参数(当前数组)就被忽略了 . 因而 parseInt 最终变成 parseInt(curElement, curIndex) 这种调用形式.

这样整个题目就变成了:

console.log(["1", "2", "3"].map(parseInt(curElement, curIndex)));
复制代码

每一个元素的 curIndex 做为基数. 将循环拆开, 能够依次看出 parseInt 对每一个元素进行的操做:

[ parseInt('1', 0), parseInt('2', 1), parseInt('3', 2)];  
// [1 NaN NaN]
复制代码

经过以上就能够理解这个题目的答案是怎么来的了. 固然还要了解关于 parseInt 函数的机制, 这是另一个话题, 再讨论起来就偏题了. 具体参见 MDN

顾名思义的 every()some() 函数

这两个函数都用回调函数检测数组中的元素是否知足给定的条件, 返回一个布尔值.

对于 every() 来讲, 只有每一个元素都知足回调函数中定义的条件, 才会返回 true, 不然返回 false.

而对于 some() 来讲, 只要有一个元素知足条件, 函数就会返回 true, 不然返回 false.

例如, 检测一个数组中是否全部数值都 > 10:

let arr = [20, 30, 40];  // 定义一个元素都 > 10 的数组

let boolValue = arr.every(function(curElement, curIndex, curArray){
    return curElement > 10;  // 回调函数返回当前元素是否知足 > 10 的布尔值
});
console.log(boolValue);  // true
复制代码

注意: 空数组调用every这个函数会返回 true.

再例如, 检测一个数组中是否是存在 > 10 的元素:

let arr = [2,3,40];

let boolValue = arr.some(function(curElement, curIndex, curArray){
    return curElement > 10; // 回调函数返回当前元素是否知足 > 10 的布尔值
});

console.log(boolValue);  // true
复制代码

注意1: 空数组调用some这个函数会返回 false.

注意2: some 会从前日后遍历数组, 一旦找到一个知足条件的元素, 就会返回 true, 中止遍历, 并不会再遍历后面的数组. 验证代码以下:

[2, 3, 40, 50].some(function(curElement, curIndex, curArray){
    console.log(curIndex); // 当前元素的 位置
    return curElement > 2; // 返回当前元素是否知足 > 10 这个条件
});

// 输出: 0 1 , 能够看到只访问到了 1 位置, 因为 1 位置的元素 3 > 2, 因此就不向后继续遍历了
复制代码

从上面的输出中能够知道 some 在遍历到第 1 个位置时找到了知足条件的数组项, 函数就中止了执行, 再也不遍历以后的数组.

一样顾名思义的 filter() 函数

filter 有过滤的意思, 顾名思义, 这个函数会返回知足回调函数的全部元素所组成的新数组. 若是全部元素都不知足, 则返回一个 空数组.

例如, 返回数组中全部 > 10 的元素:

let arr = [2, 3, 40]; // 建立只有一个元素 > 10 的数组

let newArr = arr.filter(function(curElement, curIndex, curArray){
    return curElement > 10; // 回调函数返回当前元素是否知足 > 10 的布尔值
});

console.log(newArr); // [40], 只有 40 > 10, 则返回的数组中只包含 40 
复制代码

数组的查找方法 find()findIndex() 函数

find() 函数返回数组中知足回调函数条件的第一个元素. 若是没有元素知足条件就返回 undefined.

findIndex() 函数返回数组中知足回调函数条件的第一个元素的索引, 若是没有元素知足条件就返回 -1.

例如, 想找到数组中第一个 > 10 的元素用 find, 想找到这个元素的位置用 findIndex:

let arr = [2, 3, 40, 50]; // 数组中第一个 > 10 的元素是 40, 索引是 2

let item = arr.find(function(curElement, curIndex, curArray){
    return curElement > 10; // 返回当前元素是否知足 > 10 这个条件
});

let index = arr.findIndex(function(curElement, curIndex, curArray){
    return curElement > 10; // 返回当前元素是否知足 > 10 这个条件
});

console.log(item); // 40
console.log(index); // 2
复制代码

必需要知道的7个函数的共同点

这 7 种方法遍历数组的过程是按索引依次访问数组每一项的过程. 在开始遍历以前会事先肯定数组的长度 len, 从 0 位置依次忠实的访问到 len - 1 这个位置, 无论数组怎么变化, len 的值就像被 const 定义的同样---直到遍历完成以前永远不变. 然而在遍历的过程当中数组自己可能会发生变化, 例如长度变化和元素变化, 可分红如下 3 种状况: 1. 数组的长度不变, 可是其中的元素发生了变化 不管每一个元素的值怎么变化, 始终按当前的值为准

2. 数组元素增长的状况, 例如使用 `push` 方法往数组里塞进一个新的元素. 

假设遍历以前数组的长度值是 len, 则只会访问到 len - 1 位置, 不管数组增长了多少元素, len - 1 以后的位置都不会被访问到. 以 `forEach` 函数为例来讲明, 例如:
```js
let array = ['小x' , '小明', '小红'];
array.forEach(function(curElement, curIndex, curArray){
    console.log(curIndex + ' 位置是 ' + curElement); 

    // 遍历到小明的时候向数组里添加新元素
    if(curElement === '小明'){
        array.push('小新'); 
        array.push('小新新');
    }
});

/* 查看输出的结果 并无遍历到新加入的元素

0 位置是 小x
1 位置是 小明
2 位置是 小红
*/ 

// 将数组打印出来观察, 发现其中确实增长了新的元素
console.log(array);
// Array(5) ["小x", "小明", "小红", "小新", "小新新"]
```
因此, 不要尝试在遍历的过程当中向数组**后面**添加元素并期待能访问到它们.

3. 数组元素减小
会遍历到数组元素减小后的数组的最后一个位置. 例如数组原本有 n 个元素, 遍历过程当中元素个数变成了 n - 1, 则只会遍历到 n - 2 这个位置了.
复制代码

这 7 个函数接受的参数都是相同的: 回调函数和用来指定this值的参数. 可是他们的回调函数接受的参数数量可能会不一样, 好比 map 的回调函数能够不指定第2、三个参数.

简单总结上述 7 个方法

  1. forEach: 无返回值. 对每一个元素执行回调函数.
  2. map: 返回一个数组. 每一个元素执行回调函数, 返回全部由回调函数结果组成的数组.
  3. every: 返回一个布尔值. 用回调函数提供的条件判断每一个元素, 若是全部的元素都知足, 则返回 true, 不然 false.
  4. some: 返回一个布尔值. 从头至尾用回调函数提供的条件判断每一个元素, 若是有元素知足条件, 就中止循环, 返回 true, 若是数组中的元素都不知足条件, 返回 false.
  5. filter: 返回一个数组. 数组中是知足条件的元素.
  6. find: 返回一个值. 遍历数组, 返回第一个知足条件的元素值. 若是都不知足, 则返回 undefined.
  7. findIndex: 和 find 函数类似, 不过返回的是知足条件元素的索引. 不然返回 -1.

数组的归并方法, reduce()reduceRight()

这两个函数都会遍历数组并返回由回调函数计算出来的值, 不一样点仅在于这两个函数遍历数组的方向不一样, 前者从数组的第一项遍历到最后一项, 后者从最后一项遍历到第一项. 因此, 这里只讨论 reduce.

reduce()

reduce() 函数能够指定两个参数, 回调函数 callback 和一个做为初始值的 initValue.

回调函数接受四个参数, 分别是:

  1. 当前的累加值: count
  2. 当前元素: curElement
  3. 当前索引: curIndex
  4. 当前的数组: curArray

因此这个函数的完整语法能够写做以下:

reduce(function(count, curElement, curIndex, curArray){}, initValue);
复制代码

其中回调函数的第一个参数( count )的值是在访问上一个元素时的回调函数的返回值. 换句话说, 当前的回调函数的返回值会被赋下一次执行的回调函数的参数 count.

这里可能有 2 个疑惑:

  1. 在遍历开始以前, count 的值是什么?
  2. 初始值 initValue 是用来作什么的? 下面的内容会讨论这两个问题.

第二个参数 initValue 对回调函数的影响

reduce 函数中的第二个参数 initValue 是能够省略的. 但省略与否会影响回调函数中前三个参数的初始值. 具体要结合实验来讲明以下:

  • 不指定参数 initValue 的值时, 回调函数的参数 curIndex = 1, curElement 值为 array[1], count 为 arr[0]:
let arr = [2, 3]; // 建立有两个元素的数组以便观察

arr.reduce(function(count, curElement, curIndex, curArr){
    console.log(count, curElement, curIndex);  // 2 3 1
});
复制代码

能够看到 count === arr[0], curIndex === 1, curElement === arr[1]. 即从数组的第二个元素开始向后遍历.

  • 指定参数 initValue 的值时, 回调函数的参数 curIndex = 0, curElement 值为a[0], count 值为 initValue:
let arr = [2]; // 建立有一个元素的数组以便观察

arr.reduce(function(count, curElement, curIndex, curArr){
    console.log(count, curElement, curIndex);  // 100 2 0
}, 100); // 指定 initValue 为 100
复制代码

能够看到 count === initValue, curIndex === 0, curElement === arr[0]. 这时才是从数组的第一个元素开始向后遍历.

reduce 函数使用案例

上面的内容提到“ 回调函数的第一个参数( count )的值是在访问上一个元素时的回调函数的返回值. 换句话说, 当前的回调函数的返回值会被赋下一次执行的回调函数的参数 count ”. 这个特色正好能够用来求一个数组全部元素的和, 能够把 count 看作以前全部元素的总和, 把当前的值和 count 加起来就是数组到如今位置的和.

let arr = [1, 2, 3, 4, 5]; 
let sum = arr.reduce(function(count, curElement, curIndex, curArr){
    console.log('数组前 ' + curIndex + ' 个元素的和是: ' + count );
    console.log('当前是数组第 ' + (curIndex + 1) + ' 个元素, 值是: ' + curElement);
    console.log('加上当前元素后的值是: ' + (count + curElement));
    console.log('-------------------------------');

    return count + curElement; // 前面全部的和 + 当前的元素值
}); 

console.log(sum);

/* 输出 数组前 1 个元素的和是: 1 当前是数组第 2 个元素, 值是: 2 加上当前元素后的值是: 3 ------------------------------- 数组前 2 个元素的和是: 3 当前是数组第 3 个元素, 值是: 3 加上当前元素后的值是: 6 ------------------------------- 数组前 3 个元素的和是: 6 当前是数组第 4 个元素, 值是: 4 加上当前元素后的值是: 10 ------------------------------- 数组前 4 个元素的和是: 10 当前是数组第 5 个元素, 值是: 5 加上当前元素后的值是: 15 ------------------------------- 数组的和是: 15 */
复制代码

能够看到回调函数的返回值被赋给了 count 以供下次使用, 最后一个 count 就是整个数组的和.

#完.

相关文章
相关标签/搜索