🔁 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. 更易维护,统一封装迭代行为并复用。

🔗 相关资源


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

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

相关推荐
A***07173 小时前
React数据可视化应用
前端·react.js·信息可视化
泉城老铁4 小时前
Vue2实现语音报警
前端·vue.js·架构
临江仙4554 小时前
前端骚操作:用户还在摸鱼,新版本已悄悄上线!一招实现无感知版本更新通知
前端·vue.js
想个什么名好呢4 小时前
解决uniapp的H5项目uni-popup页面滚动穿透bug
前端
用户93816912553604 小时前
Vue3项目--mock数据
前端
前端加油站4 小时前
一种新HTML 页面转换成 PDF 技术方案
前端·javascript·vue.js
w***Q3505 小时前
Vue打包
前端·javascript·vue.js
有事没事实验室5 小时前
router-link的custom模式
前端·javascript·vue.js
4***V2025 小时前
Vue3响应式原理详解
开发语言·javascript·ecmascript
常乐我净6165 小时前
十、路由和导航
前端