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. 利用链式调用构建复杂的数据流:让数据处理流程一目了然。

相关推荐
wordbaby2 分钟前
TanStack Router 基于文件的路由
前端
wordbaby7 分钟前
TanStack Router 路由概念
前端
努力学习的小廉8 分钟前
【QT(六)】—— 常用控件(三)
开发语言·qt
wordbaby9 分钟前
TanStack Router 路由匹配
前端
cc蒲公英10 分钟前
vue nextTick和setTimeout区别
前端·javascript·vue.js
Z.yping14 分钟前
qt语言家一键更新或发布多个模块且多个国家的语言
开发语言·qt·restful
程序员刘禹锡15 分钟前
Html中常用的块标签!!!12.16日
前端·html
MSTcheng.17 分钟前
【C++】set / multiset 保姆级教程:从底层原理到实战应用!
开发语言·c++·set
历程里程碑17 分钟前
C++ 16:C++11新特化
c语言·开发语言·数据结构·c++·经验分享
巴拉巴拉~~19 分钟前
KMP 算法通用步进器组件:KmpStepperWidget 横向 / 纵向 + 匹配进度 + 全样式自定义
java·服务器·开发语言