多种遍历方式
JavaScript 遍历方式全解析:从基础到高级
JavaScript 提供了多种遍历数据结构的方式,每种方式都有其适用场景和性能特点。以下是对常见遍历方式的全面汇总,涵盖数组、对象、Map、Set 等结构。
一、数组遍历
1. for 循环(基础版)
const arr = [1, 2, 3];
for (let i = 0; i < arr.length; i++) {
console.log(arr[i]);
}
- 特点:控制灵活,可中断、反向遍历。
- 性能:适合大型数组(避免
length重复计算)。
2. for...of 循环
for (const item of arr) {
console.log(item);
}
- 特点:支持迭代器协议(如数组、字符串、Set、Map)。
- 优势:语法简洁,可使用
break、continue。
3. forEach 方法
arr.forEach((item, index) => {
console.log(item, index);
});
- 特点:内置方法,自动处理索引和边界。
- 限制:无法使用
break或return提前终止。
4. for...in 循环
for (const key in arr) {
console.log(key, arr[key]); // key 为字符串类型
}
- 注意:遍历可枚举属性(包括原型链和非数字键),不推荐用于数组。
5. map/filter/reduce 等高阶方法
const doubled = arr.map(x => x * 2);
const sum = arr.reduce((acc, x) => acc + x, 0);
- 特点:声明式编程,代码简洁,支持链式调用。
- 性能:略低于传统循环(创建中间数组)。
二、对象遍历
1. for...in 循环
const obj = { a: 1, b: 2 };
for (const key in obj) {
console.log(key, obj[key]);
}
- 特点:遍历对象自身和继承的可枚举属性(不含 Symbol 键)。
2. Object.keys()
Object.keys(obj).forEach(key => {
console.log(key, obj[key]);
});
- 特点:仅遍历对象自身的可枚举字符串键。
3. Object.entries()
for (const [key, value] of Object.entries(obj)) {
console.log(key, value);
}
- 优势:直接获取键值对,支持解构和
for...of。
4. Object.getOwnPropertyNames()
Object.getOwnPropertyNames(obj).forEach(key => {
console.log(key, obj[key]);
});
- 特点:包含不可枚举属性(如
Object.defineProperty定义的属性)。
5. Reflect.ownKeys()
Reflect.ownKeys(obj).forEach(key => {
console.log(key, obj[key]);
});
- 特点:包含所有键(字符串和 Symbol,可枚举和不可枚举)。
三、Map 和 Set 遍历
1. Map 遍历
const map = new Map([['a', 1], ['b', 2]]);
// 遍历键值对
for (const [key, value] of map) {
console.log(key, value);
}
// 仅遍历键/值
map.keys();
map.values();
map.entries();
// forEach
map.forEach((value, key) => {
console.log(key, value);
});
2. Set 遍历
const set = new Set([1, 2, 3]);
for (const item of set) {
console.log(item);
}
set.forEach(item => {
console.log(item);
});
- 特点:元素唯一,遍历时按插入顺序。
四、异步遍历
1. for await...of 循环
async function fetchData() {
const asyncIterable = {
[Symbol.asyncIterator]() {
return {
i: 0,
next() {
if (this.i < 3) {
return Promise.resolve({ value: this.i++, done: false });
}
return Promise.resolve({ done: true });
}
};
}
};
for await (const num of asyncIterable) {
console.log(num);
}
}
- 适用场景:遍历异步生成器或 Promise 数组。
五、性能对比与最佳实践
1. 性能排序(从快到慢)
- 传统 for 循环(手动控制索引)
- for...of 循环(现代语法,性能接近传统 for)
- forEach/map/filter(函数调用开销)
- for...in 循环(遍历原型链,性能最差)
2. 选择建议
- 性能敏感场景:使用传统
for或for...of。 - 需要中断遍历:使用
for、for...of或some/every。 - 函数式编程:使用
map/filter/reduce。 - 对象遍历:优先使用
Object.keys()或Object.entries()。
六、常见陷阱与注意事项
1. for...in 的陷阱
Array.prototype.customMethod = () => {};
const arr = [1, 2, 3];
for (const key in arr) {
console.log(key); // 输出: 0, 1, 2, customMethod
}
- 解决方案:使用
hasOwnProperty过滤原型链属性:for (const key in arr) {
if (arr.hasOwnProperty(key)) {
console.log(key);
}
}
2. 异步遍历的错误方式
// 错误:forEach 不等待 Promise
const promises = [Promise.resolve(1), Promise.resolve(2)];
promises.forEach(async promise => {
const result = await promise;
console.log(result); // 不会按顺序执行
});
// 正确:使用 for...of 或 Promise.all
for await (const promise of promises) {
console.log(await promise); // 按顺序执行
}
七、总结表格
| 遍历方式 | 适用结构 | 是否支持中断 | 是否保留顺序 | 性能特点 |
|---|---|---|---|---|
| for 循环 | 数组 | 是 | 是 | 最快 |
| for...of 循环 | 可迭代对象 | 是 | 是 | 快 |
| forEach | 数组、Map、Set | 否 | 是 | 中等 |
| for...in 循环 | 对象 | 是 | 否 | 最慢 |
| Object.keys() | 对象 | 否 | 是 | 快 |
| map/filter | 数组 | 否 | 是 | 中等 |
| for await...of | 异步可迭代对象 | 是 | 是 | 中等(异步) |
根据具体场景选择合适的遍历方式,既能保证代码简洁性,又能优化性能。在现代 JavaScript 中,推荐优先使用 for...of 和高阶数组方法,仅在需要最大性能或特殊控制时使用传统 for 循环。