精通 JS 遍历:数组与对象的高效遍历方法论(2026 版)
遍历是 JavaScript 数据处理的基石,从前端 DOM 渲染到后端数据聚合,几乎所有业务场景都离不开数组和对象的遍历操作。但新手常陷入 "方法用错、性能低效、边界踩坑" 的困境 ------ 比如用for...in遍历数组、用forEach强行终止循环、忽略reduce初始值导致异常。本文将从场景适配 、性能优化 、避坑指南三个维度,系统拆解数组与对象的遍历方法,结合实战案例和最佳实践,帮你实现 "选对方法、写对代码、用对性能" 的目标。
一、数组遍历:按场景选方法,而非 "一招鲜"
数组遍历的核心是 "匹配业务意图":是单纯遍历?是生成新数组?是筛选数据?还是聚合计算?不同意图对应不同最优方法,以下按 "基础可控型""函数式迭代型""特殊场景型" 分类讲解。
1. 基础可控型遍历(性能优先)
这类方法完全掌控遍历过程,支持终止 / 跳过,适合大数据量、需精细控制的场景。
(1)原生 for 循环(性能天花板)
最底层、性能最优的遍历方式,无任何额外开销,适合 10 万级以上大数据量遍历。
javascript
运行
const arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
// 基础版
for (let i = 0; i < arr.length; i++) {
// 终止循环:找到第一个大于5的数就停止
if (arr[i] > 5) {
console.log("找到目标值:", arr[i]);
break;
}
console.log("当前值:", arr[i]);
}
// 性能优化版(缓存长度,避免每次读取arr.length)
for (let i = 0, len = arr.length; i < len; i++) {
// 跳过偶数
if (arr[i] % 2 === 0) continue;
console.log("奇数:", arr[i]);
}
// 反向遍历(适合从后删除元素的场景)
for (let i = arr.length - 1; i >= 0; i--) {
if (arr[i] < 3) arr.splice(i, 1); // 从后删除避免索引错乱
}
console.log(arr); // [3,4,5,6,7,8,9,10]
核心优势:
- 性能第一,无函数调用、迭代器等额外开销;
- 支持
break(终止)、continue(跳过)、反向遍历; - 可直接操作索引,适合修改原数组的场景。
(2)for...of 循环(ES6+,简洁可控)
ES6 迭代器语法,兼顾简洁性和可控性,支持遍历所有可迭代对象(数组、Set、Map、字符串等)。
javascript
运行
const arr = [1, 2, 3, 4, 5];
// 基础遍历元素
for (const item of arr) {
if (item === 3) break; // 支持终止
console.log(item); // 1,2
}
// 同时获取索引和元素(结合entries())
for (const [index, item] of arr.entries()) {
console.log(`索引${index}:${item}`);
}
// 遍历Set(可迭代对象示例)
const set = new Set([1, 2, 3]);
for (const item of set) {
console.log(item);
}
核心优势:
- 语法简洁,比原生 for 循环易读;
- 支持
break/continue,弥补 forEach 的缺陷; - 适配所有可迭代对象,通用性强。
2. 函数式迭代方法(可读性优先)
ES5+ 新增的数组原型方法,基于函数式编程思想,无需手动控制索引,是日常开发的主流选择。所有方法均不修改原数组,核心差异在于 "返回值" 和 "终止逻辑"。
核心方法对比表(建议收藏)
| 方法名 | 核心用途 | 返回值 | 终止逻辑 | 典型场景 |
|---|---|---|---|---|
| forEach | 单纯遍历 | 无(undefined) | 无法终止(return 仅跳过当前轮次) | 列表渲染、无返回值的遍历操作 |
| map | 映射转换 | 新数组(长度与原数组一致) | 无终止(遍历所有元素) | 数据格式转换(如接口数据适配) |
| filter | 筛选数据 | 新数组(符合条件的元素) | 无终止(遍历所有元素) | 条件筛选(如筛选已完成的任务) |
| some | 存在性判断 | 布尔值(true/false) | 返回 true 时立即终止 | 判断是否有符合条件的元素 |
| every | 全量判断 | 布尔值(true/false) | 返回 false 时立即终止 | 判断所有元素是否符合规则 |
| find | 查找元素 | 第一个符合条件的元素 /undefined | 返回 true 时立即终止 | 查找唯一元素(如根据 ID 找用户) |
| findIndex | 查找索引 | 第一个符合条件的索引 /-1 | 返回 true 时立即终止 | 查找元素位置(如定位错误数据) |
| reduce | 聚合计算 | 最终聚合值 | 无终止(遍历所有元素) | 求和、拼接、扁平化数组等 |
实战示例(带业务场景)
javascript
运行
const users = [
{ id: 1, name: "张三", age: 22, status: "active" },
{ id: 2, name: "李四", age: 18, status: "inactive" },
{ id: 3, name: "王五", age: 25, status: "active" },
];
// 1. forEach:渲染用户列表(无返回值)
users.forEach(user => {
console.log(`用户${user.name}:状态${user.status}`);
});
// 2. map:转换数据格式(前端适配)
const userOptions = users.map(user => ({
label: user.name,
value: user.id
}));
console.log(userOptions);
// [{label: '张三', value: 1}, {label: '李四', value: 2}, {label: '王五', value: 3}]
// 3. filter:筛选活跃用户
const activeUsers = users.filter(user => user.status === "active");
console.log(activeUsers); // 张三、王五
// 4. some:判断是否有未成年用户
const hasMinor = users.some(user => user.age < 18);
console.log(hasMinor); // false(李四18岁,不满足<18)
// 5. every:判断所有用户是否成年
const allAdult = users.every(user => user.age >= 18);
console.log(allAdult); // true
// 6. find:根据ID查找用户
const targetUser = users.find(user => user.id === 2);
console.log(targetUser); // 李四
// 7. reduce:计算所有用户年龄总和
const totalAge = users.reduce((sum, user) => sum + user.age, 0);
console.log(totalAge); // 22+18+25=65
// 8. reduce进阶:按状态分组
const groupedUsers = users.reduce((obj, user) => {
if (!obj[user.status]) obj[user.status] = [];
obj[user.status].push(user);
return obj;
}, {});
console.log(groupedUsers);
// {active: [张三, 王五], inactive: [李四]}
避坑要点:
- ❌ 不要用
forEach尝试终止循环(return无效),改用some/every或for...of; - ❌
reduce必须传初始值(第二个参数),否则空数组会报错,单元素数组直接返回该元素; - ❌
map不要用于无返回值的遍历(浪费性能),改用forEach; - ✅
some/every是 "短路遍历",找到结果立即终止,比forEach+ 判断更高效。
3. 特殊场景遍历
(1)for...in(不推荐遍历数组)
for...in设计初衷是遍历对象,遍历数组时会包含原型链属性,且索引为字符串类型:
javascript
运行
Array.prototype.foo = "原型属性";
const arr = [1, 2, 3];
for (const key in arr) {
console.log(key); // 0,1,2,foo(包含原型属性)
console.log(typeof key); // string(索引是字符串)
}
结论 :数组遍历绝对不要用for...in!
(2)Array.from(遍历类数组 / 可迭代对象)
适合将 DOM 集合、arguments 等类数组对象转为数组并遍历:
javascript
运行
// 遍历DOM节点列表
const lis = document.querySelectorAll("li");
const liTexts = Array.from(lis, li => li.textContent);
console.log(liTexts); // 所有li的文本内容
// 遍历字符串(可迭代对象)
const str = "hello";
const charArr = Array.from(str, char => char.toUpperCase());
console.log(charArr); // ['H','E','L','L','O']
二、对象遍历:精准获取键值对
对象遍历的核心是 "区分自身属性 / 原型属性""可枚举 / 不可枚举属性""普通键 / Symbol 键",以下按 "常用程度" 排序讲解。
1. 主流方案:Object.entries () + for...of(ES6+)
最推荐的对象遍历方式,无需过滤原型属性,直接获取键值对,语法简洁:
javascript
运行
const user = {
name: "张三",
age: 22,
status: "active"
};
// 遍历键值对
for (const [key, value] of Object.entries(user)) {
console.log(`${key}: ${value}`);
}
// 结合解构+forEach
Object.entries(user).forEach(([key, value]) => {
console.log(`${key}: ${value}`);
});
核心优势:
- 仅遍历对象自身可枚举属性 ,无需
hasOwnProperty过滤; - 直接获取 [key, value],无需手动
obj[key]取值; - 语法统一,与数组遍历风格一致。
2. 基础方案:for...in + hasOwnProperty
传统对象遍历方法,需手动过滤原型属性,否则会遍历到原型链上的可枚举属性:
javascript
运行
const user = { name: "张三", age: 22 };
Object.prototype.foo = "原型属性";
// 正确用法:过滤原型属性
for (const key in user) {
if (user.hasOwnProperty(key)) {
console.log(`${key}: ${user[key]}`); // 仅name、age
}
}
适用场景 :仅兼容 ES5 的老旧项目,新项目优先用Object.entries()。
3. 专项方案:按需求获取键 / 值
Object.keys(obj):获取自身可枚举键的数组;Object.values(obj):获取自身可枚举值的数组;
javascript
运行
const user = { name: "张三", age: 22 };
// 获取所有键
const keys = Object.keys(user); // ['name', 'age']
// 获取所有值
const values = Object.values(user); // ['张三', 22]
// 遍历键
keys.forEach(key => console.log(key));
// 遍历值
values.forEach(value => console.log(value));
4. 特殊场景:遍历不可枚举 / Symbol 属性
业务中极少用到,但面试常考,需掌握Object.getOwnPropertyNames()和Object.getOwnPropertySymbols():
javascript
运行
// 定义不可枚举属性和Symbol键
const symKey = Symbol("id");
const obj = {
name: "张三"
};
// 定义不可枚举属性
Object.defineProperty(obj, "id", {
value: 1001,
enumerable: false // 不可枚举
});
// 定义Symbol键
obj[symKey] = "symbol-value";
// 1. 获取所有自身属性(含不可枚举,不含Symbol)
const allKeys = Object.getOwnPropertyNames(obj);
console.log(allKeys); // ['name', 'id']
// 2. 获取所有Symbol键
const symKeys = Object.getOwnPropertySymbols(obj);
console.log(symKeys); // [Symbol(id)]
// 3. 遍历所有自身属性(含不可枚举+Symbol)
const allProps = [...allKeys, ...symKeys];
allProps.forEach(key => {
console.log(`${key}: ${obj[key]}`);
});
三、选型决策树(快速选对方法)
数组遍历选型
预览
查看代码
是
否
无返回值
新数组(映射)
新数组(筛选)
布尔值(存在性)
单个元素/索引
聚合值(求和/分组)
数组遍历
是否需要终止/跳过?
for循环 / for...of
是否需要返回值?
forEach
map
filter
some/every
find/findIndex
reduce
graph TD
A[数组遍历] --> B{是否需要终止/跳过?}
B -->|是| C[for循环 / for...of]
B -->|否| D{是否需要返回值?}
D -->|无返回值| E[forEach]
D -->|新数组(映射)| F[map]
D -->|新数组(筛选)| G[filter]
D -->|布尔值(存在性)| H[some/every]
D -->|单个元素/索引| I[find/findIndex]
D -->|聚合值(求和/分组)| J[reduce]
是
否
无返回值
新数组(映射)
新数组(筛选)
布尔值(存在性)
单个元素/索引
聚合值(求和/分组)
数组遍历
是否需要终止/跳过?
for循环 / for...of
是否需要返回值?
forEach
map
filter
some/every
find/findIndex
reduce

豆包
你的 AI 助手,助力每日工作学习
对象遍历选型
预览
查看代码
生成失败,请重试
graph TD
A[对象遍历] --> B{是否需要键值对?}
B -->|是| C[Object.entries() + for...of]
B -->|否| D{仅需键?}
D -->|是| E[Object.keys()]
D -->|否| F[Object.values()]
A --> G{是否需遍历不可枚举/Symbol?}
G -->|是| H[Object.getOwnPropertyNames() + Object.getOwnPropertySymbols()]
G -->|否| I[主流方案]
生成失败,请重试

豆包
你的 AI 助手,助力每日工作学习
四、性能与最佳实践
1. 性能对比(10 万条数据测试)
| 方法 | 执行时间(ms) | 适用场景 |
|---|---|---|
| for 循环 | ~5 | 大数据量、需极致性能 |
| for...of | ~8 | 中等数据量、需简洁可控 |
| forEach | ~10 | 小数据量、无终止需求 |
| map/filter | ~12 | 小数据量、需返回新数组 |
| for...in(数组) | ~50 | 绝对避免 |
2. 最佳实践
- ✅ 业务代码优先保证可读性,大数据量(10 万 +)再优化性能;
- ✅ 数组遍历优先用
map/filter/some/every/reduce(语义化),而非 forEach + 手动逻辑; - ✅ 对象遍历优先用
Object.entries(),替代for...in; - ✅ 避免在遍历中修改原数组(如 splice/push),易导致索引错乱;
- ✅ 遍历嵌套数据时,优先扁平化(如
flatMap)后再遍历,减少嵌套层级。
总结
- 数组遍历的核心是 "匹配业务意图":需终止用
for/for...of,需映射用map,需筛选用filter,需聚合用reduce; - 对象遍历优先选择
Object.entries() + for...of,无需手动过滤原型属性,语法简洁; - 选择遍历方法的核心原则:语义化 > 简洁性 > 性能,仅在大数据量场景下优先考虑性能。
掌握这些遍历方法,不仅能写出更优雅的代码,更能避免常见的边界错误。记住:没有 "最好" 的方法,只有 "最适合" 当前场景的方法。