跳到主要内容

JavaScript 嵌套循环

什么是嵌套循环?

嵌套循环是指在一个循环内部包含另一个循环的结构。在JavaScript中,我们可以在任何类型的循环(forwhiledo...while)内部放置另一个循环,形成嵌套结构。这种结构使我们能够处理多维数据或需要重复执行复杂模式的情况。

简单来说,外部循环每执行一次,内部循环就会完整地执行一遍。这就像是时钟的时针和分针:时针走一格(外循环),分针要走完一圈(内循环)。

嵌套循环的基本语法

嵌套for循环

javascript
for (初始化外部循环变量; 外部循环条件; 外部循环变量更新) {
// 外部循环代码

for (初始化内部循环变量; 内部循环条件; 内部循环变量更新) {
// 内部循环代码
// 这里的代码会被执行:外部循环次数 × 内部循环次数
}
}

让我们通过一个简单的例子来理解:

javascript
// 打印3行,每行5个星号
for (let i = 0; i < 3; i++) {
let row = "";
for (let j = 0; j < 5; j++) {
row += "* ";
}
console.log(row);
}

输出:

* * * * * 
* * * * *
* * * * *

在这个例子中:

  • 外部循环负责生成3行
  • 内部循环负责在每行中添加5个星号

嵌套循环的执行流程

为了更好地理解嵌套循环的执行流程,让我们详细跟踪上面示例的执行过程:

  1. 初始化外部循环变量 i = 0
  2. 检查 i < 3(true)
  3. 设置 row = ""
  4. 初始化内部循环变量 j = 0
  5. 检查 j < 5(true)
  6. 执行 row += "* ",row变为 "* "
  7. j++,j变为1
  8. 检查 j < 5(true)
  9. 执行 row += "* ",row变为 "* * "
  10. 这个过程继续直到j = 5,内部循环结束
  11. 输出第一行星号
  12. i++,i变为1
  13. 重复步骤2-11
  14. 以此类推...

嵌套循环的常见应用场景

1. 处理多维数组

多维数组是嵌套循环最常见的应用场景之一:

javascript
// 定义一个2x3的二维数组
const matrix = [
[1, 2, 3],
[4, 5, 6]
];

// 遍历并输出每个元素
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
matrix[0][2] = 3
matrix[1][0] = 4
matrix[1][1] = 5
matrix[1][2] = 6

2. 生成图形模式

嵌套循环可以用来生成各种图形模式,如三角形:

javascript
// 生成一个5行的直角三角形
for (let i = 1; i <= 5; i++) {
let row = "";
for (let j = 1; j <= i; j++) {
row += "* ";
}
console.log(row);
}

输出:

* 
* *
* * *
* * * *
* * * * *

3. 表格生成

在网页开发中,嵌套循环常用于动态生成HTML表格:

javascript
// 生成一个3x3的表格(仅示例,实际中可能使用DOM操作)
let tableHtml = "<table border='1'>";
for (let i = 1; i <= 3; i++) {
tableHtml += "<tr>";
for (let j = 1; j <= 3; j++) {
tableHtml += `<td>单元格[${i},${j}]</td>`;
}
tableHtml += "</tr>";
}
tableHtml += "</table>";

console.log(tableHtml);
// 在实际应用中,我们会将tableHtml插入到DOM中

嵌套循环的优化技巧

1. 避免不必要的计算

在嵌套循环中,如果可能,将不依赖于内部循环的计算移到外部循环:

javascript
// 低效方式
for (let i = 0; i < array.length; i++) {
for (let j = 0; j < array.length; j++) {
// 在每次内部循环迭代时都计算array.length
}
}

// 优化方式
const len = array.length; // 只计算一次
for (let i = 0; i < len; i++) {
for (let j = 0; j < len; j++) {
// 使用预计算的长度
}
}

2. 尽早退出循环

当找到所需结果时,可以使用break语句提前退出循环:

javascript
// 在二维数组中查找特定元素
const matrix = [
[1, 2, 3],
[4, 5, 6],
[7, 8, 9]
];

function findElement(target) {
for (let i = 0; i < matrix.length; i++) {
for (let j = 0; j < matrix[i].length; j++) {
if (matrix[i][j] === target) {
console.log(`找到目标 ${target} 在位置 [${i}, ${j}]`);
return true; // 找到后立即返回
}
}
}
return false; // 没找到
}

findElement(5); // 找到目标 5 在位置 [1, 1]

3. 使用标签跳转(慎用)

JavaScript允许使用标签来标识循环,并跳出多层循环:

javascript
outerLoop: for (let i = 0; i < 3; i++) {
for (let j = 0; j < 3; j++) {
if (i === 1 && j === 1) {
console.log(`跳出所有循环,当前位置 [${i}, ${j}]`);
break outerLoop; // 直接跳出外层循环
}
console.log(`位置 [${i}, ${j}]`);
}
}

输出:

位置 [0, 0]
位置 [0, 1]
位置 [0, 2]
位置 [1, 0]
跳出所有循环,当前位置 [1, 1]
警告

虽然标签跳转可以提高代码效率,但过度使用会降低代码可读性。请谨慎使用此技巧。

实际应用案例

案例1:商品库存管理

假设我们有一个商场的库存系统,需要计算每个部门的总库存价值:

javascript
// 库存数据:[部门][商品][价格, 数量]
const inventory = [
// 电子部门
[
["手机", 1000, 5],
["电脑", 5000, 3],
["耳机", 200, 15]
],
// 服装部门
[
["T恤", 50, 100],
["牛仔裤", 80, 50],
["夹克", 150, 30]
]
];

// 计算每个部门的库存价值
const departmentNames = ["电子部门", "服装部门"];
for (let i = 0; i < inventory.length; i++) {
let totalValue = 0;
for (let j = 0; j < inventory[i].length; j++) {
const product = inventory[i][j];
const productValue = product[1] * product[2]; // 价格 × 数量
totalValue += productValue;
console.log(`${product[0]}: ${product[2]}件 × ${product[1]}元 = ${productValue}`);
}
console.log(`${departmentNames[i]}总价值: ${totalValue}元\n`);
}

输出:

手机: 5件 × 1000元 = 5000元
电脑: 3件 × 5000元 = 15000元
耳机: 15件 × 200元 = 3000元
电子部门总价值: 23000元

T恤: 100件 × 50元 = 5000元
牛仔裤: 50件 × 80元 = 4000元
夹克: 30件 × 150元 = 4500元
服装部门总价值: 13500元

案例2:学生成绩分析

假设我们有一个班级的学生成绩数据,需要计算每个学生的平均分:

javascript
// 学生成绩数据 [学生][科目分数]
const students = [
{ name: "小明", scores: [85, 90, 78] },
{ name: "小红", scores: [92, 86, 93] },
{ name: "小华", scores: [76, 85, 80] }
];

// 科目名称
const subjects = ["数学", "英语", "科学"];

// 计算每个学生的成绩并找出每科的最高分
const highestScores = new Array(subjects.length).fill(0);

for (let i = 0; i < students.length; i++) {
const student = students[i];
let sum = 0;

console.log(`${student.name}的成绩:`);
for (let j = 0; j < student.scores.length; j++) {
const score = student.scores[j];
sum += score;

// 检查是否是该科目的最高分
if (score > highestScores[j]) {
highestScores[j] = score;
}

console.log(` ${subjects[j]}: ${score}`);
}

const average = sum / student.scores.length;
console.log(` 平均分: ${average.toFixed(2)}分\n`);
}

// 输出每科的最高分
console.log("各科目最高分:");
for (let i = 0; i < subjects.length; i++) {
console.log(` ${subjects[i]}: ${highestScores[i]}`);
}

输出:

小明的成绩:
数学: 85分
英语: 90分
科学: 78分
平均分: 84.33分

小红的成绩:
数学: 92分
英语: 86分
科学: 93分
平均分: 90.33分

小华的成绩:
数学: 76分
英语: 85分
科学: 80分
平均分: 80.33分

各科目最高分:
数学: 92分
英语: 90分
科学: 93分

注意事项与陷阱

性能考量

嵌套循环的时间复杂度是各个循环复杂度的乘积。例如,两个嵌套的O(n)循环会产生O(n²)的时间复杂度,这在大数据集上可能导致性能问题。

注意

当数据量很大时,嵌套循环可能导致应用程序响应速度变慢。如果可能,考虑使用更高效的算法或数据结构。

无限循环风险

在编写嵌套循环时,确保每个循环都有适当的终止条件,并且循环变量正确更新:

javascript
// 错误示例 - 无限循环
for (let i = 0; i < 3; i++) {
for (let j = 0; j < 3; j++) {
console.log(i, j);
// 忘记更新j,导致内部循环永不结束
j = j; // 错误!
}
}

总结

嵌套循环是JavaScript中处理多维数据和复杂重复模式的强大工具。通过掌握嵌套循环:

  • 你可以有效处理多维数组和复杂数据结构
  • 生成各种视觉模式和表格
  • 实现真实应用中的复杂数据处理逻辑

然而,使用嵌套循环时需注意性能问题和无限循环风险。通过适当的优化技巧,你可以编写高效、可靠的嵌套循环代码。

练习题

为了巩固你对嵌套循环的理解,尝试以下练习:

  1. 编写一个函数,使用嵌套循环生成乘法表(1-9)
  2. 创建一个程序,找出二维数组中的最大值和最小值
  3. 使用嵌套循环绘制一个空心矩形(只有边框的矩形)
  4. 编写代码检查一个二维数组是否为对称矩阵

进一步学习资源

通过这些资源和练习,你将能够更加熟练地应用嵌套循环来解决各种编程问题。