数组迭代方法:声明式处理数据的艺术
回想一下你在餐厅点餐的经历。你不会告诉厨师"先把锅加热,然后倒油,接着放入洋葱...",而是直接说"我要一份意大利面"。厨师知道如何制作,你只需要描述你想要什么。这种方式被称为"声明式"——你声明目标,而不是描述过程。JavaScript 的数组迭代方法采用同样的哲学。你不需要写循环来描述"如何"遍历数组,而是声明你想"做什么"——转换、过滤、查找或归纳数据。这些方法让代码更简洁、更易读、更具表达力。
什么是数组迭代方法
数组迭代方法是用于遍历数组元素并对每个元素执行操作的方法。它们接受一个回调函数作为参数,这个函数会被应用到数组的每个元素上。这些方法是函数式编程的核心工具,让我们能够以声明式的方式处理数据。
让我们先看一个对比,理解为什么迭代方法如此重要:
let numbers = [1, 2, 3, 4, 5];
// 命令式:告诉计算机"如何做"
let doubled1 = [];
for (let i = 0; i < numbers.length; i++) {
doubled1.push(numbers[i] * 2);
}
// 声明式:告诉计算机"做什么"
let doubled2 = numbers.map((n) => n * 2);
console.log(doubled1); // [2, 4, 6, 8, 10]
console.log(doubled2); // [2, 4, 6, 8, 10]声明式代码更简洁、更易读,意图更明确。这就是迭代方法的力量。
forEach() - 遍历数组
forEach() 是最基础的迭代方法,它对数组的每个元素执行一次提供的函数。
基本用法
let fruits = ["apple", "banana", "orange"];
// 遍历并打印每个元素
fruits.forEach((fruit) => {
console.log(fruit);
});
// apple
// banana
// orange
// 回调函数可以接收三个参数:元素、索引、数组本身
fruits.forEach((fruit, index, array) => {
console.log(`${index}: ${fruit} (total: ${array.length})`);
});
// 0: apple (total: 3)
// 1: banana (total: 3)
// 2: orange (total: 3)与传统 for 循环的对比
let numbers = [1, 2, 3, 4, 5];
// 传统 for 循环
for (let i = 0; i < numbers.length; i++) {
console.log(numbers[i] * 2);
}
// forEach
numbers.forEach((n) => console.log(n * 2));
// for...of(ES6)
for (let n of numbers) {
console.log(n * 2);
}forEach() 的特点:
- 不能使用
break或continue - 总是返回
undefined(不返回新数组) - 不修改原数组(除非在回调中显式修改)
实际应用
// 更新对象数组
let products = [
{ name: "Laptop", price: 1000 },
{ name: "Mouse", price: 25 },
{ name: "Keyboard", price: 75 },
];
products.forEach((product) => {
product.tax = product.price * 0.1;
product.total = product.price + product.tax;
});
console.log(products);
// [
// { name: "Laptop", price: 1000, tax: 100, total: 1100 },
// { name: "Mouse", price: 25, tax: 2.5, total: 27.5 },
// { name: "Keyboard", price: 75, tax: 7.5, total: 82.5 }
// ]map() - 映射转换
map() 方法创建一个新数组,包含对原数组每个元素调用函数的结果。这是数据转换的首选方法。
基本用法
let numbers = [1, 2, 3, 4, 5];
// 将每个数字乘以2
let doubled = numbers.map((n) => n * 2);
console.log(doubled); // [2, 4, 6, 8, 10]
console.log(numbers); // [1, 2, 3, 4, 5] - 原数组未变
// 提取对象属性
let users = [
{ name: "Alice", age: 25 },
{ name: "Bob", age: 30 },
{ name: "Charlie", age: 35 },
];
let names = users.map((user) => user.name);
console.log(names); // ["Alice", "Bob", "Charlie"]
let ages = users.map((user) => user.age);
console.log(ages); // [25, 30, 35]创建新对象
let products = [
{ id: 1, name: "Laptop", price: 1000 },
{ id: 2, name: "Mouse", price: 25 },
{ id: 3, name: "Keyboard", price: 75 },
];
// 添加折扣价格
let discounted = products.map((product) => ({
...product,
discount: product.price * 0.2,
finalPrice: product.price * 0.8,
}));
console.log(discounted[0]);
// {
// id: 1,
// name: "Laptop",
// price: 1000,
// discount: 200,
// finalPrice: 800
// }使用索引参数
let words = ["apple", "banana", "orange"];
// 添加序号
let numbered = words.map((word, index) => `${index + 1}. ${word}`);
console.log(numbered); // ["1. apple", "2. banana", "3. orange"]
// 根据索引计算
let indexed = [10, 20, 30, 40];
let result = indexed.map((value, index) => value * index);
console.log(result); // [0, 20, 60, 120]实际应用:数据格式化
// API 响应数据转换
let apiResponse = [
{
user_id: 1,
user_name: "Alice Johnson",
email_address: "[email protected]",
created_at: "2024-01-15",
},
{
user_id: 2,
user_name: "Bob Smith",
email_address: "[email protected]",
created_at: "2024-02-20",
},
];
// 转换为前端需要的格式
let formattedUsers = apiResponse.map((user) => ({
id: user.user_id,
name: user.user_name,
email: user.email_address,
joinedDate: new Date(user.created_at).toLocaleDateString(),
}));
console.log(formattedUsers);
// [
// { id: 1, name: "Alice Johnson", email: "[email protected]", joinedDate: "1/15/2024" },
// { id: 2, name: "Bob Smith", email: "[email protected]", joinedDate: "2/20/2024" }
// ]filter() - 过滤元素
filter() 方法创建一个新数组,包含通过测试函数的所有元素。
基本用法
let numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
// 筛选偶数
let evens = numbers.filter((n) => n % 2 === 0);
console.log(evens); // [2, 4, 6, 8, 10]
// 筛选大于5的数字
let greaterThanFive = numbers.filter((n) => n > 5);
console.log(greaterThanFive); // [6, 7, 8, 9, 10]
// 筛选符合条件的对象
let users = [
{ name: "Alice", age: 25, active: true },
{ name: "Bob", age: 17, active: true },
{ name: "Charlie", age: 30, active: false },
{ name: "David", age: 22, active: true },
];
let activeAdults = users.filter((user) => user.age >= 18 && user.active);
console.log(activeAdults);
// [
// { name: "Alice", age: 25, active: true },
// { name: "David", age: 22, active: true }
// ]复杂过滤条件
let products = [
{ name: "Laptop", price: 1200, category: "Electronics", inStock: true },
{ name: "Phone", price: 800, category: "Electronics", inStock: false },
{ name: "Desk", price: 300, category: "Furniture", inStock: true },
{ name: "Chair", price: 150, category: "Furniture", inStock: true },
{ name: "Monitor", price: 400, category: "Electronics", inStock: true },
];
// 多个条件
let affordableElectronics = products.filter(
(p) => p.category === "Electronics" && p.price < 1000 && p.inStock
);
console.log(affordableElectronics);
// [{ name: "Monitor", price: 400, category: "Electronics", inStock: true }]移除特定元素
let tasks = [
"Buy groceries",
"Clean house",
"Exercise",
"Read book",
"Exercise",
];
// 移除 "Exercise"
let withoutExercise = tasks.filter((task) => task !== "Exercise");
console.log(withoutExercise); // ["Buy groceries", "Clean house", "Read book"]
// 移除重复值(配合 indexOf)
let unique = tasks.filter(
(task, index, array) => array.indexOf(task) === index
);
console.log(unique); // ["Buy groceries", "Clean house", "Exercise", "Read book"]reduce() - 归纳累加
reduce() 是最强大也最复杂的迭代方法。它将数组归纳为单个值,可以实现求和、计数、分组等各种操作。
基本用法:求和
let numbers = [1, 2, 3, 4, 5];
// 计算总和
let sum = numbers.reduce((accumulator, current) => {
return accumulator + current;
}, 0); // 0 是初始值
console.log(sum); // 15
// 简化写法
let sum2 = numbers.reduce((acc, curr) => acc + curr, 0);
console.log(sum2); // 15reduce() 的工作原理:
- 第一次调用:
accumulator = 0(初始值),current = 1,返回0 + 1 = 1 - 第二次调用:
accumulator = 1,current = 2,返回1 + 2 = 3 - 第三次调用:
accumulator = 3,current = 3,返回3 + 3 = 6 - 第四次调用:
accumulator = 6,current = 4,返回6 + 4 = 10 - 第五次调用:
accumulator = 10,current = 5,返回10 + 5 = 15
计算平均值
let scores = [85, 90, 78, 92, 88];
let average = scores.reduce((sum, score, index, array) => {
sum += score;
// 最后一个元素时计算平均值
if (index === array.length - 1) {
return sum / array.length;
}
return sum;
}, 0);
console.log(average); // 86.6查找最大值和最小值
let numbers = [5, 2, 9, 1, 7, 6];
let max = numbers.reduce((max, current) => (current > max ? current : max));
console.log(max); // 9
let min = numbers.reduce((min, current) => (current < min ? current : min));
console.log(min); // 1对象数组的统计
let cart = [
{ product: "Laptop", price: 1000, quantity: 1 },
{ product: "Mouse", price: 25, quantity: 2 },
{ product: "Keyboard", price: 75, quantity: 1 },
];
// 计算总价
let total = cart.reduce((sum, item) => sum + item.price * item.quantity, 0);
console.log(total); // 1125
// 统计商品数量
let totalItems = cart.reduce((count, item) => count + item.quantity, 0);
console.log(totalItems); // 4分组和计数
let votes = ["Alice", "Bob", "Alice", "Charlie", "Bob", "Alice"];
// 计数
let voteCount = votes.reduce((counts, name) => {
counts[name] = (counts[name] || 0) + 1;
return counts;
}, {});
console.log(voteCount); // { Alice: 3, Bob: 2, Charlie: 1 }
// 按年龄分组用户
let users = [
{ name: "Alice", age: 25 },
{ name: "Bob", age: 30 },
{ name: "Charlie", age: 25 },
{ name: "David", age: 30 },
];
let grouped = users.reduce((groups, user) => {
let age = user.age;
if (!groups[age]) {
groups[age] = [];
}
groups[age].push(user);
return groups;
}, {});
console.log(grouped);
// {
// 25: [{ name: "Alice", age: 25 }, { name: "Charlie", age: 25 }],
// 30: [{ name: "Bob", age: 30 }, { name: "David", age: 30 }]
// }扁平化数组
let nested = [
[1, 2],
[3, 4],
[5, 6],
];
let flattened = nested.reduce((flat, current) => flat.concat(current), []);
console.log(flattened); // [1, 2, 3, 4, 5, 6]find() 和 findIndex() - 查找元素
find() - 查找第一个匹配元素
find() 返回数组中第一个满足测试函数的元素,如果没找到返回 undefined。
let users = [
{ id: 1, name: "Alice", role: "admin" },
{ id: 2, name: "Bob", role: "user" },
{ id: 3, name: "Charlie", role: "user" },
];
// 查找第一个管理员
let admin = users.find((user) => user.role === "admin");
console.log(admin); // { id: 1, name: "Alice", role: "admin" }
// 按 ID 查找
let user = users.find((u) => u.id === 2);
console.log(user); // { id: 2, name: "Bob", role: "admin" }
// 找不到返回 undefined
let notFound = users.find((u) => u.id === 999);
console.log(notFound); // undefinedfindIndex() - 查找元素索引
findIndex() 返回第一个满足测试函数的元素的索引,如果没找到返回 -1。
let numbers = [5, 12, 8, 130, 44];
// 查找第一个大于10的数字的索引
let index = numbers.findIndex((n) => n > 10);
console.log(index); // 1 (值为12的索引)
// 查找对象索引
let products = [
{ id: 1, name: "Laptop" },
{ id: 2, name: "Mouse" },
{ id: 3, name: "Keyboard" },
];
let mouseIndex = products.findIndex((p) => p.name === "Mouse");
console.log(mouseIndex); // 1some() 和 every() - 条件检查
some() - 至少一个满足
some() 检查数组中是否至少有一个元素满足测试函数。
let numbers = [1, 2, 3, 4, 5];
// 是否有偶数
let hasEven = numbers.some((n) => n % 2 === 0);
console.log(hasEven); // true
// 是否有大于10的数
let hasLarge = numbers.some((n) => n > 10);
console.log(hasLarge); // false
// 实际应用:权限检查
let userPermissions = ["read", "write"];
let requiredPermissions = ["read", "delete"];
let hasAnyPermission = requiredPermissions.some((perm) =>
userPermissions.includes(perm)
);
console.log(hasAnyPermission); // true (有 read 权限)every() - 全部都满足
every() 检查数组中是否所有元素都满足测试函数。
let numbers = [2, 4, 6, 8, 10];
// 是否全是偶数
let allEven = numbers.every((n) => n % 2 === 0);
console.log(allEven); // true
// 是否都大于5
let allGreaterThanFive = numbers.every((n) => n > 5);
console.log(allGreaterThanFive); // false
// 实际应用:表单验证
let formFields = [
{ name: "email", value: "[email protected]", valid: true },
{ name: "password", value: "12345678", valid: true },
{ name: "age", value: "", valid: false },
];
let formIsValid = formFields.every((field) => field.valid);
console.log(formIsValid); // falseflat() 和 flatMap() - 扁平化操作
flat() - 扁平化嵌套数组
flat() 方法创建一个新数组,包含所有子数组元素,可以指定扁平化深度。
// 默认扁平化一层
let arr1 = [1, 2, [3, 4]];
console.log(arr1.flat()); // [1, 2, 3, 4]
// 扁平化多层
let arr2 = [1, 2, [3, 4, [5, 6]]];
console.log(arr2.flat()); // [1, 2, 3, 4, [5, 6]]
console.log(arr2.flat(2)); // [1, 2, 3, 4, 5, 6]
// 完全扁平化(使用 Infinity)
let arr3 = [1, [2, [3, [4, [5]]]]];
console.log(arr3.flat(Infinity)); // [1, 2, 3, 4, 5]
// 移除空项
let arr4 = [1, 2, , 4, 5];
console.log(arr4.flat()); // [1, 2, 4, 5]flatMap() - 映射后扁平化
flatMap() 相当于先 map() 再 flat(),但更高效。
let sentences = ["Hello world", "How are you"];
// 使用 map + flat
let words1 = sentences.map((s) => s.split(" ")).flat();
console.log(words1); // ["Hello", "world", "How", "are", "you"]
// 使用 flatMap
let words2 = sentences.flatMap((s) => s.split(" "));
console.log(words2); // ["Hello", "world", "How", "are", "you"]
// 过滤并扁平化
let numbers = [1, 2, 3, 4];
let result = numbers.flatMap((n) => (n % 2 === 0 ? [n, n * 2] : []));
console.log(result); // [2, 4, 4, 8]链式调用的力量
迭代方法可以链式调用,创建强大的数据处理管道:
let products = [
{ name: "Laptop", price: 1200, category: "Electronics", rating: 4.5 },
{ name: "Phone", price: 800, category: "Electronics", rating: 4.8 },
{ name: "Desk", price: 300, category: "Furniture", rating: 4.2 },
{ name: "Chair", price: 150, category: "Furniture", rating: 4.0 },
{ name: "Monitor", price: 400, category: "Electronics", rating: 4.6 },
];
// 复杂的数据处理管道
let avgElectronicsPrice = products
.filter((p) => p.category === "Electronics") // 筛选电子产品
.filter((p) => p.rating >= 4.5) // 筛选高评分
.map((p) => p.price) // 提取价格
.reduce(
(
sum,
price,
_,
array // 计算平均价格
) => sum + price / array.length,
0
);
console.log(avgElectronicsPrice); // 800
// 另一个例子:用户数据处理
let users = [
{ name: "Alice", age: 28, orders: [100, 200, 50] },
{ name: "Bob", age: 35, orders: [300, 150] },
{ name: "Charlie", age: 22, orders: [75, 125, 175] },
];
let topSpenders = users
.map((user) => ({
name: user.name,
totalSpent: user.orders.reduce((sum, amount) => sum + amount, 0),
}))
.filter((user) => user.totalSpent > 200)
.sort((a, b) => b.totalSpent - a.totalSpent)
.map((user) => user.name);
console.log(topSpenders); // ["Bob", "Charlie"]性能考虑
虽然链式调用很优雅,但每个方法都会遍历数组一次。在处理大型数组时,可能需要优化:
let largeArray = Array.from({ length: 100000 }, (_, i) => i);
// ❌ 多次遍历(3次)
console.time("chained");
let result1 = largeArray
.filter((n) => n % 2 === 0)
.map((n) => n * 2)
.filter((n) => n > 1000);
console.timeEnd("chained");
// ✅ 单次遍历(使用 reduce)
console.time("single");
let result2 = largeArray.reduce((acc, n) => {
if (n % 2 === 0) {
let doubled = n * 2;
if (doubled > 1000) {
acc.push(doubled);
}
}
return acc;
}, []);
console.timeEnd("single");
// 两者结果相同,但第二种方式更快总结
数组迭代方法是 JavaScript 函数式编程的核心,它们提供了声明式的数据处理方式:
- forEach() - 遍历数组,执行 side effect
- map() - 转换每个元素,返回新数组
- filter() - 筛选符合条件的元素
- reduce() - 将数组归纳为单个值,功能最强大
- find() / findIndex() - 查找匹配元素或索引
- some() / every() - 检查是否有/全部元素满足条件
- flat() / flatMap() - 扁平化嵌套数组
使用迭代方法的优势:
- 代码更简洁、可读性更强
- 声明式编程,关注"做什么"而非"怎么做"
- 支持链式调用,构建数据处理管道
- 函数式编程风格,易于测试和维护
这些方法是现代 JavaScript 开发的基石,掌握它们将大大提升你的编程效率和代码质量。配合之前学习的数组基础和其他数组方法,你现在已经具备了全面的数组操作能力。