JavaScript for 循环

简介

for 循环是 JavaScript 中最常用的循环语句,特别适合已知循环次数的场景。ES6 还引入了 for...of 循环,用于遍历可迭代对象。本文介绍三种 for 循环及相关的 forEach 方法。


基本 for 循环

语法

for (初始化; 条件; 更新) {
  // 循环体
}

执行流程

  1. 初始化:循环开始前执行一次,通常用于定义循环变量
  2. 判断条件:如果为真,执行循环体;如果为假,跳出循环
  3. 执行循环体
  4. 更新:执行更新表达式(通常是循环变量自增/自减)
  5. 回到步骤 2
// 从 1 数到 5
for (let i = 1; i <= 5; i++) {
  console.log(i);
}
// 输出:1 2 3 4 5

// 执行流程解析:
// 1. let i = 1;       初始化
// 2. i <= 5? 是      判断
// 3. console.log(i);  执行
// 4. i++              更新(i 变为 2)
// 5. i <= 5? 是      判断
// 6. console.log(i);  执行
// ...以此类推直到 i = 6 时条件为假,跳出循环

三个表达式都可以省略

// 省略初始化(循环变量在外部定义)
let i = 1;
for (; i <= 3; i++) {
  console.log(i);
}

// 省略条件(会变成无限循环,需要在循环体内 break)
for (let j = 0; ; j++) {
  if (j >= 3) break;
  console.log(j);
}

// 省略更新(在循环体内更新)
for (let k = 0; k < 3; ) {
  console.log(k);
  k++;
}

// 全部省略(无限循环)
// for (;;) {
//   console.log('永不停止');
// }

使用 let 定义循环变量(推荐)

// 使用 let:变量作用域在循环内
for (let i = 0; i < 3; i++) {
  console.log(i);
}
// console.log(i); // ReferenceError: i is not defined

// 使用 var:变量提升到函数作用域(不推荐)
for (var j = 0; j < 3; j++) {
  console.log(j);
}
console.log(j); // 3(j 在循环外仍然可访问)

倒序循环

// 从 5 数到 1
for (let i = 5; i >= 1; i--) {
  console.log(i);
}
// 输出:5 4 3 2 1

跳过某些值

for (let i = 1; i <= 5; i++) {
  if (i === 3) continue;  // 跳过 3
  console.log(i);
}
// 输出:1 2 4 5

for…in 循环

语法

for (variable in object) {
  // 遍历对象的每个可枚举属性
}

用途:遍历对象属性

let person = {
  name: 'Alice',
  age: 25,
  city: 'Beijing'
};

for (let key in person) {
  console.log(key + ': ' + person[key]);
}
// 输出:
// name: Alice
// age: 25
// city: Beijing

注意事项

1. 不保证遍历顺序

// 数组虽能用 for...in 遍历,但顺序不保证,且会遍历非数字键
let arr = ['a', 'b', 'c'];
arr.customProp = 'hello';  // 添加自定义属性

for (let index in arr) {
  console.log(index, arr[index]);
}
// 输出可能包含:
// 0 a
// 1 b
// 2 c
// customProp hello  ← 不是我们想要的!

2. 会遍历原型链上的可枚举属性

// 给 Object.prototype 添加一个属性
Object.prototype.customMethod = function() {};

let obj = { a: 1, b: 2 };

for (let key in obj) {
  console.log(key);
}
// 输出:a b customMethod  ← 原型链上的属性也被遍历了!

// 解决方法:使用 hasOwnProperty 过滤
for (let key in obj) {
  if (obj.hasOwnProperty(key)) {
    console.log(key, obj[key]);
  }
}
// 输出:a 1  b 2(只输出自身属性)

3. 不适合遍历数组

// 不推荐:用 for...in 遍历数组
let arr = [10, 20, 30];
for (let i in arr) {
  console.log(i, arr[i]);  // i 是字符串 "0", "1", "2"
}

// 推荐:用普通 for 或 for...of 遍历数组
for (let i = 0; i < arr.length; i++) {
  console.log(i, arr[i]);  // i 是数字 0, 1, 2
}

for…of 循环(ES6)

语法

for (variable of iterable) {
  // 遍历可迭代对象的每个值
}

用途:遍历可迭代对象的值

for...of 可以遍历可迭代对象(Array、String、Map、Set、NodeList、arguments 等)。

// 遍历数组(直接获取值,不是索引)
let colors = ['red', 'green', 'blue'];
for (let color of colors) {
  console.log(color);
}
// 输出:red  green  blue

// 遍历字符串
let str = 'hello';
for (let char of str) {
  console.log(char);
}
// 输出:h e l l o

// 遍历 Set
let uniqueNums = new Set([1, 2, 2, 3]);
for (let num of uniqueNums) {
  console.log(num);
}
// 输出:1 2 3

// 遍历 Map
let map = new Map([['name', 'Alice'], ['age', 25]]);
for (let [key, value] of map) {
  console.log(key + ': ' + value);
}
// 输出:name: Alice  age: 25

不能使用 for…of 遍历普通对象

let obj = { a: 1, b: 2 };

// 报错:TypeError: obj is not iterable
// for (let value of obj) {
//   console.log(value);
// }

// 解决方法1:遍历 values
for (let value of Object.values(obj)) {
  console.log(value);
}

// 解决方法2:遍历 entries
for (let [key, value] of Object.entries(obj)) {
  console.log(key, value);
}

for…in vs for…of 对比

let arr = ['a', 'b', 'c'];

// for...in:遍历键(索引)
for (let key in arr) {
  console.log(key);    // 输出:0 1 2(字符串类型的索引)
}

// for...of:遍历值
for (let value of arr) {
  console.log(value);   // 输出:a b c(实际的数组元素)
}
特性 for…in for…of
遍历内容 键(索引/属性名)
适用对象 普通对象 可迭代对象(Array、String、Map、Set 等)
遍历原型链 会遍历 不会遍历
遍历顺序 不保证 按迭代器顺序
数组遍历 不推荐 推荐

forEach 方法

语法

array.forEach(callback(currentValue, index, array), thisArg);

用途:函数式遍历数组

let fruits = ['apple', 'banana', 'orange'];

fruits.forEach(function(fruit, index) {
  console.log(index + ': ' + fruit);
});
// 输出:
// 0: apple
// 1: banana
// 2: orange

// 使用箭头函数
fruits.forEach((fruit, index) => {
  console.log(index + ': ' + fruit);
});

与 for 循环的对比

let arr = [1, 2, 3, 4, 5];

// for 循环:可以使用 break 和 continue
for (let i = 0; i < arr.length; i++) {
  if (arr[i] === 3) break;  // 可以中断
  console.log(arr[i]);
}
// 输出:1 2

// forEach:不能使用 break 或 continue!
arr.forEach(item => {
  if (item === 3) return;  // return 相当于 continue,不能 break
  console.log(item);
});
// 输出:1 2 4 5(continue 效果,无法完全中断)

无法使用 break 和 continue

let numbers = [1, 2, 3, 4, 5];

// forEach 中不能用 break
// numbers.forEach(n => {
//   if (n === 3) break;  // SyntaxError!
//   console.log(n);
// });

// 如果需要中断,改用 for...of 或 for
for (let n of numbers) {
  if (n === 3) break;  // 可以中断
  console.log(n);
}
// 输出:1 2

break 和 continue

break:跳出整个循环

// 基本 for 循环
for (let i = 1; i <= 10; i++) {
  if (i === 5) {
    break;  // 当 i 等于 5 时,跳出整个循环
  }
  console.log(i);
}
// 输出:1 2 3 4

// for...of 循环
let numbers = [10, 20, 30, 40, 50];
for (let num of numbers) {
  if (num > 25) {
    break;
  }
  console.log(num);
}
// 输出:10 20

continue:跳过本次循环

// 跳过偶数
for (let i = 1; i <= 5; i++) {
  if (i % 2 === 0) {
    continue;  // 跳过本次循环剩余代码
  }
  console.log(i);
}
// 输出:1 3 5

// for...of 中使用 continue
let scores = [85, 92, 78, 96, 88];
for (let score of scores) {
  if (score < 90) continue;
  console.log('优秀:', score);
}
// 输出:优秀: 92  优秀: 96

带标签的 break(label)

// 跳出嵌套循环
outer: for (let i = 0; i < 3; i++) {
  for (let j = 0; j < 3; j++) {
    if (i === 1 && j === 1) {
      break outer;  // 跳出外层循环
    }
    console.log(`i=${i}, j=${j}`);
  }
}
// 输出:
// i=0, j=0
// i=0, j=1
// i=0, j=2
// i=1, j=0

嵌套 for 循环

九九乘法表

for (let i = 1; i <= 9; i++) {
  let line = '';
  for (let j = 1; j <= i; j++) {
    line += `${j}×${i}=${i * j}\t`;
  }
  console.log(line);
}
// 输出:
// 1×1=1
// 1×2=2    2×2=4
// 1×3=3    2×3=6    3×3=9
// ...

遍历二维数组

let matrix = [
  [1, 2, 3],
  [4, 5, 6],
  [7, 8, 9]
];

for (let i = 0; i < matrix.length; i++) {
  for (let j = 0; j < matrix[i].length; j++) {
    console.log(`matrix[${i}][${j}] = ${matrix[i][j]}`);
  }
}
// 输出:
// matrix[0][0] = 1
// matrix[0][1] = 2
// ...

找出所有配对

let team = ['Alice', 'Bob', 'Charlie'];

for (let i = 0; i < team.length; i++) {
  for (let j = i + 1; j < team.length; j++) {
    console.log(`${team[i]} 和 ${team[j]} 一组`);
  }
}
// 输出:
// Alice 和 Bob 一组
// Alice 和 Charlie 一组
// Bob 和 Charlie 一组

for vs while vs for…of:如何选择

场景 推荐方式
已知循环次数(计数) for
循环次数不确定 while
遍历数组的值 for...offorEach
遍历对象属性 for...in(配合 hasOwnProperty)或 Object.keys()
需要中途中断 forwhilefor...of(不用 forEach
函数式编程风格 forEachmapfilter

对比示例

let arr = [10, 20, 30, 40, 50];

// for:灵活,可控制一切
for (let i = 0; i < arr.length; i++) {
  console.log(i, arr[i]);
}

// for...of:简洁,直接获取值
for (let value of arr) {
  console.log(value);
}

// forEach:函数式,但不能中断
arr.forEach(value => console.log(value));

// while:次数不确定时
let sum = 0;
let i = 0;
while (i < arr.length) {
  sum += arr[i];
  i++;
}
console.log('总和:', sum); // 150

性能考虑

缓存数组长度

let arr = new Array(10000).fill(1);

// 不推荐:每次循环都访问 length
for (let i = 0; i < arr.length; i++) {  // arr.length 每次都被读取
  // ...
}

// 推荐:缓存长度(现代 JS 引擎已优化,但习惯上仍推荐)
for (let i = 0, len = arr.length; i < len; i++) {
  // ...
}

倒序循环(更快)

// 倒序循环有时更快(减少属性查找)
let arr = [1, 2, 3, 4, 5];
for (let i = arr.length - 1; i >= 0; i--) {
  console.log(arr[i]);
}
// 输出:5 4 3 2 1

遍历数组的方法性能对比(大概)

for(正向)    ≈ 最快
for(倒序)    ≈ 最快
for...of       ≈ 略慢于 for
forEach       ≈ 略慢于 for...of
for...in       ≈ 最慢(不适合数组)

注意:在大多数应用场景中,可读性比微小的性能差异更重要。除非处理海量数据,否则选择最清晰的写法。


综合示例

示例 1:数组去重

function unique(arr) {
  let result = [];
  for (let i = 0; i < arr.length; i++) {
    let isDuplicate = false;
    for (let j = 0; j < result.length; j++) {
      if (arr[i] === result[j]) {
        isDuplicate = true;
        break;
      }
    }
    if (!isDuplicate) {
      result.push(arr[i]);
    }
  }
  return result;
}

console.log(unique([1, 2, 2, 3, 3, 4])); // [1, 2, 3, 4]

示例 2:找出所有素数

function findPrimes(limit) {
  let primes = [];
  for (let num = 2; num <= limit; num++) {
    let isPrime = true;
    for (let divisor = 2; divisor <= Math.sqrt(num); divisor++) {
      if (num % divisor === 0) {
        isPrime = false;
        break;
      }
    }
    if (isPrime) {
      primes.push(num);
    }
  }
  return primes;
}

console.log(findPrimes(20)); // [2, 3, 5, 7, 11, 13, 17, 19]

示例 3:统计字符串中每个字符的出现次数

function charCount(str) {
  let count = {};
  for (let char of str) {
    if (char === ' ') continue;  // 跳过空格
    count[char] = (count[char] || 0) + 1;
  }
  return count;
}

console.log(charCount('hello world'));
// { h: 1, e: 1, l: 3, o: 2, w: 1, r: 1, d: 1 }

示例 4:对象深拷贝(简单版)

function simpleDeepCopy(obj) {
  let copy = {};
  for (let key in obj) {
    if (obj.hasOwnProperty(key)) {
      if (typeof obj[key] === 'object' && obj[key] !== null) {
        copy[key] = simpleDeepCopy(obj[key]);  // 递归拷贝
      } else {
        copy[key] = obj[key];
      }
    }
  }
  return copy;
}

let original = { a: 1, b: { c: 2 } };
let copied = simpleDeepCopy(original);
copied.b.c = 999;
console.log(original.b.c); // 2(未被影响)

总结:循环方式选择速查表

循环方式 适用场景 可中断 获取内容
for 已知次数、需要精确控制 索引,需通过索引获取值
while 次数不确定、条件驱动 取决于循环体内逻辑
do...while 至少执行一次 取决于循环体内逻辑
for...of 遍历可迭代对象的值 直接获取值
for...in 遍历对象属性 键(属性名)
forEach 数组遍历(函数式) 值、索引、原数组
map/filter 数组转换/过滤 返回新数组