🔁 JavaScript中的迭代全攻略 - for/while/迭代器/生成器/for await...of详解

🎯 学习目标:全面掌握 JavaScript 的迭代语句与迭代协议,能在不同数据结构与同步/异步场景下选择正确的遍历方式,并写出高性能、可维护的代码。

📊 难度等级:中级

🏷️ 技术标签:#JavaScript #迭代器 #生成器 #for-of #for-in #for await...of

⏱️ 阅读时间:约9分钟


🌟 引言

在日常 JavaScript 开发中,遍历数据"看起来都差不多",但一旦涉及对象、Map/Set、类数组、异步数据流、性能优化,就容易踩坑:

  • 数组用 for...in 导致顺序和性能问题;
  • 把对象当可迭代结构用 for...of 直接报错;
  • 自定义迭代器和生成器不会用,错过优雅的惰性计算;
  • 大数据流用 for await...of 才是正确姿势,却不熟悉协议细节。

今天我用 7 个核心技巧,系统讲清"语句与协议"的边界与最佳实践,帮你写出更高效、可控的迭代代码。


💡 核心技巧详解

1. for vs while:基础循环的选择

🔍 应用场景

索引驱动的数组遍历、需要精细控制起止与步长、或在条件驱动下执行循环。

❌ 常见问题

while 忘记更新计数器,或数组越界导致死循环/错误。

js 复制代码
// ❌ 计数器更新缺失可能导致死循环
let i = 0;
while (i < 3) {
  console.log('loop:', i);
  // 缺失 i++
}

✅ 推荐方案

js 复制代码
/**
 * 使用 for 循环安全遍历数组
 * @param {any[]} list - 任意元素数组
 * @returns {any[]} 同步收集的结果
 */
const traverseWithFor = (list) => {
  const results = [];
  for (let i = 0; i < list.length; i++) {
    // 索引明确,性能稳定
    results.push(list[i]);
  }
  return results;
};

/**
 * 使用 while 循环在条件驱动下遍历
 * @param {number} start - 起始值
 * @param {number} end - 结束值(不含)
 * @returns {number[]} 生成的序列
 */
const traverseWithWhile = (start, end) => {
  const seq = [];
  let i = start;
  while (i < end) {
    seq.push(i);
    i += 1; // 确保条件推进
  }
  return seq;
};

💡 核心要点

  • for 适合索引明确的顺序遍历;
  • while 适合条件驱动的循环,注意推进条件;
  • 数据量大时优先 for,可微调步长与边界以获得稳定性能。

2. for...of vs for...in:遍历语义不要混用

🔍 应用场景

for...of 用于"可迭代对象"(数组、字符串、Map、Set、生成器等);for...in 用于对象的"可枚举属性键"。

❌ 常见问题

对数组使用 for...in 导致遍历到原型属性、顺序不稳定;对普通对象使用 for...of 直接报错。

js 复制代码
// ❌ 对数组使用 for...in(遍历索引字符串,顺序可能受影响)
const arr = [10, 20, 30];
for (const k in arr) {
  console.log('index string:', k); // '0', '1', '2'
}

// ❌ 对对象使用 for...of(非可迭代,抛错)
// for (const v of { a: 1 }) {} // TypeError: {} is not iterable

✅ 推荐方案

js 复制代码
/**
 * 遍历可迭代对象(数组/字符串/Map/Set)
 * @param {Iterable<any>} iterable - 可迭代对象
 * @returns {any[]} 收集到的值
 */
const collectIterableValues = (iterable) => {
  const values = [];
  for (const v of iterable) {
    values.push(v);
  }
  return values;
};

/**
 * 遍历对象自有可枚举属性键
 * @param {Record<string, any>} obj - 普通对象
 * @returns {string[]} 键列表
 */
const collectOwnKeys = (obj) => {
  const keys = [];
  for (const k in obj) {
    if (Object.prototype.hasOwnProperty.call(obj, k)) keys.push(k);
  }
  return keys;
};

💡 核心要点

  • for...of 读取"值";for...in 读取"键";
  • 对象遍历更推荐 Object.keys/values/entries 保持可控与可读;
  • 避免对数组使用 for...in,不稳定且性能较差。

3. 迭代协议:可迭代与迭代器

🔍 应用场景

自定义数据结构的迭代行为,支持惰性求值与可控遍历。

✅ 推荐方案

js 复制代码
/**
 * 自定义可迭代范围 [start, end)
 * @param {number} start - 起始
 * @param {number} end - 结束(不含)
 * @returns {Iterable<number>} 可迭代范围
 */
const createRange = (start, end) => ({
  [Symbol.iterator]: () => {
    let i = start;
    return {
      next: () => (i < end ? { value: i++, done: false } : { value: undefined, done: true })
    };
  }
});

💡 核心要点

  • 可迭代对象需实现 [Symbol.iterator] 返回迭代器;
  • 迭代器需实现 next() 返回 { value, done }
  • 惰性迭代避免一次性创建大量中间数据,利于性能与内存。

4. 生成器(Generator):更优雅的迭代器写法

🔍 应用场景

用更简洁的语法生成迭代序列,支持 yield 惰性输出与中断。

js 复制代码
/**
 * 生成器创建斐波那契序列(前 n 个)
 * @param {number} n - 个数
 * @returns {Iterable<number>} 斐波那契序列
 */
const fibonacci = function* (n) {
  let a = 0, b = 1, i = 0;
  while (i < n) {
    yield a;
    [a, b] = [b, a + b];
    i += 1;
  }
};

💡 核心要点

  • 生成器本质是迭代器,语法更简洁;
  • yield 提供惰性产生值的能力;
  • 可搭配 for...of 直接遍历。

5. 异步迭代与 for await...of:数据流的正确遍历

🔍 应用场景

分页加载、流式文件读写、网络请求批次处理等异步数据源。

js 复制代码
/**
 * 异步生成器:模拟批次拉取数据
 * @param {number} batches - 批次数
 * @returns {AsyncIterable<number>} 异步可迭代数据
 */
const fetchBatches = async function* (batches) {
  for (let i = 1; i <= batches; i++) {
    await new Promise((r) => setTimeout(r, 5));
    yield i; // 每批返回一个结果
  }
};

/**
 * 使用 for await...of 收集异步迭代结果
 * @param {AsyncIterable<any>} asyncIterable - 异步可迭代
 * @returns {Promise<any[]>} 收集到的值
 */
const collectAsync = async (asyncIterable) => {
  const out = [];
  for await (const chunk of asyncIterable) {
    out.push(chunk);
  }
  return out;
};

💡 核心要点

  • for await...of 遍历 AsyncIterable/AsyncGenerator
  • 避免将 Promise[] 直接用 for await...of,应先 Promise.all 或转为异步迭代;
  • 异步迭代是处理大数据流的内存友好方案。

6. 遍历不同数据结构:Map/Set/字符串/类数组

js 复制代码
/**
 * 遍历 Map/Set/字符串/类数组的统一收集器
 * @param {any} target - 目标数据结构
 * @returns {any[]} 收集到的值
 */
const collectValues = (target) => {
  if (target instanceof Map) return Array.from(target.entries());
  if (target instanceof Set) return Array.from(target.values());
  if (typeof target === 'string') return Array.from(target);
  if (typeof target.length === 'number') return Array.from(target); // NodeList/HTMLCollection
  return [];
};

💡 核心要点

  • Map 默认遍历 [key, value]Set 遍历值;
  • 字符串是可迭代对象,可被 for...of 按字符遍历;
  • 类数组(如 NodeList)可用 Array.from 转正,避免 for...in

7. 性能与边界:选择正确的迭代策略

🎯 实战建议

  • 大数组性能更稳定的通常是索引 for
  • 需要可读性与语义清晰,优先 for...of
  • 对象遍历优先 Object.keys/entries,避免 for...in 的原型链干扰;
  • 大数据流与分页拉取用 for await...of,避免一次性内存爆炸;
  • 迭代中修改容器(增删元素)要谨慎,优先生成快照或用惰性策略。

📊 技巧对比总结

技巧 使用场景 优势 注意事项
for 索引驱动数组 性能稳定,可控 注意边界与步长
while 条件驱动循环 灵活 防止死循环
for...of 可迭代对象 语义清晰 不适用于普通对象
for...in 对象键遍历 简单 避免用于数组,过滤原型链
迭代协议 自定义结构 惰性、可控 正确实现 next()
生成器 简洁迭代器 语法优雅 正确使用 yield
for await...of 异步数据流 内存友好 仅用于异步可迭代

🎯 实战应用建议

最佳实践

  1. 使用 for...of 遍历集合类型,提升语义与可读性。
  2. 对象遍历使用 Object.entriesfor...of 组合处理键值对。
  3. 大数据流统一封装为 AsyncGenerator,用 for await...of 消费。
  4. 根据性能需求选择 forfor...of,避免对数组使用 for...in

性能考虑

  • 大数组在热路径中优先 for
  • 遍历中避免闭包捕获大对象,及时释放引用;
  • 生成器/异步生成器的惰性策略可显著降低内存峰值。

💡 总结

这 7 个迭代技巧覆盖了"语句与协议"的完整谱系:从 for/whilefor...of/for...in,再到迭代器/生成器与异步迭代。掌握它们后,你的代码将:

  1. 更语义化,遍历意图清晰;
  2. 更高性能,避免不必要的中间数据与错误用法;
  3. 更易维护,统一封装迭代行为并复用。

🔗 相关资源


💡 今日收获:理解迭代语句与协议的边界,选择合适的遍历方式,写出可读、可控、性能稳定的迭代代码。

如果这篇文章对你有帮助,欢迎点赞、收藏和分享!有任何问题也欢迎在评论区讨论。 🚀

相关推荐
2501_938799427 小时前
CSS Container Queries:基于父容器的响应式设计
前端·css
用户11481867894847 小时前
深入 V8 引擎与浏览器原理:从理论到 Vue 实战的完整指南
前端
spmcor7 小时前
Vue命名冲突:当data和computed相爱相杀...
前端·面试
拉不动的猪7 小时前
单点登录中权限同步的解决方案及验证策略
前端·javascript·面试
znhy@1237 小时前
十三、JS进阶(二)
开发语言·前端·javascript
JarvanMo7 小时前
Flutter:使用图像作为屏幕背景
前端
Mintopia7 小时前
💰 金融Web应用中的AIGC风险控制技术与合规适配
前端·javascript·aigc
Mintopia7 小时前
🚀 Next.js 压力测试与性能调优实战
前端·javascript·全栈
江城开朗的豌豆7 小时前
TypeScript 类型系统漫游指南:从入门到爱上类型安全
前端·javascript