主题
1-9for循环嵌套案例
本页对应 第一章JavaScript基础,共 5 段视频,约 28 分钟。
学习目标
- 能用自己的话说明循环嵌套的结构,并举例解释“外层循环执行一次,内层循环执行全部次数”的执行特点。
- 能独立使用双重
for循环打印 5 行 5 列小星星,并将换行语句放在内层循环结束后。 - 能根据“每行星星数等于当前行号”的规律,编写双重循环打印 5 行三角形星星,并说明
j <= i的作用。 - 能将用户输入的行数、列数转换为数字,并通过双重循环打印对应行数和列数的小星星。
- 能使用双重循环和模板字符串输出 99 乘法表,正确使用列数与行数生成“列 × 行 = 积”的表达式。
- 能用
span包裹乘法表表达式,并通过 CSS 设置显示方式、边框、间距、圆角、颜色和阴影完成美化。
知识点
数组排序sort:为什么需要
实际开发里经常要把一组数字按大小排列,比如成绩、价格、销量。手写排序麻烦,数组自带的 sort() 可以直接排序。本节笔记先演示默认排序,再演示升序、降序,以及排序后取最大值和最小值。
数组排序sort:怎么想
sort() 会修改原数组,所以调用后 arr 本身变了。默认不传比较函数时,它按字符串规则比较,因此 [88, 78, 100, 34, 99] 排成 [100, 34, 78, 88, 99],并不是数值升序。要按数值排,就传比较函数:升序返回 a - b,降序返回 b - a。排好序后,降序数组的第一个是最大值,最后一个是 minced 最小值;笔记里正是先降序,再取 arr[0] 和 arr[arr.length - 1]。
数组排序sort:怎么做
保留笔记代码如下:
javascript
<script>
// 数组排序 数组.sort() 修改原数组
let arr = [88, 78, 100, 34, 99]
// arr.sort()
// console.log(arr) // [100, 34, 78, 88, 99]
// 1. 升序排序
// arr.sort(function (a, b) {
// return a - b
// })
// console.log(arr) // [34, 78, 88, 99, 100]
// 2. 降序排序
arr.sort(function (a, b) {
return b - a
})
console.log(arr) // [100, 99, 88, 78, 34]
// 3. 获取数组最大值和最小值
document.write(`数组的最大值:\${arr[0]}`)
document.write(`数组的最小值:\${arr[arr.length - 1]}`)
</script>数组排序sort:易错点
第一,别把默认 sort() 当成数值排序;第二,比较函数一定要有返回值,升序写 return a - b,降序写 return b - a;第三,sort() 修改原数组,若后面还要用原顺序,要先复制;第四,取最大值和最小值前要明确当前是升序还是降序,降序时首元素最大、尾元素最小。
选择排序:为什么需要
内置 sort() 很方便,但学习选择排序能理解“排序到底怎么发生”,也能训练循环嵌套、索引记录和变量交换。笔记把它标为“了解”,重点是理解原理,不要求替代 sort()。
选择排序:怎么想
选择排序的思路是:从第一个位置开始,在它和后面所有数里找最小数,放到第一个位置;第二轮从第二个位置开始找剩余最小数,放到第二个位置;以此类推。外层循环控制“轮数”,内层循环控制“查找最小值”。转写稿里用打印星星演示过嵌套循环:外层每执行一次,内层要执行完整次数;这里同样如此,外层每轮确定一个位置,内层扫描剩余元素。转写稿中的“否循环/风循环”应纠正为 for 循环,“债”是变量 j。算法可视化可参考:https://visualgo.net/zh/sorting 。
选择排序:怎么做
保留笔记代码如下:
javascript
<script>
// 选择排序算法
let arr = [4, 2, 5, 1, 3]
// 1 [1, 2, 5, 4, 3]
// 2 [1, 2, 5, 4, 3]
// 3 [1, 2, 3, 4, 5]
// 4 [1, 2, 3, 4, 5]
// 1. 外层循环控制是几轮能够比较出来
for (let i = 0; i < arr.length - 1; i++) {
// 最小值的索引号
let minIndex = i
// 2. 里层循环来查找最小值,并且记录最小值的索引号
for (let j = i + 1; j < arr.length; j++) {
if (arr[j] < arr[minIndex]) {
minIndex = j
}
}
// 3. 交换变量
if (minIndex !== i) {
let temp = arr[minIndex]
arr[minIndex] = arr[i]
arr[i] = temp
}
}
console.log(arr)选择排序:易错点
外层循环次数是 arr.length - 1,因为最后一个位置不用再比较;内层必须从 i + 1 开始,避免和自己比较;每轮开始都要把 minIndex 重置为 i;只有 minIndex !== i 时才交换;交换要用临时变量 temp,否则值会被覆盖。调试时可看笔记注释:第一轮把最小值 1 换到前面,后面几轮逐步把 2、3、4 放到对应位置。
课件图示








代码示例
示例一:循环嵌套 —— 打印 N 行 N 列小星星
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>循环嵌套 —— 打印 N 行 N 列小星星</title>
</head>
<body>
<script>
// 用户输入行数和列数(+ 号把字符串转成数字,方便比较)
let rows = +prompt('请输入行数')
let cols = +prompt('请输入列数')
// 外层循环:控制打印几行
for (let i = 1; i <= rows; i++) {
// 内层循环:控制每行打印几颗
for (let j = 1; j <= cols; j++) {
document.write('☆')
}
// 一行全部打印完毕,再换行
document.write('<br>')
}
</script>
</body>
</html>关键行与易错点
for (let i = 1; i <= rows; i++)是外层循环,控制行数;for (let j = 1; j <= cols; j++)是内层循环,控制每行打印几个。外层变量习惯用i(index),内层用j。document.write('<br>')必须写在内层 for 循环的外面、外层循环的里面。如果写进内层循环,就会变成“打印一颗星星就换一次行”,所有星星竖着排成一条线。- 循环嵌套的执行规律:外层循环每执行一次,内层循环要把自己的全部次数跑完。所以行数为 3 时,内层这套“打 5 颗星 + 换行”的流程会被完整执行 3 遍。
prompt拿到的是字符串,用+prompt(...)转成数字再和i做<=比较,否则容易出现意外的比较结果。- 想要实心星星,可以打开输入法软键盘的“特殊符号”面板选实心五角星。
示例二:循环嵌套 —— 打印三角形星星 + 99 乘法表
js
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>循环嵌套 —— 三角形星星 与 99 乘法表</title>
<style>
span {
display: inline-block; /* 让 span 变成一行内可排列的盒子 */
width: 80px;
border: 1px solid pink;
text-align: center; /* 文字水平居中 */
padding: 5px;
margin: 2px;
border-radius: 5px; /* 小圆角 */
color: hotpink;
background: rgba(255, 192, 203, .2); /* 半透明粉色背景 */
box-shadow: 2px 2px 2px rgba(0, 0, 0, .2); /* 小影子 */
}
</style>
</head>
<body>
<script>
// ===== 1. 三角形星星 =====
for (let i = 1; i <= 5; i++) { // 外层:行数,固定 5 行
for (let j = 1; j <= i; j++) { // 内层:每行个数 = 行数
document.write('☆')
}
document.write('<br>')
}
document.write('<hr>')
// ===== 2. 99 乘法表 =====
for (let i = 1; i <= 9; i++) { // 外层:行,1~9
for (let j = 1; j <= i; j++) { // 内层:列,列数 = 当前行号
document.write(`<span>${j} × ${i} = ${i * j}</span>`)
}
document.write('<br>')
}
</script>
</body>
</html>关键行与易错点
- 三角形星星的核心是
j <= i:每行打印的个数等于当前行号(第 1 行 1 颗、第 5 行 5 颗)。这里如果写成j <= 5就退化成矩形了。 - 99 乘法表就是把三角形里的星星换成公式:
\${j} × \${i} = \${i * j}。前面的数用列j,后面的数用行i,因为 2×3 出现在第 2 列、第 3 行;结果是i * j,不要写死。 - 模板字符串的反引号不要写成单引号,否则
${}不会被解析。 <br>依旧写在内层循环之后,保证一整行公式打完再换行。- 美化时
display: inline-block是让span盒子在一行内横向排列的关键;width固定后列才能对齐。半透明背景用rgba()第四位小数控制透明度。
示例三:数组排序 sort 与选择排序
js
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>数组排序 sort 与选择排序</title>
</head>
<body>
<script>
// ========== 1. sort 排序(会修改原数组) ==========
let arr = [88, 78, 100, 34, 99]
// 不传参:默认按字符串排序,结果是 [100, 34, 78, 88, 99]
// arr.sort()
// 升序
arr.sort(function (a, b) {
return a - b
})
console.log(arr) // [34, 78, 88, 99, 100]
// 降序
arr.sort(function (a, b) {
return b - a
})
console.log(arr) // [100, 99, 88, 78, 34]
// 降序后:第一个是最大值,最后一个是能最小值
document.write(`数组的最大值:${arr[0]}<br>`)
document.write(`数组的最小值:${arr[arr.length - 1]}<br>`)
// ========== 2. 选择排序 ==========
let nums = [4, 2, 5, 1, 3]
// 外层循环:一共比较 length - 1 轮
for (let i = 0; i < nums.length - 1; i++) {
let minIndex = i // 假设本轮最小值的索引是 i
// 内层循环:从 i + 1 开始找更小的数
for (let j = i + 1; j < nums.length; j++) {
if (nums[j] < nums[minIndex]) {
minIndex = j // 记录最小值所在的索引
}
}
// 如果最小值不在 i 位置,就交换
if (minIndex !== i) {
let temp = nums[minIndex]
nums[minIndex] = nums[i]
nums[i] = temp
}
}
console.log(nums) // [1, 2, 3, 4, 5]
</script>
</body>
</html>关键行与易错点
arr.sort()不传参数时是按字符串规则排序,所以100会排在34前面,必须传比较函数。- 比较函数
return a - b是升序,return b - a是降序。sort()会修改原数组,升序排完再排降序,操作的是同一个数组。 - 求最大值最小值要看清排序方向:降序后才是
arr[0]最大、arr[arr.length - 1]最小;如果是升序,则正好相反。 - 选择排序外层是
i < arr.length - 1(最后一轮只剩一个数,不用再比);内层从j = i + 1开始,避免自己和自己比较。 minIndex一开始必须赋值成i,而不是0,否则前面已经排好的位置会被破坏。- 交换变量必须借助临时变量
temp,写成a = b; b = a会丢掉数据;if (minIndex !== i)只是优化,索引相同时可以不交换。
常见错误
arr.sort()直接排数字,结果变成[100, 34, 78, 88, 99]现象:数字数组排序后顺序诡异,100 排到了最前面。 原因:不传比较函数时,sort()把所有元素转成字符串按 Unicode 码位比较,"100" < "34"。 修法:数字排序必须传比较函数,升序(a, b) => a - b,降序(a, b) => b - a。以为
sort()返回一个新数组 现象:let newArr = arr.sort(...)之后改newArr,发现原arr也跟着变了。 原因:sort()原地修改数组,返回值就是原数组本身的引用。 修法:需要保留原数组时先arr.slice()复制一份再排序。星星 / 乘法表全部竖着排成一列 现象:想打印 5 行 5 列,结果打印出 25 行 1 列。 原因:
<br>写在了内层循环体内,每打印一个元素就换一次行。 修法:<br>必须放在内层for的右花括号之后、外层循环体内,即"一行全部打印完再换行"。三角形星星每行都是 5 颗 现象:内层条件照抄成
j <= 5,打出来是矩形而不是三角形。 原因:没有把"每行个数"和"当前行号"建立联系,内层条件写成了固定值。 修法:内层条件用外层变量,写成j <= i,让第 i 行恰好打印 i 颗。内外层循环变量都叫
i现象:三角形 / 乘法表输出错乱,甚至页面卡死。 原因:内层let i遮蔽了外层i,外层循环的推进被破坏。 修法:约定外层用i,内层用j(再深一层用k)。选择排序里
minIndex定义位置不对或交换不用临时变量 现象一:排序结果部分正确部分错乱;现象二:两个元素交换后变成同一个值。 原因:minIndex写在了外层循环外面,每轮不重置;交换时直接a = b; b = a。 修法:let minIndex = i必须放在外层循环体内第一行;交换必须借助temp(或解构[arr[i], arr[minIndex]] = [arr[minIndex], arr[i]])。
练习
倒三角星星:打印 5 行,第 1 行 5 颗、第 5 行 1 颗。写出内层循环的条件表达式,并说明它和第 4 条易错点的区别。
成绩排行榜:给定
[88, 78, 100, 34, 99],用sort降序取出前三名,要求原数组保持不变。写出两种实现(先复制再排 / 用toSorted),并说明为什么直接arr.sort()会出问题。选择排序改降序:把笔记中的选择排序改成从大到小,并额外统计一共发生了多少次交换,最后打印交换次数。
健壮的 N 行 M 列星星:用户通过
prompt输入行数和列数,要求:输入 0、负数或非数字时给出提示并中止打印;输入合法时正常输出。思考prompt应该写在哪一层,为什么不能写进循环里。99 乘法表变形:在已完成的下三角乘法表基础上,只打印"行号为偶数"的行(第 2、4、6、8 行),并让每行的乘积结果单独用
<span>加粗。提示:改动只发生在外层循环的步长上。
原视频对照
| # | 视频 | 时长 |
|---|---|---|
| 1 | 今日内容 | 01:46 |
| 2 | 循环嵌套 | 05:35 |
| 3 | 打印5行5列的小星星 | 07:54 |
| 4 | 打印三角形星星 | 06:48 |
| 5 | 99乘法表 | 06:44 |
小结
- 循环嵌套的执行模型是"外层走一步,内层跑完整轮",判断循环次数永远用乘法而不是加法。
- 打印二维图形类问题的通用公式:外层管行数,内层管列数,换行放在两层之间——这三句话能覆盖星星、三角形、乘法表全部变体。
- 图形题从"矩形"到"三角形"的唯一变化点,是把内层的固定上界换成与外层变量挂钩的表达式;会做矩形就应该会做三角形。
sort()是原地方法、默认按字符串比较,这两点是它绝大多数 bug 的来源;数字排序时比较函数不是可选项而是必选项。- 选择排序的三个必要件是"外层轮数
length - 1""每轮重置minIndex""用临时变量交换",缺一个结果就会出错。 - 手写选择排序的价值不在排序本身(实际项目一律用
sort),而在于它是"循环嵌套 + 索引操作 + 变量交换"的最小综合训练场。 - 任何"一行/一组内容结束后才该做的事"(换行、分隔符、求和归零)都应放在对应层循环的外面,这是循环边界感的核心。
- 从今天起建立变量命名直觉:
i行、j列、k第三层,minIndex存索引,命名对得上语义,嵌套层级就不会写乱。