Web-Dev-For-Beginners 实战作业拆解:用 4 个编程练习吃透 JavaScript 数组与循环
本文围绕开源课程 Web-Dev-For-Beginners 中 2-js-basics 第 4 课「数组与循环」的课后作业 展开(阿拉伯语译文见 translations/ar/2-js-basics/4-arrays-loops/assignment.md)。该作业要求你独立完成 4 个由浅入深的编程练习,覆盖
for循环自定义步进、数组极值与求和函数、字符串数组的多循环遍历,以及对象数组的条件过滤与平均分统计。读完本文,你将能逐题理解考察要点、拿到可复跑的参考实现、掌握浏览器控制台的测试方法,并按评分标准自我评估。
这篇作业是 JavaScript 基础四课(数据类型 → 函数与方法 → 条件判断 → 数组与循环)的收官实践,也是通往后续购物车、好友列表、数据统计等真实场景的必经之路。课程用一句话点明了它的价值:数组是把多份信息装进一个容器,循环则让你无需重复编写代码即可高效处理容器里的全部数据。
作业全景:练什么、怎么练、如何判分
四个练习的目标设计
| 练习 | 标题 | 核心考点 |
|---|---|---|
| 1 | Number Pattern Generator(数字模式生成器) | for 循环三段式与自定义迭代步进 |
| 2 | Array Analysis(数组分析) | 函数封装 + 遍历求最大值、最小值、总和 |
| 3 | String Array Processing(字符串数组处理) | 传统 for、for...of、forEach 三种遍历方式对比 |
| 4 | Data Filtering(数据过滤,进阶) | 对象数组 + 条件筛选 + 平均值计算 |
难度逐级递进:练习 1 只处理单个循环,练习 2 引入"数据 + 函数 + 返回结果"的分析模式,练习 3 开始训练同一种数据结构用不同循环风格处理的能力,练习 4 则模拟真实业务中最常见的"对象数组筛选与聚合"场景。
配套资源
本课的讲解正文位于 4-arrays-loops 课程 README,其中包含了数组创建与索引、push/pop/shift/unshift/indexOf/includes 等内置方法、四类循环的语法,以及"用一次循环同时算总和/最高/最低分"的经典数据处理示例(见 课程数据统计示例)。做作业前建议先把这些语法在浏览器控制台里跑一遍——作业本身就是在检验你是否真正掌握了它们。
练习 1:Number Pattern Generator(数字模式生成器)
需求原文(完整继承):
- 编写一个程序,输出 1–20 之间每隔三个数出现一次的数字,并打印到控制台;
- 必须使用
for循环,且采用自定义增量; - 以对用户友好的方式展示数字;
- 添加能解释你逻辑的描述性注释。
预期输出:
3, 6, 9, 12, 15, 18
提示:修改
for循环括号中的**迭代表达式(iteration-expression)**即可实现跳数。
知识点:for 循环的三个组成部分
从 课程 for 循环小节 可知,for 循环由三个有序执行的部分组成:
| 组成部分 | 作用 | 示例 |
|---|---|---|
| 初始化(initialization) | 设定起点 | let i = 3 |
| 条件(condition) | 决定何时继续 | i < 20 |
| 迭代表达式(iteration) | 决定每轮如何更新 | i += 3 |
作业提示正是抓住第三部分做文章:把常见的 i++(每次 +1)改为 i += 3,循环体每执行一次 i 就前进 3 位,天然跳过中间数字。
参考实现
// 从 3 开始(1-20 范围内第一个能被 3 整除的数字)
// 条件 i < 20 确保不会越界打印 21
// 迭代表达式 i += 3 是本题的核心:每轮跳过 2 个数字
for (let i = 3; i < 20; i += 3) {
console.log(i);
}
逐行执行过程为:i=3 → 条件 3<20 成立 → 打印 → i=6 → … → 打印 18 → i=21 → 条件 21<20 不成立 → 循环结束。控制台逐行输出 3 6 9 12 15 18。
用户友好格式与变体
逐行打印在控制台没问题,但若想输出与预期一致的单行格式,可以先收集再拼接:
const pattern = [];
for (let i = 3; i < 20; i += 3) {
pattern.push(i); // 用数组方法 push 把结果收集起来
}
console.log(pattern.join(", ")); // "3, 6, 9, 12, 15, 18"
这里顺带复习了数组的 push()(末尾追加)与 join()(以分隔符合并为字符串)两个方法,也是本课数组基础知识的实际调用。若把 i += 3 改成 i += 5,输出会变成 5, 10, 15——这正是练习想要你体会的:循环结构的控制力来自对三个表达式的任意定制。
练习 2:Array Analysis(数组分析)
需求原文(完整继承):
- 创建一个名为
numbers的数组,至少包含 8 个不同的数值; - 编写函数
findMaximum():返回数组中最大的数; - 编写函数
findMinimum():返回数组中最小的数; - 编写函数
calculateSum():返回所有数字的总和; - 逐个测试每个函数并展示结果。
加分挑战: 再写一个函数,找出数组中第二高的数字。
知识点:一次遍历 + 累计变量
课程的数据处理示例(见 README 实战示例)演示了标准套路:初始化"追踪变量"(如 total、highestGrade、lowestGrade),然后用一次循环边读边比较,循环结束后变量即为答案。本练习的三个函数本质上是把这一套路拆解为可复用的函数。
参考实现
const numbers = [12, 45, 7, 89, 23, 56, 4, 78];
// 返回最高值:假设第一个元素最大,然后逐个比较更新
function findMaximum(arr) {
let max = arr[0];
for (let i = 1; i < arr.length; i++) {
if (arr[i] > max) {
max = arr[i];
}
}
return max;
}
// 返回最低值:比较方向反过来即可
function findMinimum(arr) {
let min = arr[0];
for (let i = 1; i < arr.length; i++) {
if (arr[i] < min) {
min = arr[i];
}
}
return min;
}
// 返回总和:累加器 total 从 0 开始逐项累加
function calculateSum(arr) {
let total = 0;
for (let i = 0; i < arr.length; i++) {
total += arr[i];
}
return total;
}
// 测试三个函数
console.log(`Maximum: ${findMaximum(numbers)}`); // 89
console.log(`Minimum: ${findMinimum(numbers)}`); // 4
console.log(`Sum: ${calculateSum(numbers)}`); // 314
加分挑战——第二高值,有两种思路:
// 思路 A:排序后取倒数第二个(注意先拷贝,避免修改原数组)
function findSecondHighest(arr) {
const sorted = [...arr].sort((a, b) => a - b);
return sorted[sorted.length - 2];
}
// 思路 B:两轮遍历——先找最大,再找小于它的最大值
function findSecondHighestLoop(arr) {
let max = findMaximum(arr);
let second = -Infinity; // 哨兵初值,保证任何真实值都能覆盖它
for (const num of arr) {
if (num < max && num > second) {
second = num;
}
}
return second;
}
思路 B 顺带练习了本课第二种遍历语法 for...of——它不需要索引变量,直接拿到每个元素,更不易产生"差一错误(off-by-one)"。
现代写法(值得了解,但请先掌握循环本体)
课程正文末的 Challenge 鼓励探索 map/filter/reduce 等函数式数组方法。本题的三个函数存在等价的现代一行实现,可作为完成作业后的对比:
const max = Math.max(...numbers); // 展开运算符 + Math.max
const min = Math.min(...numbers);
const sum = numbers.reduce((acc, n) => acc + n, 0);
reduce 的第二个参数 0 是累加器初值,与手写循环里的 let total = 0 一一对应——理解手写版之后再读函数式版本会事半功倍。
练习 3:String Array Processing(字符串数组处理)
需求原文(完整继承):
- 创建一个包含你喜欢的电影/书籍/歌曲名称的数组,至少 5 个字符串值;
- 用传统
for循环带编号展示元素(格式1. 元素名); - 用
for...of循环以大写形式展示元素; - 用
forEach()方法统计并展示所有片名的字符总数。
示例输出:
Traditional for loop:
1. The Matrix
2. Inception
3. Interstellar
For...of loop (uppercase):
THE MATRIX
INCEPTION
INTERSTELLAR
Character count:
Total characters across all titles: 42
知识点:三种遍历风格的取舍
这是四个练习中最"方法论"的一题,其考察点与 课程「循环选择指南」 一致:
| 方式 | 语法 | 适合场景 | 特点 |
|---|---|---|---|
传统 for |
for (let i = 0; i < arr.length; i++) |
需要索引做编号、反向遍历、跳步 | 最可控,但要手动管理索引 |
for...of(ES6+) |
for (const item of arr) |
只关心元素本身 | 直接取值、代码更干净、无差一错误 |
forEach() |
arr.forEach(cb) |
函数式风格逐项处理 | 回调里同时给 (value, index),不可提前 break,返回 undefined |
参考实现
const titles = [
"The Matrix",
"Inception",
"Interstellar",
"The Godfather",
"Forrest Gump",
];
// 1) 传统 for 循环:利用索引 i 拼出编号
console.log("Traditional for loop:");
for (let i = 0; i < titles.length; i++) {
console.log(`${i + 1}. ${titles[i]}`); // 模板字符串:编号从 1 开始,索引从 0 开始
}
// 2) for...of 循环:直接取元素并转大写
console.log("For...of loop (uppercase):");
for (const title of titles) {
console.log(title.toUpperCase()); // String.prototype.toUpperCase()
}
// 3) forEach 方法:累加每个字符串的 length 属性
console.log("Character count:");
let totalCharacters = 0;
titles.forEach((title) => {
totalCharacters += title.length; // length 含空格,逐片名计数
});
console.log(`Total characters across all titles: ${totalCharacters}`);
关于示例输出中的 42:它对应作业作者演示数据集的统计结果(示例输出只展示了前三个片名)。真实总数取决于你自己选择的 5 个片名——"Forrest Gump"(含空格 12 个字符)与 "The Godfather"(含空格 13 个字符)都会影响最终数值。你应当以自己数组的实际计算结果为准,而不是照抄 42。
实现细节自查
- 编号输出中
i + 1是为了把从 0 开始的索引显示成人类习惯的"第 1 项",这是零基索引最常见的处理方式; toUpperCase()属于字符串方法,不改变原数组(字符串不可变),所以大写在for...of循环体内部完成即可;- 三个循环操作同一个数组,正好验证:数据不变、遍历方式可变,这是选择不同循环语法的核心价值。
练习 4:Data Filtering(数据过滤,进阶)
需求原文(完整继承):
- 创建一个至少包含 5 个学生对象的数组,每个对象含属性:
name、age、grade; - 用循环找出**年满 18 岁(age >= 18)**的学生;
- 计算所有学生的平均分;
- 创建新数组,只保留成绩高于 85 分的学生。
示例结构:
const students = [
{ name: "Alice", age: 17, grade: 92 },
{ name: "Bob", age: 18, grade: 84 },
// 继续添加更多学生...
];
知识点:从"纯数字/纯字符串"升级到"对象数组"
前三个练习的元素都是原始类型(number、string),本题则是数组元素本身就是对象,访问方式从 arr[i] 变成 arr[i].name。从课程视角看,这是本课数据处理能力在真实场景(购物车条目、用户列表、成绩单)中的直接投射:用 for...of 逐项取出对象,用 .属性名 访问字段。
参考实现
const students = [
{ name: "Alice", age: 17, grade: 92 },
{ name: "Bob", age: 18, grade: 84 },
{ name: "Charlie", age: 20, grade: 78 },
{ name: "Diana", age: 19, grade: 88 },
{ name: "Eve", age: 16, grade: 91 },
];
// 1) 找出 18 岁及以上的学生:条件筛选 + push 到新数组
const adults = [];
for (const student of students) {
if (student.age >= 18) {
adults.push(student);
}
}
console.log("Students 18 or older:", adults.map((s) => s.name));
// 2) 计算全体学生平均分:先累加再除以人数
let gradeTotal = 0;
for (let i = 0; i < students.length; i++) {
gradeTotal += students[i].grade;
}
const averageGrade = gradeTotal / students.length;
console.log(`Average grade: ${averageGrade.toFixed(1)}`);
// 3) 建立只含 85 分以上学生的新数组
const topStudents = [];
for (const student of students) {
if (student.grade > 85) {
topStudents.push(student);
}
}
console.log("Students above 85:", topStudents.map((s) => s.name));
运行结果(基于示例数据):18 岁及以上为 Alice? no(17) → Bob、Charlie、Diana;平均分 (92+84+78+88+91)/5 = 86.6;高于 85 分的是 Alice(92) 与 Eve(91)。
与函数式方法对照
作业明确要求"用循环",但完成后再看一眼现代写法能加深理解——filter 本质上就是把"遍历 + 条件 + 建新数组"三步封装成了一个方法:
const adultsFilter = students.filter((s) => s.age >= 18);
const topFilter = students.filter((s) => s.grade > 85);
注意区分两个需求的关键措辞:找 18 岁学生与筛选 85 分学生都是新建数组(适合 filter),而求平均分是聚合统计(必须遍历累加,无法用单个 filter 完成)——这正是题目刻意安排你分别处理两种模式的原因。
如何测试你的代码
测试指南(继承自作业原文):
- 在浏览器控制台逐个运行每个练习;
- 核对输出是否与预期一致;
- 换用不同的数据集再次测试;
- 检查代码能否正确处理边界情况(空数组、单元素数组)。
浏览器控制台操作
在课程场景中(无需任何构建工具),最直接的运行方式是:打开浏览器(Chrome/Edge/Firefox 均可)→ 按 F12 打开开发者工具 → 切到 Console 面板 → 粘贴整段代码回车执行。函数定义粘贴后不会立即打印,需再调用一次,例如:
findMaximum([3, 9, 1, 7]); // 9
若本机已安装 Node.js,也可以把代码保存为 .js 文件后用 node 文件名.js 运行,效果等价且便于长期保存迭代。
边界情况:空数组与单元素数组
作业明确要求验证两类边界,这里给出针对性检查:
// 空数组:极值函数若直接读 arr[0] 会得到 undefined
console.log(findMaximum([])); // undefined —— 需要防御性处理
console.log(findMinimum([])); // undefined
console.log(calculateSum([])); // 0(累加器天然安全)
// 单元素数组:唯一元素应同时是最大值与最小值
console.log(findMaximum([7])); // 7
console.log(findMinimum([7])); // 7
// 空数组求平均分:0 / 0 = NaN,需要先判空
function averageOf(arr) {
if (arr.length === 0) return 0; // 显式处理空数组
return arr.reduce((a, b) => a + b, 0) / arr.length;
}
这提醒了一个通用原则:函数对输入数据的鲁棒性(robustness)与功能本身同等重要——"用不同数据集测试"和"检查边界情况"正是为了让函数在面对真实多变的数据时不崩溃。
提交指南
作业提交物需要包含(完整继承原文要求):
- 每个练习带清晰注释的 JavaScript 代码——注释应解释"为什么这么做",而不只是复述代码;
- 运行截图或文本输出——证明程序真实运行过且结果正确;
- 对各任务选择循环类型的简短说明——例如"练习 3 的编号需要索引,因此选择传统
for;纯遍历用for...of更简洁"。
评分标准(Rubric)
这是评判作业完成度的官方量表(原文含阿拉伯语与英文两个版本,下表按 3 档翻译整理):
| 标准 | 优秀(3 分) | 合格(2 分) | 待改进(1 分) |
|---|---|---|---|
| 功能性 | 全部练习正确完成,且攻克了加分挑战 | 所有必做练习均可正常运行 | 部分练习未完成或存在错误 |
| 代码质量 | 代码整洁、组织良好,变量名描述性强 | 代码能跑但还可更规整 | 代码混乱或难以理解 |
| 注释 | 注释全面,解释了逻辑与决策依据 | 有基础注释 | 几乎没有注释 |
| 循环使用 | 展示了对不同类型循环的恰当理解与使用 | 循环使用正确但类型偏单一 | 循环使用不正确或低效 |
| 测试 | 有覆盖多种场景的充分测试证据 | 有基础测试证据 | 几乎无测试痕迹 |
对照此表自查:注释是否解释了"为什么用 i += 3 而不是 i++"?是否真的尝试了加分挑战?是否给空数组写了保护分支?每一档的差距都是具体的改进项。
反思问题
作业原文要求完成练习后思考以下 4 个问题,它们也是期末复盘与面试准备的绝佳素材:
- 哪种循环用起来最自然?为什么?
- 处理数组时你遇到了哪些挑战?
- 这些技能如何应用到真实的 Web 开发项目(如渲染列表、过滤用户数据)中?
- 若要针对性能优化代码,你会做哪些改动?
回答第 4 题时值得对照课程里 for...of vs 传统 for vs forEach 的取舍——例如 forEach 无法提前终止,而大数组下传统 for 配合 break 可能更省算力;这些在 课程 README 的现代循环小节 中都有对应讨论。
更进一步的挑战
学有余力时,课程正文还给出了一个 GitHub Copilot Agent Challenge(见 课程 README 挑战节):编写一个 analyzeGrades 函数,接收"含 name 与 score 的学生对象数组",返回含最高分、最低分、平均分、及格人数(score >= 70)以及"高于平均分学生名单"的结果对象,并强制要求至少使用两种不同类型的循环。它几乎是对作业四个练习的综合:练习 2 的极值/求和 + 练习 4 的过滤/统计,最终以函数形式统一交付——完成作业后去实现它,即为本课画上最完整的一句号。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0627
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
