js遍历数组和对象的常用方法有哪些?

精通 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/everyfor...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)后再遍历,减少嵌套层级。

总结

  1. 数组遍历的核心是 "匹配业务意图":需终止用for/for...of,需映射用map,需筛选用filter,需聚合用reduce
  2. 对象遍历优先选择Object.entries() + for...of,无需手动过滤原型属性,语法简洁;
  3. 选择遍历方法的核心原则:语义化 > 简洁性 > 性能,仅在大数据量场景下优先考虑性能。

掌握这些遍历方法,不仅能写出更优雅的代码,更能避免常见的边界错误。记住:没有 "最好" 的方法,只有 "最适合" 当前场景的方法。

相关推荐
小CC吃豆子17 小时前
Java数据结构与算法
java·开发语言
晨旭缘17 小时前
后端日常启动及常用命令(Java)
java·开发语言
小oo呆17 小时前
【学习心得】Python的Pydantic(简介)
前端·javascript·python
星辰_mya17 小时前
RockerMQ之commitlog与consumequeue
java·开发语言
꧁Q༒ོγ꧂17 小时前
C++ 入门完全指南(六)--指针与动态内存
开发语言·c++
IT=>小脑虎17 小时前
2026版 Go语言零基础衔接进阶知识点【详解版】
开发语言·后端·golang
ChangYan.17 小时前
ffi-napi运行失败,报错:No native build was found,解决办法
开发语言
专注VB编程开发20年17 小时前
压栈顺序是反向(从右往左)的,但正因为是反向压栈,所以第一个参数反而离栈顶(ESP)最近。
java·开发语言·算法
say_fall17 小时前
C++ 类与对象易错点:初始化列表顺序 / 静态成员访问 / 隐式类型转换
android·java·开发语言·c++