首页
/ Web-Dev-For-Beginners 实战作业拆解:用 4 个编程练习吃透 JavaScript 数组与循环

Web-Dev-For-Beginners 实战作业拆解:用 4 个编程练习吃透 JavaScript 数组与循环

2026-09-07 13:59:08作者:冯梦姬Eddie

本文围绕开源课程 Web-Dev-For-Beginners 中 2-js-basics 第 4 课「数组与循环」的课后作业 展开(阿拉伯语译文见 translations/ar/2-js-basics/4-arrays-loops/assignment.md)。该作业要求你独立完成 4 个由浅入深的编程练习,覆盖 for 循环自定义步进、数组极值与求和函数、字符串数组的多循环遍历,以及对象数组的条件过滤与平均分统计。读完本文,你将能逐题理解考察要点、拿到可复跑的参考实现、掌握浏览器控制台的测试方法,并按评分标准自我评估。

这篇作业是 JavaScript 基础四课(数据类型函数与方法条件判断数组与循环)的收官实践,也是通往后续购物车、好友列表、数据统计等真实场景的必经之路。课程用一句话点明了它的价值:数组是把多份信息装进一个容器,循环则让你无需重复编写代码即可高效处理容器里的全部数据。

JavaScript 数组与循环手绘视觉笔记:冰淇淋比喻数组索引、for/while 循环语法与遍历逻辑

作业全景:练什么、怎么练、如何判分

四个练习的目标设计

练习 标题 核心考点
1 Number Pattern Generator(数字模式生成器) for 循环三段式与自定义迭代步进
2 Array Analysis(数组分析) 函数封装 + 遍历求最大值、最小值、总和
3 String Array Processing(字符串数组处理) 传统 forfor...offorEach 三种遍历方式对比
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 → … → 打印 18i=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 实战示例)演示了标准套路:初始化"追踪变量"(如 totalhighestGradelowestGrade),然后用一次循环边读边比较,循环结束后变量即为答案。本练习的三个函数本质上是把这一套路拆解为可复用的函数。

参考实现

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 个学生对象的数组,每个对象含属性:nameagegrade
  • 用循环找出**年满 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 完成)——这正是题目刻意安排你分别处理两种模式的原因。


如何测试你的代码

测试指南(继承自作业原文):

  1. 在浏览器控制台逐个运行每个练习;
  2. 核对输出是否与预期一致;
  3. 换用不同的数据集再次测试;
  4. 检查代码能否正确处理边界情况(空数组、单元素数组)。

浏览器控制台操作

在课程场景中(无需任何构建工具),最直接的运行方式是:打开浏览器(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 个问题,它们也是期末复盘与面试准备的绝佳素材:

  1. 哪种循环用起来最自然?为什么?
  2. 处理数组时你遇到了哪些挑战?
  3. 这些技能如何应用到真实的 Web 开发项目(如渲染列表、过滤用户数据)中?
  4. 若要针对性能优化代码,你会做哪些改动?

回答第 4 题时值得对照课程里 for...of vs 传统 for vs forEach 的取舍——例如 forEach 无法提前终止,而大数组下传统 for 配合 break 可能更省算力;这些在 课程 README 的现代循环小节 中都有对应讨论。

更进一步的挑战

学有余力时,课程正文还给出了一个 GitHub Copilot Agent Challenge(见 课程 README 挑战节):编写一个 analyzeGrades 函数,接收"含 namescore 的学生对象数组",返回含最高分、最低分、平均分、及格人数(score >= 70)以及"高于平均分学生名单"的结果对象,并强制要求至少使用两种不同类型的循环。它几乎是对作业四个练习的综合:练习 2 的极值/求和 + 练习 4 的过滤/统计,最终以函数形式统一交付——完成作业后去实现它,即为本课画上最完整的一句号。

登录后查看全文
热门项目推荐
相关项目推荐