JavaScript 遍历对象与数组的方法详解:优缺点与最佳实践

在 JavaScript 开发中,遍历对象和数组是日常编码中最常见的操作之一。然而,JavaScript 提供了多种遍历方式,每种方法都有其适用场景、性能特点和潜在陷阱。本文将系统梳理主流的遍历方法,分析它们的优缺点,并给出相应的最佳实践建议,帮助开发者写出更高效、更安全、更具可读性的代码。


一、数组遍历方法

1. for 循环(传统 for)

for (let i = 0; i < arr.length; i++) {
  console.log(arr[i]);
}

优点

  • 性能最优(尤其在大型数组中)。
  • 支持 breakcontinue
  • 可精确控制索引(如倒序、跳步等)。

缺点

  • 代码冗长,可读性较差。
  • 容易因边界错误导致 bug(如 <= 误用)。

最佳实践

  • 对性能敏感或需要复杂控制逻辑时优先使用。
  • 缓存 length 值以避免重复计算(现代 JS 引擎已优化,但仍是良好习惯):
for (let i = 0, len = arr.length; i < len; i++) { ... }

2. for...of 循环(ES6+)

for (const item of arr) {
  console.log(item);
}

优点

  • 语法简洁,语义清晰。
  • 支持 break / continue / return
  • 可遍历所有可迭代对象(包括 SetMap、字符串等)。

缺点

  • 无法直接获取索引(需配合 entries())。
  • 不支持 IE 浏览器(需 Babel 转译)。

最佳实践

  • 适用于大多数现代项目,尤其是只需访问元素值的场景。
  • 若需索引,使用解构:
for (const [index, item] of arr.entries()) {
  console.log(index, item);
}

3. forEach

arr.forEach((item, index) => {
  console.log(item, index);
});

优点

  • 函数式风格,代码简洁。
  • 自动提供 itemindexarray 三个参数。

缺点

  • 无法中断循环(不能使用 breakreturn 跳出整个循环)。
  • 性能略低于 for 循环(函数调用开销)。
  • 不适合异步操作(除非使用 async/await 包裹,但仍是串行执行)。

最佳实践

  • 用于简单的同步遍历,且不需要中途退出。
  • 避免在 forEach 中使用 async 函数处理异步逻辑(应改用 for...of + await)。

4. map / filter / reduce 等高阶函数

const doubled = arr.map(x => x * 2);
const evens = arr.filter(x => x % 2 === 0);
const sum = arr.reduce((acc, x) => acc + x, 0);

优点

  • 函数式编程,链式调用,表达力强。
  • 不修改原数组(纯函数),易于测试和维护。

缺点

  • 每次调用都会创建新数组(map/filter),内存开销较大。
  • 多次遍历(链式调用时)可能影响性能。

最佳实践

  • 用于数据转换、筛选、聚合等场景。
  • 避免过度链式调用;若性能敏感,可考虑单次 for 循环完成多任务。

二、对象遍历方法

1. for...in

for (const key in obj) {
  if (obj.hasOwnProperty(key)) {
    console.log(key, obj[key]);
  }
}

优点

  • 可遍历对象自身及继承的可枚举属性。
  • 语法简单。

缺点

  • 会遍历原型链上的可枚举属性(需配合 hasOwnProperty 过滤)。
  • 遍历顺序不保证(尽管现代引擎通常按插入顺序)。
  • 无法遍历 Symbol 键。

最佳实践

  • 仅在明确需要包含继承属性时使用。
  • 务必使用 hasOwnProperty 检查,或改用 Object.keys()

2. Object.keys() + forEach / for...of

Object.keys(obj).forEach(key => {
  console.log(key, obj[key]);
});

// 或
for (const key of Object.keys(obj)) {
  console.log(key, obj[key]);
}

优点

  • 仅遍历对象自身的可枚举属性(不含原型链)。
  • 返回数组,可与其他数组方法组合使用。

缺点

  • 仅包含字符串键,不包含 Symbol 键
  • 创建中间数组,内存开销略高。

最佳实践

  • 现代项目中遍历普通对象的首选方式。
  • 若需同时处理字符串键和 Symbol 键,使用 Reflect.ownKeys()

3. Object.entries() / Object.values()

for (const [key, value] of Object.entries(obj)) {
  console.log(key, value);
}

优点

  • 直接获取键值对或值,无需 obj[key] 访问。
  • for...of 结合使用非常优雅。

缺点

  • 同样不包含 Symbol 键。
  • 创建新数组,有内存开销。

最佳实践

  • 当需要同时使用 key 和 value 时,优先使用 Object.entries()

4. Reflect.ownKeys()(包含 Symbol)

Reflect.ownKeys(obj).forEach(key => {
  console.log(key, obj[key]);
});

优点

  • 返回对象自身的所有键(包括字符串和 Symbol)。
  • 不遍历原型链。

缺点

  • 包含不可枚举属性(如 Object.defineProperty 定义的)。
  • 使用频率较低,需了解其行为。

最佳实践

  • 当对象可能包含 Symbol 键,且需要完整遍历时使用。

三、总结对比表

方法适用类型可中断性能是否含原型是否含 Symbol推荐场景
for数组⭐⭐⭐⭐⭐--高性能、复杂控制
for...of数组/可迭代对象⭐⭐⭐⭐--现代项目通用遍历
forEach数组⭐⭐⭐--简单同步遍历
map/filter数组⭐⭐--数据转换/筛选
for...in对象⭐⭐需要继承属性时
Object.keys()对象取决于外层循环⭐⭐⭐普通对象遍历
Object.entries()对象取决于外层循环⭐⭐⭐需要 key-value 对
Reflect.ownKeys()对象取决于外层循环⭐⭐包含 Symbol 的完整遍历

四、通用最佳实践建议

  1. 优先使用语义清晰的方法:如 map 表示映射,filter 表示过滤。
  2. 避免在 forEach 中处理异步逻辑:改用 for...of + await
  3. 遍历对象时默认使用 Object.keys()Object.entries(),避免 for...in 的陷阱。
  4. 性能关键路径使用传统 for 循环
  5. 不要修改正在遍历的数组/对象结构(如在循环中 splice),可能导致跳过元素。

掌握这些遍历方法的本质差异和适用场景,能让你在面对不同需求时做出更合理的技术选型,写出既高效又可维护的 JavaScript 代码。

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐