一、为什么需要遍历语法?从数据处理的核心需求说起
在 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+ |
八、总结:选择遍历语法的黄金法则
-
数组优先原则:
简单场景用 for...of 性能优先用 for 函数式需求用 map/filter。
-
对象遍历安全:
字符串键用 Object.entries () Symbol 键用 Reflect.ownKeys ()。
-
类数组转换:
始终先转为数组(Array.from) 避免直接操作类数组。
-
异步场景:
串行用 for...of + await 并行用 Promise.all () 流数据用异步迭代器。
-
内存与性能:
大数据量用 for 循环 避免 for...in 遍历数组 及时释放闭包引用。
通过掌握不同语法的适用边界和性能特性 你可以在开发中精准选择工具 写出既高效又易维护的代码。遇到复杂场景时 结合 MDN 文档和实际测试 总能找到最佳解决方案!