for...of循环遍历数组时有哪些注意事项?

简介: for...of循环遍历数组时有哪些注意事项?

在 JavaScript 中,for...of 循环是遍历可迭代对象(如数组、字符串、Set、Map 等)的常用语法,相比 for...in 更专注于值的遍历。但在使用时仍需注意以下细节,以避免潜在问题:

一、仅遍历可迭代对象,非可迭代需转换

注意场景

  • for...of 只能遍历实现了 Symbol.iterator 接口的可迭代对象,普通对象(Object)默认不可迭代。
  • 类数组对象(如 arguments、DOM 节点列表)需先转换为数组才能使用 for...of

示例与解决方案

// 错误示例:直接遍历普通对象
const obj = {
    name: '豆包', age: 20 };
for (const value of obj) {
   
  // 报错:Uncaught TypeError: obj is not iterable
}

// 正确做法1:对象转可迭代结构(如Object.entries())
for (const [key, value] of Object.entries(obj)) {
   
  console.log(key, value);  // 输出: "name" "豆包", "age" 20
}

// 错误示例:遍历类数组对象(arguments)
function demo() {
   
  for (const item of arguments) {
   
    // 若浏览器环境不支持,可能报错(需ES6+支持)
  }
}

// 正确做法2:类数组转数组(Array.from或展开运算符)
function demo() {
   
  for (const item of Array.from(arguments)) {
   
    console.log(item);
  }
}
demo(1, 2, 3);

二、遍历数组时无法直接获取索引

注意场景

for...of 直接获取数组元素值,若需索引需额外处理,否则可能导致逻辑错误。

解决方案

  1. 结合 entries() 方法
    const fruits = ['苹果', '香蕉', '橙子'];
    for (const [index, fruit] of fruits.entries()) {
         
    console.log(index, fruit);  // 输出: 0 "苹果", 1 "香蕉", 2 "橙子"
    }
    
  2. 手动维护索引变量
    let index = 0;
    for (const fruit of fruits) {
         
    console.log(index++, fruit);  // 同上效果
    }
    

三、跳过空元素(hole)的处理

注意场景

数组中的空元素(通过 delete 删除或直接留空)会被 for...of 跳过,与 forEach 和普通 for 循环行为不同。

const arr = [1, , 3];  // 中间为空元素
console.log(arr.length);  // 输出: 3

// for...of 遍历效果
for (const item of arr) {
   
  console.log(item);  // 输出: 1, 3(跳过空元素)
}

// forEach 遍历效果
arr.forEach(item => {
   
  console.log(item);  // 输出: 1, undefined, 3(空元素转为undefined)
});

// 普通for循环效果
for (let i = 0; i < arr.length; i++) {
   
  console.log(arr[i]);  // 输出: 1, undefined, 3(同上)
}

解决方案

若需处理空元素,可先通过 fill() 填充或转换为密集数组:

const filledArr = arr.fill(0);  // 空元素转为0
for (const item of filledArr) {
   
  console.log(item);  // 输出: 1, 0, 3
}

四、无法中断遍历的场景限制

注意场景

for...of 支持使用 breakcontinuereturn 控制流程,但无法像 forEach 一样通过 try/catch 以外的方式中断异步操作(如 Promise)。

// 正确示例:使用break中断同步遍历
for (const num of [1, 2, 3, 4]) {
   
  if (num === 3) break;
  console.log(num);  // 输出: 1, 2
}

// 异步场景限制:无法直接中断Promise链
async function demo() {
   
  for (const i of [1, 2, 3]) {
   
    await new Promise(resolve => setTimeout(resolve, 100));
    if (i === 2) break;  // 虽break,但已发出的Promise仍会执行
    console.log(i);  // 输出: 1, 2(i=2时break,但第2次Promise仍完成)
  }
}

解决方案

异步场景中如需严格中断,可改用 for 循环配合 try/catch 或标志位:

async function demo() {
   
  let shouldBreak = false;
  for (let i = 0; i < 3 && !shouldBreak; i++) {
   
    try {
   
      await new Promise(resolve => setTimeout(resolve, 100));
      if (i === 2) {
   
        shouldBreak = true;
        continue;
      }
      console.log(i);  // 仅输出: 1(i=2时标记break,不再继续循环)
    } catch (e) {
   
      // 处理异常
    }
  }
}

五、与解构赋值结合的注意事项

注意场景

for...of 支持解构赋值,但需注意结构匹配,否则可能得到 undefined

const users = [
  {
    name: '张三', age: 18 },
  {
    name: '李四', age: 20 }
];

// 正确解构:获取对象属性
for (const {
    name, age } of users) {
   
  console.log(name, age);  // 正常输出
}

// 错误解构:属性名不匹配
for (const {
    username, age } of users) {
   
  console.log(username);  // 输出: undefined, undefined(users无username属性)
}

解决方案

解构时确保属性名与实际对象一致,或使用默认值避免 undefined

for (const {
    name = '匿名', age } of users) {
   
  console.log(name, age);  // 若对象无name属性,默认显示"匿名"
}

六、遍历迭代器时的状态管理

注意场景

手动使用迭代器(如 arr[Symbol.iterator]())时,for...of 会自动管理迭代状态,但若中途修改原数组,可能影响遍历结果。

const arr = [1, 2, 3];
const iterator = arr[Symbol.iterator]();  // 获取迭代器

// 第一次遍历:正常输出1,2,3
for (const item of iterator) {
   
  console.log(item);
}

// 第二次遍历:迭代器已耗尽,无输出
for (const item of iterator) {
   
  console.log(item);  // 无输出
}

// 错误示例:遍历中修改数组长度
const arr2 = [1, 2, 3];
for (const item of arr2) {
   
  arr2.push(4);  // 遍历中添加元素,部分浏览器可能报错或跳过新元素
  console.log(item);  // 输出: 1, 2, 3(新元素4可能不被遍历)
}

解决方案

  • 迭代器仅可遍历一次,若需重复遍历需重新获取迭代器。
  • 遍历过程中避免修改原数组长度,否则可能导致不可预测的结果。

七、总结:最佳实践与注意要点

  1. 明确可迭代对象:先确认目标是否为可迭代对象,普通对象需转换为 Object.entries() 等结构。
  2. 索引需求处理:通过 entries() 或手动维护索引,避免直接依赖 for...of 的值遍历。
  3. 空元素处理:若数组包含空元素(hole),需注意 for...of 会跳过,按需填充或转换数组。
  4. 异步场景限制for...of 无法中断已发出的异步操作,需配合标志位或其他控制逻辑。
  5. 解构赋值规范:确保解构结构与数据匹配,必要时设置默认值。

遵循以上注意事项,可充分发挥 for...of 循环在遍历可迭代对象时的简洁性与可靠性,避免因语法特性导致的逻辑错误。

相关文章
|
Web App开发 存储 网络协议
chrome命令行参数
chrome命令行参数
420 0
el-tree技巧之只能选中最后一层级的子节点以及查找树结构第一个无子节点的叶节点
el-tree技巧之只能选中最后一层级的子节点以及查找树结构第一个无子节点的叶节点
|
8月前
|
负载均衡 应用服务中间件 nginx
如何使用nginx实现负载均衡?
如何使用nginx实现负载均衡?
|
XML JSON API
淘宝商品详情API接口:获取商品信息的指南
淘宝详情API接口是淘宝开放平台提供的一种API接口,它允许开发者通过编程方式获取淘宝商品的详细信息。这些信息包括商品的基本属性、价格、库存状态、销售策略、卖家信息等,对于电商分析、市场研究或者商品信息管理等场景非常有用。
612 1
|
11月前
|
机器学习/深度学习 人工智能 算法
【AI系统】模型压缩基本介绍
模型压缩旨在通过减少存储空间、降低计算量和提高计算效率,降低模型部署成本,同时保持模型性能。主要技术包括模型量化、参数剪枝、知识蒸馏和低秩分解,广泛应用于移动设备、物联网、在线服务系统、大模型及自动驾驶等领域。
633 4
【AI系统】模型压缩基本介绍
|
12月前
|
前端开发 JavaScript 搜索推荐
HTML与CSS在Web组件化中的核心作用及前端技术趋势
本文探讨了HTML与CSS在Web组件化中的核心作用及前端技术趋势。从结构定义、语义化到样式封装与布局控制,两者不仅提升了代码复用率和可维护性,还通过响应式设计、动态样式等技术增强了用户体验。面对兼容性、代码复杂度等挑战,文章提出了相应的解决策略,强调了持续创新的重要性,旨在构建高效、灵活的Web应用。
258 6
|
存储 并行计算 安全
C++多线程应用
【10月更文挑战第29天】C++ 中的多线程应用广泛,常见场景包括并行计算、网络编程中的并发服务器和图形用户界面(GUI)应用。通过多线程可以显著提升计算速度和响应能力。示例代码展示了如何使用 `pthread` 库创建和管理线程。注意事项包括数据同步与互斥、线程间通信和线程安全的类设计,以确保程序的正确性和稳定性。
263 5
|
XML 存储 Android开发
Android实战经验之Kotlin中快速实现MVI架构
本文介绍MVI(Model-View-Intent)架构模式,强调单向数据流与不可变状态管理,提升Android应用的可维护性和可测试性。MVI分为Model(存储数据)、View(展示UI)、Intent(用户动作)、State(UI状态)与ViewModel(处理逻辑)。通过Kotlin示例展示了MVI的实现过程,包括定义Model、State、Intent及创建ViewModel,并在View中观察状态更新UI。
562 12