JavaScript 循环结构:forfor...infor...of 区别

1. 文档概述

本文档旨在详细说明 JavaScript 中三种常用循环结构(forfor...infor...of)的语法、核心特点、适用场景及差异,帮助开发者根据业务需求选择最优循环方式,避免使用误区。

文档信息 说明
标题 JS 循环结构区别文档
适用版本 ES5+(for...of 为 ES6 新增)
核心目标 明确三种循环的适用边界

2. 基本 for 循环

for 是 JavaScript 中最基础、灵活性最高的循环结构,通过手动控制索引/条件实现迭代,适用于需精确控制循环过程的场景。

2.1 语法格式

for (初始化表达式; 条件表达式; 增量/减量表达式) {
  // 循环体:满足条件时执行的代码
}
  • 初始化表达式:循环前执行一次,用于定义索引变量(如 let i = 0)。
  • 条件表达式:每次循环前判断,结果为 true 时执行循环体(如 i < arr.length)。
  • 增量/减量表达式:每次循环体执行后执行,用于更新索引(如 i++i--)。

2.2 核心特点

  1. 手动管理索引:需显式定义索引的初始值、终止条件及更新逻辑,灵活性高。
  2. 适用范围广:支持数组、类数组对象(如 arguments)、自定义循环次数的场景。
  3. 支持循环控制:可通过 break(中断循环)、continue(跳过当前迭代)控制流程。

2.3 使用示例

示例 1:遍历数组(通过索引访问元素)
const fruits = ["apple", "banana", "orange"];
// 初始化索引i=0,条件i<数组长度,每次循环i自增1
for (let i = 0; i < fruits.length; i++) {
  console.log(`索引 ${i}${fruits[i]}`); 
  // 输出:索引 0:apple → 索引 1:banana → 索引 2:orange
}
示例 2:自定义循环次数(非数组场景)
// 循环输出 1~5 的整数
for (let num = 1; num <= 5; num++) {
  console.log(num); // 输出:1 2 3 4 5
}

2.4 注意事项

  • 若使用 var 定义索引变量,会存在变量提升导致的作用域污染问题,建议用 let(块级作用域)。
  • 遍历数组时,若数组长度固定,可将长度缓存(const len = arr.length),避免每次循环重复计算长度。

3. for...in 循环

for...in 专为遍历对象属性设计,迭代对象的可枚举属性(包括自身属性和原型链继承的属性),遍历结果为属性名(而非属性值)。

3.1 语法格式

for (const 属性名变量 in 目标对象) {
  // 循环体:处理属性的逻辑
}

3.2 核心特点

  1. 遍历属性名:迭代对象的所有可枚举属性,变量存储的是属性名(如对象 {name: "Tom"} 中,变量会取到 "name")。
  2. 原型链穿透:默认会遍历对象原型链上的可枚举属性,需通过 obj.hasOwnProperty(属性名) 过滤自身属性。
  3. 不适合数组:遍历数组时会将索引视为字符串(如 "0""1"),且可能遍历到数组的自定义非数字属性(如 arr.custom = "test")。

3.3 使用示例

示例 1:遍历对象(过滤原型链属性)
const user = { name: "Alice", age: 25, gender: "female" };

// 遍历对象属性,用 hasOwnProperty 过滤原型链属性
for (const key in user) {
  if (user.hasOwnProperty(key)) { // 关键:确保只处理对象自身属性
    console.log(`属性 ${key}${user[key]}`); 
    // 输出:属性 name:Alice → 属性 age:25 → 属性 gender:female
  }
}
示例 2:不推荐遍历数组(存在隐患)
const arr = [10, 20, 30];
arr.customProp = "不应该被遍历的属性"; // 给数组添加自定义非数字属性

for (const key in arr) {
  console.log(key, arr[key]); 
  // 输出:0 10 → 1 20 → 2 30 → customProp 不应该被遍历的属性(意外包含自定义属性)
}

3.4 注意事项

  • 必须过滤原型链:若未使用 hasOwnProperty,可能遍历到原型链上的无关属性(如 Object.prototype 上的自定义方法)。
  • 遍历顺序不确定:对于数字属性(如数组索引),浏览器会按升序遍历,但非数字属性的顺序不固定,不依赖遍历顺序时慎用。

4. for...of 循环(ES6 新增)

for...of 是 ES6 引入的可迭代对象专用循环,直接迭代对象的元素值(而非索引或属性名),适用于数组、字符串、Map、Set 等可迭代对象。

4.1 语法格式

for (const 元素值变量 of 可迭代对象) {
  // 循环体:处理元素的逻辑
}
  • 可迭代对象:指实现了 Symbol.iterator 接口的对象,常见类型包括:数组、字符串、Map、Set、argumentsNodeList(DOM 节点集合)等。

4.2 核心特点

  1. 直接获取元素值:无需通过索引或属性名间接访问,变量直接存储元素值(如数组 [1,2,3] 中,变量取到 123)。
  2. 不遍历属性:仅关注可迭代对象的“元素”,不会遍历自定义属性(如数组的 customProp 不会被迭代)。
  3. 支持循环控制:可通过 breakcontinue 控制循环,且支持 async/await(异步迭代)。
  4. 不支持普通对象:普通对象(如 {a:1, b:2})默认未实现 Symbol.iterator,无法直接用 for...of 遍历(需手动实现迭代器)。

4.3 使用示例

示例 1:遍历数组
const numbers = [1, 2, 3, 4];
for (const num of numbers) {
  if (num % 2 === 0) continue; // 跳过偶数
  console.log(num); // 输出:1 → 3
}
示例 2:遍历字符串(按字符迭代)
const str = "hello";
for (const char of str) {
  console.log(char); // 输出:h → e → l → l → o
}
示例 3:遍历 Map(按键值对迭代)
const productMap = new Map();
productMap.set("phone", 5999);
productMap.set("laptop", 9999);

// Map 迭代时,元素为 [键, 值] 数组
for (const [key, value] of productMap) {
  console.log(`商品 ${key}${value}`); 
  // 输出:商品 phone:5999 元 → 商品 laptop:9999 元
}

4.4 注意事项

  • 普通对象不可直接遍历:若需用 for...of 遍历普通对象,需手动实现 Symbol.iterator 接口,示例如下:
 const obj = { a: 1, b: 2 };
 // 手动实现迭代器
 obj[Symbol.iterator] = function* () {
   for (const key of Object.keys(this)) {
     yield [key, this[key]];
   }
 };
 // 此时可用 for...of 遍历
 for (const [key, value] of obj) {
   console.log(key, value); // 输出:a 1 → b 2
 }

5. 三种循环的核心差异对比

对比维度 基本 for 循环 for...in 循环 for...of 循环
适用对象 数组、类数组、自定义循环次数 普通对象(可枚举属性) 可迭代对象(数组、字符串、Map 等)
遍历内容 需通过索引访问元素 对象的属性名(字符串) 可迭代对象的元素值
原型链影响 无(仅访问自身元素) 默认遍历原型链属性(需过滤) 无(仅遍历自身元素)
数组兼容性 支持(需手动管理索引) 不推荐(遍历索引字符串+自定义属性) 推荐(直接获取元素值)
循环控制 支持 break/continue 支持 break/continue 支持 break/continue
代码简洁性 较繁琐(需写索引逻辑) 中等(需过滤原型链) 简洁(直接处理元素)

6. 最佳实践建议

  1. 遍历数组:优先使用 for...of(简洁、直接获取元素);若需索引,可结合 Array.prototype.entries() 使用 for...of(如 for (const [i, val] of arr.entries()))。
  2. 遍历普通对象:仅使用 for...in,且必须搭配 hasOwnProperty 过滤原型链属性;或使用 Object.keys(obj) 结合 for...of(如 for (const key of Object.keys(obj)))。
  3. 自定义循环次数:使用基本 for 循环(如倒计时、固定次数迭代)。
  4. 遍历 Map/Set/字符串:强制使用 for...of(最贴合场景,无需额外处理)。

7. 总结

三种循环结构无“优劣”之分,核心是匹配场景

  • for:需精确控制索引或循环次数时用;
  • for...in:仅用于遍历普通对象的可枚举属性(需过滤原型链);
  • for...of:遍历可迭代对象(数组、Map 等)时优先用,兼顾简洁性与可读性。

合理选择循环方式,可提升代码效率与可维护性,避免常见使用误区(如用 for...in 遍历数组)。

更多推荐