数组迭代方法
数组迭代方法是 JavaScript 中最常用且最强大的数组操作工具,掌握它们能显著提高开发效率和代码质量。
概述
JavaScript 从 ES5 开始引入了丰富的数组迭代方法,ES6+ 进一步扩展了这些能力。这些方法提供了函数式编程风格的数据处理能力,使代码更加简洁、可读和可维护。
核心特性
- 函数式编程:支持回调函数,代码更加声明式
- 不可变性:大多数方法返回新数组,不改变原数组
- 链式调用:方法可以连续调用,构建数据处理管道
- 短路优化:部分方法支持提前终止,提升性能
一、forEach()
遍历数组的每个元素,对每个元素执行回调函数。是最基础的迭代方法。
语法
array.forEach(callback(element, index, array), thisArg);参数详解
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
callback | Function | 是 | 为每个元素执行的函数 |
element | any | - | 当前正在处理的元素 |
index | number | - | 当前元素的索引(从 0 开始) |
array | Array | - | 正在遍历的数组本身 |
thisArg | any | 否 | 回调函数中 this 的值 |
返回值
undefined
基础示例
const arr = [1, 2, 3, 4, 5];
// 基本遍历
arr.forEach((item, index) => {
console.log(`索引 ${index}: ${item}`);
});
// 输出: 索引 0: 1, 索引 1: 2, ...
// 使用 thisArg
const obj = { multiplier: 2 };
arr.forEach(function(item) {
console.log(item * this.multiplier);
}, obj);
// 输出: 2, 4, 6, 8, 10高级用法
const users = [
{ name: 'Alice', score: 85 },
{ name: 'Bob', score: 92 },
{ name: 'Charlie', score: 78 },
];
// 计算总分
let total = 0;
users.forEach((user) => {
total += user.score;
});
console.log(`平均分: ${(total / users.length).toFixed(1)}`); // 平均分: 85.0
// 批量修改对象属性
users.forEach((user) => {
user.grade = user.score >= 90 ? 'A' : user.score >= 80 ? 'B' : 'C';
});
// users 现在包含 grade 属性
// 使用 array 参数
arr.forEach((item, index, array) => {
console.log(`当前元素: ${item}, 数组长度: ${array.length}`);
});注意事项
| 特性 | 说明 | 建议 |
|---|---|---|
| 无法中断 | 不能使用 break 或 continue | 需要中断时使用 for...of 或 some() |
| 不改变原数组 | 但回调函数中可以修改元素 | 注意引用类型的修改 |
| 稀疏数组 | 跳过空位(empty slots) | 与 map、filter 行为一致 |
| 无法链式调用 | 返回 undefined | 需要返回值时使用 map |
性能提示
// ❌ 不推荐:在 forEach 中进行大量计算
arr.forEach((item) => {
// 复杂计算...
});
// ✅ 推荐:大数据量使用 for 循环
for (let i = 0; i < arr.length; i++) {
// 复杂计算...
}二、map()
创建一个新数组,其结果是对原数组每个元素调用回调函数后的返回值。是最常用的数据转换方法。
语法
const newArray = array.map(callback(element, index, array), thisArg);返回值
新的 Array 实例,长度与原数组相同。
基础示例
const numbers = [1, 2, 3, 4, 5];
// 数值转换
const doubled = numbers.map((num) => num * 2);
console.log(doubled); // [2, 4, 6, 8, 10]
// 提取对象属性
const users = [
{ name: 'Alice', age: 25 },
{ name: 'Bob', age: 30 },
];
const names = users.map((user) => user.name);
console.log(names); // ['Alice', 'Bob']
// 格式化数据
const prices = [100, 200, 300];
const formatted = prices.map((price) => `¥${price.toFixed(2)}`);
console.log(formatted); // ['¥100.00', '¥200.00', '¥300.00']高级用法
// 对象数组转换
const products = [
{ id: 1, name: 'Apple', price: 5 },
{ id: 2, name: 'Banana', price: 3 },
];
const formatted = products.map(({ id, name, price }) => ({
key: id,
label: name,
value: price,
}));
console.log(formatted);
// ... 中间省略 ...
// 重建对象(React 常用)
const items = ['Apple', 'Banana', 'Orange'];
const itemObjects = items.map((item, index) => ({
id: index,
text: item,
}));使用场景矩阵
| 场景 | 示例 | 说明 |
|---|---|---|
| 数据转换 | arr.map(x => x * 2) | 一对一映射 |
| 属性提取 | users.map(u => u.name) | 提取单一属性 |
| 格式化 | nums.map(n => n.toFixed(2)) | 统一格式 |
| 结构重组 | arr.map(({a,b}) => ({x:a, y:b})) | 改变对象结构 |
| React 列表 | items.map(item => <Item key={item.id} />) | JSX 渲染 |
性能注意事项
// ❌ 不推荐:map 中有副作用
let sum = 0;
const result = numbers.map((n) => {
sum += n; // 副作用
return n * 2;
});
// ✅ 推荐:map 只做转换
const result = numbers.map((n) => n * 2);
const sum = numbers.reduce((acc, n) => acc + n, 0);
// ❌ 不推荐:map 返回值不使用
numbers.map((n) => console.log(n)); // 应该用 forEach
// ✅ 推荐:使用正确的方法
numbers.forEach((n) => console.log(n));三、filter()
过滤数组,返回满足条件的元素组成的新数组。
语法
const newArray = array.filter(callback(element, index, array), thisArg);返回值
新的 Array 实例,仅包含使回调函数返回真值的元素。
基础示例
const numbers = [1, 2, 3, 4, 5, 6];
// 过滤偶数
const evens = numbers.filter((num) => num % 2 === 0);
console.log(evens); // [2, 4, 6]
// 过滤对象数组
const products = [
{ name: 'Apple', price: 5, inStock: true },
{ name: 'Banana', price: 3, inStock: false },
{ name: 'Orange', price: 4, inStock: true },
];
const available = products.filter((p) => p.inStock && p.price < 5);
console.log(available); // [{ name: 'Orange', price: 4, inStock: true }]
// 去除假值
const mixed = [0, 1, false, 2, '', 3, null, undefined, NaN];
const truthy = mixed.filter(Boolean);
console.log(truthy); // [1, 2, 3]高级用法
// 多条件过滤
const data = [
{ name: 'Alice', age: 25, department: 'Engineering' },
{ name: 'Bob', age: 30, department: 'Marketing' },
{ name: 'Charlie', age: 28, department: 'Engineering' },
];
const filtered = data.filter(
(item) => item.age >= 25 && item.age <= 28 && item.department === 'Engineering'
);
console.log(filtered); // [{ name: 'Charlie', ... }]
// ... 中间省略 ...
{ status: 'success', data: { value: 2 } },
];
const validData = responses
.filter((r) => r.status === 'success')
.filter((r) => r.data !== null);
console.log(validData.length); // 2使用技巧
// 链式过滤(更易读)
const result = data
.filter((item) => item.active)
.filter((item) => item.score > 60)
.filter((item) => item.age >= 18);
// 动态条件过滤
const conditions = {
minPrice: 10,
maxPrice: 50,
category: 'fruit',
};
const filtered = products.filter((p) => {
return (
p.price >= conditions.minPrice &&
p.price <= conditions.maxPrice &&
p.category === conditions.category
);
});
// 使用解构
const filtered = users.filter(({ age, status }) => age >= 18 && status === 'active');四、reduce()
累计器,将数组归约为单个值。是最强大、最灵活的数组方法。
语法
const result = array.reduce(callback(accumulator, currentValue, index, array), initialValue);参数详解
| 参数 | 类型 | 说明 |
|---|---|---|
accumulator | any | 累计器,存储每次回调的返回值 |
currentValue | any | 当前正在处理的元素 |
index | number | 当前元素的索引 |
array | Array | 正在遍历的数组 |
initialValue | any | 强烈推荐提供,作为首次回调的初始值 |
返回值
累计器的最终值。
基础示例
const numbers = [1, 2, 3, 4, 5];
// 求和
const sum = numbers.reduce((acc, cur) => acc + cur, 0);
console.log(sum); // 15
// 求乘积
const product = numbers.reduce((acc, cur) => acc * cur, 1);
console.log(product); // 120
// 找最大值
const max = numbers.reduce((acc, cur) => (acc > cur ? acc : cur), numbers[0]);
console.log(max); // 5
// 找最小值
const min = numbers.reduce((acc, cur) => (acc < cur ? acc : cur), numbers[0]);
console.log(min); // 1高级用法
// 数组转对象
const users = [
{ id: 1, name: 'Alice' },
{ id: 2, name: 'Bob' },
];
const userMap = users.reduce((acc, user) => {
acc[user.id] = user.name;
return acc;
}, {});
console.log(userMap); // { 1: 'Alice', 2: 'Bob' }
// 统计元素出现次数
// ... 中间省略 ...
const double = (x) => x * 2;
const addOne = (x) => x + 1;
const square = (x) => x * x;
const calculate = pipe(double, addOne, square);
console.log(calculate(3)); // ((3 * 2) + 1)² = 49重要提示
务必提供初始值,避免意外行为:
const arr = [];
// ❌ 不提供初始值,空数组会报错
arr.reduce((acc, cur) => acc + cur); // TypeError: Reduce of empty array with no initial value
// ✅ 提供初始值则安全
arr.reduce((acc, cur) => acc + cur, 0); // 0
// ⚠️ 无初始值时的问题
const nums = [1, 2, 3];
// 无初始值:acc 第一次是 1,cur 第一次是 2
const sum1 = nums.reduce((acc, cur) => acc + cur);
// 有初始值:acc 第一次是 0,cur 第一次是 1
const sum2 = nums.reduce((acc, cur) => acc + cur, 0);
console.log(sum1 === sum2); // true(结果相同,但过程不同)五、reduceRight()
从右向左归约数组,与 reduce() 方向相反。
语法
const result = array.reduceRight(callback(accumulator, currentValue, index, array), initialValue);示例
const arr = [1, 2, 3, 4, 5];
// 从右向左累加
const sum = arr.reduceRight((acc, cur) => acc + cur, 0);
console.log(sum); // 15(结果相同)
// 从右向左拼接
const strs = ['a', 'b', 'c'];
const result = strs.reduceRight((acc, cur) => acc + cur, '');
console.log(result); // 'cba'
// 实现compose函数
const compose =
(...fns) =>
(x) =>
fns.reduceRight((v, fn) => fn(v), x);
const double = (x) => x * 2;
const addOne = (x) => x + 1;
const calculate = compose(double, addOne);
console.log(calculate(3)); // (3 + 1) * 2 = 8六、find() 和 findIndex()
查找满足条件的第一个元素或其索引。
find()
返回第一个满足条件的元素,否则返回 undefined。
findIndex()
返回第一个满足条件的索引,否则返回 -1。
语法
const element = array.find(callback(element, index, array), thisArg);
const index = array.findIndex(callback(element, index, array), thisArg);示例
const users = [
{ id: 1, name: 'Alice', age: 25 },
{ id: 2, name: 'Bob', age: 30 },
{ id: 3, name: 'Charlie', age: 28 },
];
// find() - 查找元素
const user = users.find((u) => u.id === 2);
console.log(user); // { id: 2, name: 'Bob', age: 30 }
const notFound = users.find((u) => u.id === 99);
console.log(notFound); // undefined
// findIndex() - 查找索引
const index = users.findIndex((u) => u.name === 'Bob');
console.log(index); // 1
const notFoundIndex = users.findIndex((u) => u.id === 99);
console.log(notFoundIndex); // -1
// 查找复合条件
const result = users.find((u) => u.age > 25 && u.name.startsWith('B'));
console.log(result); // { id: 2, name: 'Bob', age: 30 }
// 查找并修改
const found = users.find((u) => u.id === 2);
if (found) {
found.age = 31; // 注意:这会修改原数组
}使用场景对比
| 场景 | 推荐方法 | 说明 |
|---|---|---|
| 只需元素 | find() | 直接获取对象 |
| 需要索引 | findIndex() | 用于删除、插入等操作 |
| 检查存在 | some() | 只需知道是否存在 |
| 获取所有匹配 | filter() | 获取所有符合条件的元素 |
// 查找后删除
const idx = users.findIndex((u) => u.id === 2);
if (idx !== -1) {
users.splice(idx, 1);
}
// 查找后替换
const idx = users.findIndex((u) => u.id === 2);
if (idx !== -1) {
users[idx] = { ...users[idx], name: 'Robert' };
}七、findLast() 和 findLastIndex() (ES2023)
从数组末尾开始查找元素或索引。
语法
const element = array.findLast(callback(element, index, array), thisArg);
const index = array.findLastIndex(callback(element, index, array), thisArg);示例
const arr = [1, 2, 3, 4, 5, 3];
// findLast() - 从末尾查找
const lastEven = arr.findLast((n) => n % 2 !== 0);
console.log(lastEven); // 3(最后一个奇数)
// findLastIndex() - 从末尾查找索引
const lastEvenIndex = arr.findLastIndex((n) => n === 3);
console.log(lastEvenIndex); // 5(最后一个3的索引)
// 对比 find 和 findLast
const firstMatch = arr.find((n) => n === 3);
const lastMatch = arr.findLast((n) => n === 3);
console.log(firstMatch, lastMatch); // 3, 3(值相同但位置不同)
const firstIndex = arr.findIndex((n) => n === 3);
const lastIndex = arr.findLastIndex((n) => n === 3);
console.log(firstIndex, lastIndex); // 2, 5兼容性
ES2023 新增,需要现代浏览器或 Node.js 18+ 支持。
八、some() 和 every()
检测数组元素是否满足条件,返回布尔值。
some()
只要有一个元素满足条件就返回 true。
every()
所有元素都满足条件才返回 true。
语法
const result = array.some(callback(element, index, array), thisArg);
const result = array.every(callback(element, index, array), thisArg);示例
const numbers = [1, 2, 3, 4, 5];
// some() - 是否存在
const hasLarge = numbers.some((num) => num > 3);
console.log(hasLarge); // true
const hasNegative = numbers.some((num) => num < 0);
console.log(hasNegative); // false
// every() - 是否全部
const allPositive = numbers.every((num) => num > 0);
console.log(allPositive); // true
// ... 中间省略 ...
// 数据验证
const formData = { name: 'Alice', email: 'alice@example.com', age: 25 };
const requiredFields = ['name', 'email'];
const hasAllFields = requiredFields.every((field) => formData[field]);
console.log(hasAllFields); // true短路特性
两个方法都具有短路特性,可提前终止遍历:
// some 找到第一个 true 就停止
let someCount = 0;
[1, 2, 3, 4, 5].some((num) => {
someCount++;
return num === 3;
});
console.log(someCount); // 3(只遍历到第三个)
// every 找到第一个 false 就停止
let everyCount = 0;
[1, 2, 3, 4, 5].every((num) => {
everyCount++;
return num < 3;
});
console.log(everyCount); // 3(只遍历到第三个)空数组行为
// some 空数组返回 false(不存在满足条件的元素)
[].some((x) => true); // false
// every 空数组返回 true(所有元素都满足条件,空集的"所有"为真)
[].every((x) => false); // true ⚠️ 注意这个特殊行为使用场景
// 权限检查
const permissions = ['read', 'write'];
const canDelete = permissions.some((p) => p === 'delete');
console.log(canDelete); // false
// 表单验证
const fields = ['email', 'password', 'confirmPassword'];
const allValid = fields.every((field) => {
const input = document.querySelector(`#${field}`);
return input.checkValidity();
});
// 数组比较
const arr1 = [1, 2, 3];
const arr2 = [1, 2, 3, 4];
const hasSameElements = arr1.every((n) => arr2.includes(n));九、其他迭代方法
entries()、keys()、values()
返回数组迭代器对象,配合 for...of 使用。
语法
const iterator = array.entries(); // [index, value] 对
const iterator = array.keys(); // 索引
const iterator = array.values(); // 值示例
const arr = ['a', 'b', 'c'];
// entries() - 返回 [index, value] 对
for (const [index, value] of arr.entries()) {
console.log(`${index}: ${value}`);
}
// 0: a, 1: b, 2: c
// keys() - 只返回索引
for (const key of arr.keys()) {
console.log(key);
}
// 0, 1, 2
// values() - 只返回值
for (const value of arr.values()) {
console.log(value);
}
// a, b, c
// 转换为数组
const entries = [...arr.entries()];
console.log(entries); // [[0, 'a'], [1, 'b'], [2, 'c']]
const keys = [...arr.keys()];
console.log(keys); // [0, 1, 2]
const values = [...arr.values()];
console.log(values); // ['a', 'b', 'c']flat() 和 flatMap()
数组扁平化(ES2019)。
语法
const newArray = array.flat(depth); // depth 默认为 1
const newArray = array.flatMap(callback(element, index, array));示例
// flat() - 扁平化数组
const nested = [1, [2, 3], [4, [5, 6]]];
console.log(nested.flat()); // [1, 2, 3, 4, [5, 6]](深度 1)
console.log(nested.flat(2)); // [1, 2, 3, 4, 5, 6](深度 2)
console.log(nested.flat(Infinity)); // [1, 2, 3, 4, 5, 6](完全扁平)
// 移除空位
const sparse = [1, , 3, , 5];
console.log(sparse.flat()); // [1, 3, 5]
// flatMap() - map + flat(深度 1)
const sentences = ['Hello World', 'Good Morning'];
const words = sentences.flatMap((s) => s.split(' '));
console.log(words); // ['Hello', 'World', 'Good', 'Morning']
// 复杂示例:映射并扁平化
const data = [
{ name: 'Alice', hobbies: ['reading', 'gaming'] },
{ name: 'Bob', hobbies: ['music'] },
];
const allHobbies = data.flatMap((user) => user.hobbies);
console.log(allHobbies); // ['reading', 'gaming', 'music']
// 过滤并映射
const numbers = [1, 2, 3, 4, 5];
const result = numbers.flatMap((n) => (n % 2 === 0 ? [n, n * 2] : []));
console.log(result); // [2, 4, 4, 8]十、方法选择决策指南
决策流程图
需要什么结果?
├─ 无需返回值,只需遍历 → forEach()
├─ 新数组(相同长度) → map()
├─ 新数组(可能更短) → filter()
├─ 单个值(聚合) → reduce()
├─ 单个元素 → find()
├─ 索引位置 → findIndex()
├─ 布尔值(存在性) → some()
├─ 布尔值(全部性) → every()
└─ 扁平化数组 → flat() / flatMap()方法对比表
| 方法 | 返回值 | 改变原数组 | 可中断 | 空数组行为 | 适用场景 |
|---|---|---|---|---|---|
forEach | undefined | 否 | 否 | 正常执行 | 副作用操作 |
map | 新数组 | 否 | 否 | 返回 [] | 数据转换 |
filter | 新数组 | 否 | 否 | 返回 [] | 数据过滤 |
reduce | 任意值 | 否 | 否 | 返回初始值 | 聚合计算 |
reduceRight | 任意值 | 否 | 否 | 返回初始值 | 反向聚合 |
find | 元素或 undefined | 否 | 是 | 返回 undefined | 查找元素 |
findIndex | 索引或 -1 | 否 | 是 | 返回 -1 | 查找位置 |
findLast | 元素或 undefined | 否 | 是 | 返回 undefined | 反向查找 |
findLastIndex | 索引或 -1 | 否 | 是 | 返回 -1 | 反向查找位置 |
some | boolean | 否 | 是 | 返回 false | 存在性检查 |
every | boolean | 否 | 是 | 返回 true | 完整性检查 |
flat | 新数组 | 否 | 否 | 返回 [] | 数组扁平化 |
flatMap | 新数组 | 否 | 否 | 返回 [] | 映射后扁平化 |
十一、性能优化建议
1. 方法选择优化
// ❌ 性能差:filter 后取第一个
const user = users.filter((u) => u.id === 1)[0];
// ✅ 性能好:使用 find
const user = users.find((u) => u.id === 1);
// ❌ 性能差:map 后 filter
const result = arr.map(transform).filter(Boolean);
// ✅ 性能好:先 filter 后 map(减少 map 操作次数)
const result = arr.filter(shouldProcess).map(transform);2. 避免不必要的遍历
// ❌ 多次遍历
const sum = arr.reduce((a, b) => a + b, 0);
const max = Math.max(...arr);
const min = Math.min(...arr);
// ✅ 单次遍历(大数据量时)
const { sum, max, min } = arr.reduce(
(acc, cur) => ({
sum: acc.sum + cur,
max: Math.max(acc.max, cur),
min: Math.min(acc.min, cur),
}),
{ sum: 0, max: -Infinity, min: Infinity }
);3. 大数据量处理
// ❌ 内存占用大:链式调用创建多个中间数组
const result = largeArray
.filter(condition1)
.map(transform1)
.filter(condition2)
.map(transform2);
// ✅ 内存优化:使用 reduce 单次处理
const result = largeArray.reduce((acc, item) => {
if (!condition1(item)) return acc;
const transformed = transform1(item);
if (!condition2(transformed)) return acc;
acc.push(transform2(transformed));
return acc;
}, []);
// ✅ 或者使用 for...of(性能最优)
const result = [];
for (const item of largeArray) {
if (!condition1(item)) continue;
const transformed = transform1(item);
if (!condition2(transformed)) continue;
result.push(transform2(transformed));
}4. 性能对比(100万数据测试)
| 操作 | forEach | map | for 循环 |
|---|---|---|---|
| 简单遍历 | ~50ms | ~55ms | ~15ms |
| 复杂计算 | ~200ms | ~210ms | ~180ms |
💡 提示:对于简单操作,函数式方法性能足够好;对于极端性能要求,传统
for循环更快。
十二、实战案例
案例1:数据转换管道
const products = [
{ name: 'Apple', price: 5, category: 'fruit', inStock: true },
{ name: 'Carrot', price: 2, category: 'vegetable', inStock: true },
{ name: 'Banana', price: 3, category: 'fruit', inStock: false },
{ name: 'Broccoli', price: 4, category: 'vegetable', inStock: true },
];
// 链式调用:过滤库存 -> 选择水果 -> 价格调整 -> 提取信息
const result = products
.filter((p) => p.inStock)
.filter((p) => p.category === 'fruit')
.map((p) => ({
name: p.name,
price: p.price * 1.1, // 加价 10%
originalPrice: p.price,
}))
.sort((a, b) => a.price - b.price);
console.log(result);
// [{ name: 'Apple', price: 5.5, originalPrice: 5 }]案例2:复杂数据聚合
const orders = [
{ id: 1, product: 'Apple', quantity: 2, price: 5, date: '2024-01-01' },
{ id: 2, product: 'Banana', quantity: 3, price: 3, date: '2024-01-01' },
{ id: 3, product: 'Apple', quantity: 1, price: 5, date: '2024-01-02' },
{ id: 4, product: 'Orange', quantity: 2, price: 4, date: '2024-01-02' },
{ id: 5, product: 'Banana', quantity: 1, price: 3, date: '2024-01-03' },
];
// 按产品分组统计
const summary = orders.reduce((acc, order) => {
const key = order.product;
if (!acc[key]) {
// ... 中间省略 ...
return acc;
}, {});
// 计算总金额
const total = orders.reduce((acc, order) => acc + order.quantity * order.price, 0);
console.log(`总金额: ¥${total}`); // 总金额: ¥35案例3:表单数据验证
const formData = {
username: 'alice',
email: 'alice@example.com',
age: 25,
password: 'abc123',
confirmPassword: 'abc123',
};
const validationRules = {
username: {
validate: (v) => v.length >= 3,
message: '用户名至少3个字符',
// ... 中间省略 ...
// 批量验证结果
const isValid = Object.keys(validationRules).every((field) =>
validationRules[field].validate(formData[field])
);
console.log(isValid); // true/false案例4:数据处理与可视化
const salesData = [
{ month: 'Jan', sales: 100, cost: 80 },
{ month: 'Feb', sales: 120, cost: 90 },
{ month: 'Mar', sales: 150, cost: 100 },
{ month: 'Apr', sales: 130, cost: 95 },
{ month: 'May', sales: 180, cost: 120 },
];
// 计算利润
const withProfit = salesData.map((item) => ({
...item,
profit: item.sales - item.cost,
// ... 中间省略 ...
// 找出最佳月份
const bestMonth = salesData.reduce((best, current) =>
current.sales > best.sales ? current : best
);
console.log(`最佳月份: ${bestMonth.month}, 销售额: ${bestMonth.sales}`);十三、常见问题解答 (FAQ)
Q1: forEach 和 map 的区别是什么?
A: 主要区别在于返回值和使用目的:
forEach返回undefined,用于执行副作用(如打印、写入文件)map返回新数组,用于数据转换
// forEach - 执行操作
[1, 2, 3].forEach((n) => console.log(n)); // 打印,返回 undefined
// map - 转换数据
const doubled = [1, 2, 3].map((n) => n * 2); // 返回 [2, 4, 6]Q2: reduce 的初始值什么时候可以省略?
A: 只有当数组非空且累加类型与元素类型一致时才可省略,但不推荐省略:
// 可以省略(但不推荐)
[1, 2, 3].reduce((a, b) => a + b); // 6
// 必须提供初始值
[].reduce((a, b) => a + b, 0); // 0(空数组必须提供)
[1, 2, 3].reduce((a, b) => a.concat(b), []); // 初始值类型不同Q3: 如何跳出 forEach 循环?
A: forEach 无法真正跳出,有以下替代方案:
// 方案1:使用 some/every 的短路特性
const found = arr.some((item) => {
if (item === target) {
// 处理逻辑
return true; // 中断
}
});
// 方案2:使用 for...of
for (const item of arr) {
if (item === target) break; // 可以中断
}
// 方案3:抛出异常(不推荐)
try {
arr.forEach((item) => {
if (item === target) throw new Error('found');
});
} catch (e) {
if (e.message !== 'found') throw e;
}Q4: filter 和 find 的性能差异?
A: find 更快,因为找到第一个匹配就停止:
// filter - 遍历全部
const result = arr.filter(item => item.id === 1)[0];
// find - 找到即停(更快)
const result = arr.find(item => item.id === 1);Q5: 如何实现数组的深度去重?
A: 使用 filter + JSON.stringify 或 reduce:
// 简单值去重
const unique = [...new Set([1, 2, 2, 3])]; // [1, 2, 3]
// 对象数组去重(基于 JSON)
const arr = [{ id: 1 }, { id: 1 }, { id: 2 }];
const unique = arr.filter(
(item, index) => arr.findIndex((i) => JSON.stringify(i) === JSON.stringify(item)) === index
);
// 对象数组去重(基于属性)
const uniqueById = arr.filter(
(item, index, self) => self.findIndex((i) => i.id === item.id) === index
);
// 使用 reduce
const unique = arr.reduce((acc, cur) => {
const exists = acc.some((item) => item.id === cur.id);
return exists ? acc : [...acc, cur];
}, []);Q6: every 对空数组返回 true 的原因?
A: 这是数学逻辑中的"空真"(vacuous truth)概念:对于空集,所有元素都满足任何条件(因为没有反例)。
[].every((x) => x > 100); // true
[].every((x) => x < 0); // true
// 逻辑:不存在不满足条件的元素,因此"所有元素都满足"Q7: map 和 forEach 哪个更快?
A: 性能差异很小,选择应基于语义:
- 需要返回值 →
map - 只需副作用 →
forEach
// 性能测试(100万元素)
// map: ~55ms
// forEach: ~50ms
// 差异约 10%,可忽略十四、浏览器兼容性
兼容性表
| 方法 | Chrome | Firefox | Safari | Edge | Node.js |
|---|---|---|---|---|---|
forEach | 1+ | 1.5+ | 3+ | 9+ | 0.1+ |
map | 1+ | 1.5+ | 3+ | 9+ | 0.1+ |
filter | 1+ | 1.5+ | 3+ | 9+ | 0.1+ |
reduce | 1+ | 3+ | 4+ | 9+ | 0.1+ |
find/findIndex | 45+ | 25+ | 7.1+ | 12+ | 4.0+ |
findLast/findLastIndex | 97+ | 104+ | 15.4+ | 97+ | 18.0+ |
flat/flatMap | 69+ | 62+ | 12+ | 79+ | 11.0+ |
entries/keys/values | 38+ | 28+ | 8+ | 12+ | 0.12+ |
Polyfill 建议
// find polyfill
if (!Array.prototype.find) {
Array.prototype.find = function (callback, thisArg) {
for (let i = 0; i < this.length; i++) {
if (callback.call(thisArg, this[i], i, this)) {
return this[i];
}
}
return undefined;
};
}
// flat polyfill
if (!Array.prototype.flat) {
Array.prototype.flat = function (depth = 1) {
return this.reduce((acc, cur) => {
if (Array.isArray(cur) && depth > 0) {
acc.push(...cur.flat(depth - 1));
} else {
acc.push(cur);
}
return acc;
}, []);
};
}十五、最佳实践总结
1. 方法选择原则
// ✅ 根据目的选择方法
const data = [1, 2, 3, 4, 5];
// 只需遍历
data.forEach((n) => console.log(n));
// 需要转换后的数组
const doubled = data.map((n) => n * 2);
// 需要筛选
const evens = data.filter((n) => n % 2 === 0);
// 需要聚合结果
const sum = data.reduce((acc, n) => acc + n, 0);
// 需要查找
const found = data.find((n) => n > 3);
// 需要判断
const hasEven = data.some((n) => n % 2 === 0);
const allPositive = data.every((n) => n > 0);2. 避免常见陷阱
// ❌ 在迭代中修改数组长度
arr.forEach((item, index) => {
if (condition) arr.splice(index, 1); // 危险!
});
// ✅ 使用 filter
const filtered = arr.filter((item) => !condition);
// ❌ map 不返回值
arr.map((item) => console.log(item));
// ✅ 使用 forEach
arr.forEach((item) => console.log(item));
// ❌ reduce 不提供初始值
arr.reduce((acc, cur) => acc + cur); // 空数组报错
// ✅ 始终提供初始值
arr.reduce((acc, cur) => acc + cur, 0);3. 链式调用优化
// ❌ 创建多个中间数组
const result = arr
.filter(condition1)
.map(transform1)
.filter(condition2)
.map(transform2);
// ✅ 合并操作
const result = arr
.filter((item) => condition1(item) && condition2(transform1(item)))
.map((item) => transform2(transform1(item)));
// ✅ 或使用 reduce(大数据量)
const result = arr.reduce((acc, item) => {
if (!condition1(item)) return acc;
const transformed = transform1(item);
if (!condition2(transformed)) return acc;
acc.push(transform2(transformed));
return acc;
}, []);小结
数组迭代方法是 JavaScript 开发中最核心的工具之一:
| 方法类别 | 方法名 | 核心用途 |
|---|---|---|
| 遍历 | forEach | 执行副作用 |
| 转换 | map | 数据映射 |
| 过滤 | filter | 条件筛选 |
| 聚合 | reduce/reduceRight | 累计计算 |
| 查找 | find/findIndex/findLast/findLastIndex | 定位元素 |
| 判断 | some/every | 条件检测 |
| 扁平 | flat/flatMap | 数组扁平化 |
| 迭代 | entries/keys/values | 获取迭代器 |
Iterator Helpers(ES2025)
ES2025 为所有内置迭代器(包括数组的 entries()、keys()、values() 返回的迭代器)新增了辅助方法,使迭代器可以像数组一样链式操作,但不会创建中间数组,更加高效:
// Iterator Helpers 链式操作
function* naturals() {
let i = 0
while (true) yield i++
}
const result = naturals()
.take(5) // 取前 5 个: [0, 1, 2, 3, 4]
.filter(x => x % 2 === 0) // 过滤偶数: [0, 2, 4]
.map(x => x * x) // 平方: [0, 4, 16]
console.log([...result]) // [0, 4, 16]
// ... 中间省略 ...
const pairs = [1, 2, 3].values().flatMap(x => [x, x * 2])
console.log([...pairs]) // [1, 2, 2, 4, 3, 6]
// drop() - 跳过前 N 个
const dropped = naturals().drop(3).take(3)
console.log([...dropped]) // [3, 4, 5]Iterator Helper 方法列表:
| 方法 | 说明 | 返回类型 |
|---|---|---|
.map(fn) | 映射每个元素 | Iterator |
.filter(fn) | 过滤元素 | Iterator |
.take(n) | 取前 n 个元素 | Iterator |
.drop(n) | 跳过前 n 个元素 | Iterator |
.flatMap(fn) | 映射后扁平化 | Iterator |
.reduce(fn, init) | 累计计算 | 值 |
.toArray() | 转为数组 | Array |
.forEach(fn) | 遍历执行副作用 | undefined |
.some(fn) | 是否存在满足条件的元素 | boolean |
.every(fn) | 是否所有元素都满足条件 | boolean |
.find(fn) | 查找第一个满足条件的元素 | 值 |
💡 核心原则:选择正确的方法,让代码更简洁、更易读、更高效。记住——函数式不是目的,清晰才是目的。