从数组到对象: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 文档和实际测试 总能找到最佳解决方案!

相关推荐
艾小逗42 分钟前
vue3中的effectScope有什么作用,如何使用?如何自动清理
前端·javascript·vue.js
小小小小宇3 小时前
手写 zustand
前端
Hamm4 小时前
用装饰器和ElementPlus,我们在NPM发布了这个好用的表格组件包
前端·vue.js·typescript
_一条咸鱼_4 小时前
深度揭秘!Android HorizontalScrollView 使用原理全解析
android·面试·android jetpack
_一条咸鱼_4 小时前
揭秘 Android RippleDrawable:深入解析使用原理
android·面试·android jetpack
_一条咸鱼_4 小时前
深入剖析:Android Snackbar 使用原理的源码级探秘
android·面试·android jetpack
_一条咸鱼_4 小时前
揭秘 Android FloatingActionButton:从入门到源码深度剖析
android·面试·android jetpack
_一条咸鱼_4 小时前
深度剖析 Android SmartRefreshLayout:原理、源码与实战
android·面试·android jetpack
_一条咸鱼_4 小时前
揭秘 Android GestureDetector:深入剖析使用原理
android·面试·android jetpack
_一条咸鱼_4 小时前
深入探秘 Android DrawerLayout:源码级使用原理剖析
android·面试·android jetpack