JavaScript for 循环
简介
for 循环是 JavaScript 中最常用的循环语句,特别适合已知循环次数的场景。ES6 还引入了 for...of 循环,用于遍历可迭代对象。本文介绍三种 for 循环及相关的 forEach 方法。
基本 for 循环
语法
for (初始化; 条件; 更新) {
// 循环体
}
执行流程
- 初始化:循环开始前执行一次,通常用于定义循环变量
- 判断条件:如果为真,执行循环体;如果为假,跳出循环
- 执行循环体
- 更新:执行更新表达式(通常是循环变量自增/自减)
- 回到步骤 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...of 或 forEach |
| 遍历对象属性 | for...in(配合 hasOwnProperty)或 Object.keys() |
| 需要中途中断 | for、while、for...of(不用 forEach) |
| 函数式编程风格 | forEach、map、filter 等 |
对比示例
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 |
数组转换/过滤 | 否 | 返回新数组 |