从数组到对象:JavaScript 遍历语法全解析(ES5 到 ES6 + 超详细指南)

一、为什么需要遍历语法?从数据处理的核心需求说起

在 JavaScript 开发中处理集合数据是高频需求。例如:

  • 数组求和:[1,2,3].reduce((a,c)=>a+c, 0)
  • 对象属性遍历:for (const key in user) console.log(key)
  • DOM 批量操作:document.querySelectorAll('button').forEach(btn => btn.disabled = true)

核心问题:如何高效安全地访问集合中的每个元素?本文系统解析各类遍历语法,覆盖数组、对象、类数组、Map/Set 等全场景,并深入探讨异步遍历、性能优化、内存管理等进阶内容。

二、数组遍历:从基础到函数式编程

1. 基础遍历:for 循环与 for...of

(1)for 循环:手动控制索引

ini 复制代码
const numbers = [10, 20, 30];
let sum = 0;
for (let i = 0; i < numbers.length; i++) {
  sum += numbers[i]; // 性能最优 适合大数据量
}

适用场景:需要中途 break/continue 或精确控制循环次数。

(2)for...of:简洁的迭代语法

dart 复制代码
for (const num of numbers) {
  console.log(num); // 直接获取元素值 支持所有可迭代对象
}

扩展:结合 entries () 获取索引:

javascript 复制代码
for (const [index, num] of numbers.entries()) {
  console.log(`索引${index}: ${num}`);
}

2. 函数式遍历:forEach/map/filter/reduce

(1)forEach:纯数据处理

javascript 复制代码
numbers.forEach((num, index) => {
  console.log(`第${index+1}个元素:${num}`); // 无需手动管理索引
});

注意:无法使用 break 中断 可通过 return 跳过当前迭代。

(2)map/filter:数据转换与筛选

ini 复制代码
const doubled = numbers.map(n => n * 2); // [20, 40, 60]
const filtered = numbers.filter(n => n > 20); // [30]

链式调用:numbers.filter(n => n>10).map(n=>n*2)。

(3)reduce:累加器模式

javascript 复制代码
const total = numbers.reduce((acc, cur) => acc + cur, 0); // 60

高级用法:对象合并 数组去重等。

3. 高级方法:some/every/find/flatMap

(1)条件判断:some/every

ini 复制代码
const hasBigNumber = numbers.some(n => n > 50); // false
const allSmall = numbers.every(n => n < 100); // true

(2)元素查找:find/findIndex

ini 复制代码
const users = [{ id: 1 }, { id: 2 }];
const target = users.find(u => u.id === 2); // { id: 2 }

(3)扁平化映射:flatMap

perl 复制代码
const words = ["a b c".split(' '), "d e f".split(' ')].flatMap(arr => arr); // ["a","b","c","d","e","f"]

三、对象遍历:键值对的安全访问

1. 传统遍历:for...in 与原型链陷阱

javascript 复制代码
const user = { name: 'Alice', age: 28 };
for (const key in user) {
  if (user.hasOwnProperty(key)) { // 必须过滤原型链属性
    console.log(`${key}: ${user[key]}`);
  }
}

缺陷:遍历顺序不确定 包含原型链可枚举属性。

2. ES5 + 方法:keys/values/entries

javascript 复制代码
// 获取属性名数组
const keys = Object.keys(user); // ["name", "age"]
// 遍历键值对(推荐)
for (const [key, value] of Object.entries(user)) {
  console.log(`${key}: ${value}`);
}

3. Symbol 键处理:getOwnPropertySymbols/Reflect.ownKeys

ini 复制代码
const obj = { [Symbol('id')]: 123 };
const symbolKeys = Object.getOwnPropertySymbols(obj); // [Symbol(id)]
const allKeys = Reflect.ownKeys(obj); // [Symbol(id)](包含所有类型键)

四、类数组与特殊数据结构遍历

1. 类数组(NodeList/arguments)

(1)转换为数组:Array.from/ 展开运算符

ini 复制代码
const nodeList = document.querySelectorAll('div');
const divArray = Array.from(nodeList); // 转换后使用数组方法
divArray.forEach(div => div.classList.add('active'));

(2)强制调用数组方法:call/apply

ini 复制代码
Array.prototype.forEach.call(nodeList, div => {
  div.style.color = 'red'; // 直接操作类数组
});

2. Map/Set 遍历

(1)Map:键值对遍历

arduino 复制代码
const map = new Map([['a', 1], ['b', 2]]);
for (const [key, value] of map) {
  console.log(key, value); // 'a' 1 'b' 2
}

(2)Set:值遍历(自动去重)

arduino 复制代码
const set = new Set([1, 2, 1]);
for (const item of set) {
  console.log(item); // 1 2
}

3. TypedArray 与 Generator

(1)TypedArray:二进制数据遍历

arduino 复制代码
const buffer = new ArrayBuffer(4);
const arr = new Uint8Array(buffer);
arr[0] = 65; // ASCII 'A'
for (const byte of arr) {
  console.log(byte); // 65
}

(2)Generator:可中断迭代

javascript 复制代码
function* gen() {
  yield 1;
  yield 2;
}
const it = gen();
console.log(it.next().value); // 1(手动控制迭代)

五、异步遍历:处理 Promise 与流数据

1. for...of 结合 async/await

ini 复制代码
const fetchUrls = async () => {
  const urls = ['https://api1', 'https://api2'];
  for (const url of urls) {
    const data = await fetch(url).then(res => res.json()); // 串行请求
  }
};

2. 异步迭代器:for await...of

javascript 复制代码
async function* asyncGen() {
  await new Promise(resolve => setTimeout(resolve, 1000));
  yield 'a';
}
(async () => {
  for await (const item of asyncGen()) {
    console.log(item); // 1秒后输出'a'
  }
})();

六、性能对比与最佳实践

1. 基准测试(100 万次遍历)

语法 耗时 场景推荐
for 80ms 高性能计算(如游戏循环)
forEach 220ms 业务逻辑处理
for...of 280ms 通用迭代(兼容可迭代对象)

2. 内存管理陷阱

(1)闭包引用泄漏

javascript 复制代码
// 错误:闭包持有DOM引用
const btns = document.querySelectorAll('button');
btns.forEach((btn, i) => {
  btn.onclick = () => console.log(i); // btn引用未释放
});

解决方案:使用弱引用或及时解除绑定。

(2)异步循环中的变量作用域

javascript 复制代码
// 正确:let块级作用域
for (let i = 0; i < 5; i++) {
  setTimeout(() => console.log(i), 100); // 输出0-4
}

七、遍历语法对比表格(终极版)

场景 推荐语法 核心特性 兼容性
数组索引遍历 for 手动控制 性能最优 全兼容
数组函数式遍历 forEach/map 语法简洁 支持链式调用 ES5+
对象安全遍历 Object.entries() 避免原型链污染 ES6+
类数组操作 Array.from() 转换为数组后使用数组方法 ES6+
异步数据迭代 for await...of 处理 Promise 流数据 ES2021+
Symbol 键获取 Object.getOwnPropertySymbols() 专门获取 Symbol 类型键 ES6+
二进制数据遍历 TypedArray + for...of 高效处理 ArrayBuffer 数据 ES6+
可中断迭代 Generator + yield 支持中途暂停和恢复 ES6+

八、总结:选择遍历语法的黄金法则

  1. 数组优先原则

    简单场景用 for...of 性能优先用 for 函数式需求用 map/filter。

  2. 对象遍历安全

    字符串键用 Object.entries () Symbol 键用 Reflect.ownKeys ()。

  3. 类数组转换

    始终先转为数组(Array.from) 避免直接操作类数组。

  4. 异步场景

    串行用 for...of + await 并行用 Promise.all () 流数据用异步迭代器。

  5. 内存与性能

    大数据量用 for 循环 避免 for...in 遍历数组 及时释放闭包引用。

通过掌握不同语法的适用边界和性能特性 你可以在开发中精准选择工具 写出既高效又易维护的代码。遇到复杂场景时 结合 MDN 文档和实际测试 总能找到最佳解决方案!

相关推荐
叶梅树2 小时前
DocsJS npmjs 自动化发布复盘(Trusted Publisher)
前端·npm
一只鹿鹿鹿2 小时前
信息安全等级保护安全建设防护解决方案(总体资料)
运维·开发语言·数据库·面试·职场和发展
喵叔哟2 小时前
9. 【Blazor全栈开发实战指南】--Blazor调用JavaScript
开发语言·javascript·udp
我命由我123452 小时前
Element Plus - Form 的 resetField 方法观察记录
开发语言·前端·javascript·vue.js·html·html5·js
Eward-an3 小时前
【算法竞赛/大厂面试】盛最多水容器的最大面积解析
python·算法·leetcode·面试·职场和发展
清空mega3 小时前
《Vue3 项目结构详解:components、views、assets、router、stores 到底该怎么理解?》
前端·javascript·vue.js
Trouvaille ~3 小时前
【递归、搜索与回溯】专题(七):FloodFill 算法——勇往直前的洪水灌溉
c++·算法·leetcode·青少年编程·面试·蓝桥杯·递归搜索回溯
雨雨雨雨雨别下啦3 小时前
Vue——小白也能学!Day6
前端·javascript·vue.js
XPoet4 小时前
AI 编程工程化:Hook——AI 每次操作前后的自动检查站
前端·后端·ai编程
難釋懷4 小时前
RedisTemplate配置读写分离
前端·bootstrap·html