ES6 数组方法:告别循环,拥抱函数式编程

一、 核心迭代方法:forEach、map、filter、reduce

这些是函数式编程的基石,虽然 forEach、map、filter 在 ES5 中就已存在,但它们在 ES6 的编程范式中扮演着核心角色,我们在此一并重温并强调其重要性。

1. forEach - 单纯的迭代器

forEach 是最简单的方法,它仅仅为数组中的每个元素执行一次提供的函数,没有返回值(返回 undefined)。

场景:当你需要遍历数组并执行一些副作用(如修改外部变量、打印日志、操作 DOM)时。

javascript 复制代码
// ES5 及之前
var arr = [1, 2, 3];
for (var i = 0; i < arr.length; i++) {
  console.log(arr[i]);
}

// ES6+
const arr = [1, 2, 3];
arr.forEach(item => console.log(item));
// 输出: 1, 2, 3

注意:forEach 无法中止或跳过(除了抛出异常),如需中止,请使用 for...of 循环。

2. map - 数组变形专家

map 是使用频率最高的方法之一。它会遍历数组,对每个元素应用一个函数,并将结果收集到一个新数组中。原数组保持不变。

场景:将一种类型的数组转换为另一种类型的数组。

javascript 复制代码
const numbers = [1, 2, 3, 4];

// 将每个数字转换为它的平方
const squares = numbers.map(num => num * num);
console.log(squares); // [1, 4, 9, 16]

// 从对象数组中提取特定属性
const users = [{name: 'Alice'}, {name: 'Bob'}, {name: 'Charlie'}];
const names = users.map(user => user.name);
console.log(names); // ['Alice', 'Bob', 'Charlie']

3. filter - 数据过滤器

filter 用于筛选数组中满足条件的元素。它接受一个测试函数,返回 true 的元素会被保留到新数组中。

场景:从集合中找出符合条件的子集。

javascript 复制代码
const numbers = [1, 2, 3, 4, 5, 6];

// 筛选出所有偶数
const evens = numbers.filter(num => num % 2 === 0);
console.log(evens); // [2, 4, 6]

// 筛选出长度大于3的字符串
const words = ['spray', 'limit', 'elite', 'exuberant', 'destruction'];
const longWords = words.filter(word => word.length > 5);
console.log(longWords); // ['exuberant', 'destruction']

4. reduce - 数据聚合器

reduce 是最强大但也较难理解的方法。它将数组中的所有元素通过一个"累加器"函数最终计算为一个单一的值。

arr.reduce(callback(accumulator, currentValue[, index[, array]]), initialValue)

场景:求和、求积、扁平数组、统计频率、对象转换等。

javascript 复制代码
const numbers = [1, 2, 3, 4];

// 求和
const sum = numbers.reduce((acc, cur) => acc + cur, 0);
console.log(sum); // 10

// 求最大值
const max = numbers.reduce((acc, cur) => (acc > cur ? acc : cur), numbers[0]);
console.log(max); // 4

// 将二维数组扁平化
const flattened = [[0, 1], [2, 3], [4, 5]].reduce(
  (acc, cur) => acc.concat(cur),
  []
);
console.log(flattened); // [0, 1, 2, 3, 4, 5]

// 统计数组中每个元素出现的次数
const fruits = ['apple', 'banana', 'apple', 'orange', 'banana', 'apple'];
const count = fruits.reduce((acc, fruit) => {
  acc[fruit] = (acc[fruit] || 0) + 1;
  return acc;
}, {});
console.log(count); // { apple: 3, banana: 2, orange: 1 }

二、 ES6 新增的搜索方法:find 与 findIndex

在 ES5 中,我们使用 indexOf 和 lastIndexOf 来查找值,但它们对于引用类型或复杂条件查找无能为力。ES6 引入了 find 和 findIndex。

1. find - 查找单个元素

find 方法返回数组中满足提供的测试函数的第一个元素的值。否则返回 undefined。

javascript 复制代码
const users = [
  { id: 1, name: 'Alice', active: true },
  { id: 2, name: 'Bob', active: false },
  { id: 3, name: 'Charlie', active: true }
];

// 查找第一个 active 为 true 的用户
const activeUser = users.find(user => user.active);
console.log(activeUser); // { id: 1, name: 'Alice', active: true }

// 查找名字为 'Bob' 的用户
const bob = users.find(user => user.name === 'Bob');
console.log(bob); // { id: 2, name: 'Bob', active: false }

// 查找不存在的用户
const nobody = users.find(user => user.name === 'David');
console.log(nobody); // undefined

2. findIndex - 查找元素索引

findIndex 方法返回数组中满足提供的测试函数的第一个元素的索引。否则返回 -1。

javascript 复制代码
const numbers = [5, 12, 8, 130, 44];

// 查找第一个大于10的元素的索引
const index = numbers.findIndex(num => num > 10);
console.log(index); // 1 (因为 12 > 10,且12的索引是1)

const inactiveIndex = users.findIndex(user => !user.active);
console.log(inactiveIndex); // 1 (Bob的索引)

与 filter 的区别:filter 返回所有匹配的数组,而 find/findIndex 在找到第一个匹配项后立即返回,性能更好。

三、 存在性检查:some 与 every

这两个方法用于逻辑判断,返回布尔值。

1. some - 是否存在一个?

some 测试数组中是不是至少有一个元素通过了提供的函数测试。

javascript 复制代码
const numbers = [1, 2, 3, 4, 5];

// 数组中有没有偶数?
const hasEven = numbers.some(num => num % 2 === 0);
console.log(hasEven); // true

// 数组中有没有大于10的数?
const hasBigNumber = numbers.some(num => num > 10);
console.log(hasBigNumber); // false

2. every - 是否所有都?

every 测试数组中的所有元素是否都通过了提供的函数测试。

javascript 复制代码
const numbers = [1, 2, 3, 4, 5];

// 数组中的所有数字都大于0吗?
const allPositive = numbers.every(num => num > 0);
console.log(allPositive); // true

// 数组中的所有数字都是偶数吗?
const allEven = numbers.every(num => num % 2 === 0);
console.log(allEven); // false

四、 数组实例的新方法:includes、flat、flatMap

1. includes - 简单包含判断

includes 方法用来判断一个数组是否包含一个指定的值,如果是返回 true,否则返回 false。它比 indexOf 更语义化,并且能正确判断 NaN。

javascript 复制代码
const arr = [1, 2, 3, NaN];

// ES5 的 indexOf 问题
console.log(arr.indexOf(2) !== -1); // true
console.log(arr.indexOf(4) !== -1); // false
console.log(arr.indexOf(NaN) !== -1); // false (无法判断NaN)

// ES6 的 includes
console.log(arr.includes(2)); // true
console.log(arr.includes(4)); // false
console.log(arr.includes(NaN)); // true (正确判断)

2. flat - 数组扁平化

flat 方法会按照一个可指定的深度递归遍历数组,并将所有元素与遍历到的子数组中的元素合并为一个新数组返回。

javascript 复制代码
const arr1 = [1, 2, [3, 4]];
console.log(arr1.flat()); // [1, 2, 3, 4]

const arr2 = [1, 2, [3, 4, [5, 6]]];
console.log(arr2.flat()); // [1, 2, 3, 4, [5, 6]] (默认深度为1)
console.log(arr2.flat(2)); // [1, 2, 3, 4, 5, 6] (指定深度为2)

// 甚至可以用 Infinity 关键字来展开任意深度的嵌套数组
const arr3 = [1, [2, [3, [4, [5]]]]];
console.log(arr3.flat(Infinity)); // [1, 2, 3, 4, 5]

3. flatMap - 先映射后扁平

flatMap 方法首先使用映射函数映射每个元素,然后将结果压缩成一个新数组。它与 map 后紧接着深度为1的 flat 几乎相同,但 flatMap 通常在效率上稍高一些。

javascript 复制代码
const arr = [1, 2, 3];

// 使用 map 和 flat
const result1 = arr.map(x => [x * 2]).flat();
console.log(result1); // [2, 4, 6]

// 使用 flatMap
const result2 = arr.flatMap(x => [x * 2]);
console.log(result2); // [2, 4, 6]

// 更实用的例子:在处理句子单词时,移除空项
const sentences = ['Hello world', 'My name is Tom', 'Goodbye'];
const words = sentences.flatMap(sentence => sentence.split(' '));
console.log(words);
// ['Hello', 'world', 'My', 'name', 'is', 'Tom', 'Goodbye']

五、 数组的静态方法:Array.from 与 Array.of

1. Array.from - 将类数组对象或可迭代对象转换为数组

类数组对象(拥有 length 属性和若干索引属性的任意对象)和可迭代对象(如 Set, Map, String)可以通过 Array.from 轻松转为真正的数组。

javascript 复制代码
// 将类数组对象(如 arguments)转换为数组
function foo() {
  // const args = Array.prototype.slice.call(arguments); // ES5
  const args = Array.from(arguments); // ES6
  console.log(args); // [1, 2, 3]
}
foo(1, 2, 3);

// 从 String 创建数组
console.log(Array.from('hello')); // ['h', 'e', 'l', 'l', 'o']

// 从 Set 创建数组(可用于数组去重)
const set = new Set([1, 2, 2, 3, 3, 3]);
const uniqueArray = Array.from(set);
console.log(uniqueArray); // [1, 2, 3]

// 第二个参数:映射函数
const squares = Array.from([1, 2, 3], x => x * x);
console.log(squares); // [1, 4, 9]

2. Array.of - 消除 Array 构造函数的歧义

Array 构造函数在接收不同数量的参数时,行为不一致,这常常令人困惑。Array.of 解决了这个问题。

javascript 复制代码
// Array 构造函数的问题
console.log(new Array(5)); // [ <5 empty items> ] 一个长度为5的空数组
console.log(new Array(1, 2, 3)); // [1, 2, 3]

// Array.of 的行为始终一致
console.log(Array.of(5)); // [5]
console.log(Array.of(1, 2, 3)); // [1, 2, 3]

六、 方法链与最佳实践

ES6 数组方法的真正威力在于它们可以链式调用,从而以清晰、流畅的方式表达复杂的数据处理逻辑。

场景:有一个用户列表,我们需要找出所有活跃的用户,提取他们的名字,并且这些名字的首字母要大写。

javascript 复制代码
const users = [
  { id: 1, name: 'alice', active: true },
  { id: 2, name: 'bob', active: false },
  { id: 3, name: 'charlie', active: true },
  { id: 4, name: 'david', active: true }
];

// ES6 方法链
const result = users
  .filter(user => user.active) // 筛选活跃用户 -> [{id:1, name:'alice'}, {id:3, name:'charlie'}, {id:4, name:'david'}]
  .map(user => user.name) // 提取名字 -> ['alice', 'charlie', 'david']
  .map(name => name.charAt(0).toUpperCase() + name.slice(1)) // 首字母大写 -> ['Alice', 'Charlie', 'David']

console.log(result); // ['Alice', 'Charlie', 'David']

这段代码如同一个数据处理的管道,每一步都清晰明了,没有任何中间变量和循环结构,完美体现了声明式编程的魅力。

总结

ES6 的数组方法不仅仅是语法糖,它们代表了一种编程范式的转变。通过拥抱 map、filter、reduce、find、some、every 等方法,我们可以:

  1. 编写更简洁、更易读的代码:代码更接近于自然语言的描述。

  2. 减少副作用和错误:避免直接修改原数组和使用易错的循环计数器。

  3. 提高代码的可维护性和可测试性:每个函数都是纯函数或接近纯函数,易于测试。

  4. 利用链式调用构建复杂的数据流:让数据处理流程一目了然。

相关推荐
小杨快跑~1 小时前
ES6 Promise:告别回调地狱的异步编程革命
前端·javascript·ecmascript·es6
码界筑梦坊2 小时前
240-基于Python的医疗疾病数据可视化分析系统
开发语言·python·信息可视化·数据分析·毕业设计·echarts
linweidong2 小时前
VIVO前端面试题及参考答案
前端·跨域·localstorage·重绘·浏览器兼容·git管理·前端重构
有意义2 小时前
从零搭建:json-server+Bootstrap+OpenAI 全栈 AI 小项目
前端·后端·llm
温宇飞2 小时前
CCState:为大型 Web 应用设计的状态管理库
前端
2301_803554522 小时前
C++ 锁类型大全详解
开发语言·c++
wuwu_q2 小时前
用通俗易懂方式,详细讲讲 Kotlin Flow 中的 map 操作符
android·开发语言·kotlin
曼巴UE52 小时前
UE5 C++ Slate 画曲线
开发语言·c++·ue5
向葭奔赴♡2 小时前
Spring IOC/DI 与 MVC 从入门到实战
java·开发语言