Making Decisions 实战:在 Web-Dev-For-Beginners 中构建学生成绩处理器(Student Grade Processor)完整指南
【免费下载链接】Web-Dev-For-Beginners24 Lessons, 12 Weeks, Get Started as a Web Developer项目地址: https://gitcode.com/GitHub_Trending/we/Web-Dev-For-Beginners
本文围绕 Web-Dev-For-Beginners 课程"JavaScript Basics"模块第 3 课《Making Decisions》的课后作业 assignment.md 展开:你将基于if...else分支、typeof类型判断、比较运算符与逻辑运算符,完成一个处理两套不同评分体系的学生成绩筛选程序。读完后你将掌握如何在动态类型的 JavaScript 中编写可靠的分支逻辑,并学会用多组测试数据验证、以及用校验、统计、分数换算三个方向对程序做实战扩展。
任务背景:两套评分体系的合并处理问题
作业文档给出了一个贴近真实业务的问题设定:你所在学校最近与另一所学校合并,现在需要处理来自两套完全不同的评分体系的学生成绩,并判断哪些学生通过(passing)。这正是练习条件逻辑(conditional logic)的典型场景。
作业的学习目标是(引自 assignment.md):
In this assignment, you'll practice the decision-making concepts from this lesson by building a program that processes student grades from different grading systems. You'll use
if...elsestatements, comparison operators, and logical operators to determine which students pass their courses.
两套评分体系的规则拆解
在写任何代码之前,先把两套体系的判定规则拆清楚。这是整个程序判定逻辑的唯一事实来源:
| 维度 | 第一套体系(Numeric 数字制) | 第二套体系(Letter 字母制) |
|---|---|---|
| 成绩形态 | 数字 1–5 | 字符串:A、A-、B、B-、C、C- |
| 及格线 | 3 及以上(3、4、5) | 列出的全部字母成绩均及格 |
| 不及格情形 | 小于 3(1 或 2) | 该体系不包含D、F等不及格字母 |
两条规则对分支逻辑的直接影响:
- 数字制只需要一个"范围比较":
grade >= 3; - 字母制没有大小顺序可比较,只能做"白名单成员判断":检查该字符串是否属于六个有效字母成绩之一,这正是作业指定用逻辑运算符
||串联多个等值判断的原因。
任务定义:allStudents 样本数据与期望输出
作业给定如下混合数组allStudents,要求构造新数组studentsWhoPass,收集其中所有及格的成绩:
let allStudents = [ 'A', // Letter grade - passing 'B-', // Letter grade - passing 1, // Numeric grade - failing 4, // Numeric grade - passing 5, // Numeric grade - passing 2 // Numeric grade - failing ]; let studentsWhoPass = [];逐个元素的判定过程如下:
| 元素 | typeof结果 | 适用规则 | 判定 |
|---|---|---|---|
'A' | 'string' | 字母制白名单命中 | 及格 |
'B-' | 'string' | 字母制白名单命中 | 及格 |
1 | 'number' | 1 >= 3为 false | 不及格 |
4 | 'number' | 4 >= 3为 true | 及格 |
5 | 'number' | 5 >= 3为 true | 及格 |
2 | 'number' | 2 >= 3为 false | 不及格 |
期望输出为['A', 'B-', 4, 5]。注意结果的顺序与输入顺序一致——因为studentsWhoPass是按遍历顺序逐个push进去的,这也为后续测试验证提供了确定性基准。
参考实现:完整可运行的解决方案
作业给出的 Step-by-Step 方法是四步:1)设一个循环遍历每个成绩;2)检查成绩类型(数字还是字符串?);3)按对应体系的规则判定(数字看>= 3,字符串看是否属于有效及格字母);4)把及格成绩 push 进结果数组。下面是一份严格按这四步实现的参考代码:
let allStudents = [ 'A', // Letter grade - passing 'B-', // Letter grade - passing 1, // Numeric grade - failing 4, // Numeric grade - passing 5, // Numeric grade - passing 2 // Numeric grade - failing ]; let studentsWhoPass = []; // 步骤 1:循环遍历 allStudents 中的每个成绩 for (const grade of allStudents) { // 步骤 2:先判断类型——是数字成绩还是字母成绩? if (typeof grade === 'number') { // 步骤 3a:数字体系——>= 3 及格 if (grade >= 3) { // 步骤 4:把及格成绩加入结果数组 studentsWhoPass.push(grade); } // 数字且 < 3:不及格,什么都不做 } else if (typeof grade === 'string') { // 步骤 3b:字母体系——逐个核对六个有效及格字母 if (grade === 'A' || grade === 'A-' || grade === 'B' || grade === 'B-' || grade === 'C' || grade === 'C-') { studentsWhoPass.push(grade); } // 字符串但不在白名单内(如 'D'):视为无效/不及格 } // 其他类型(undefined、null、boolean 等):默认不通过 } console.log(studentsWhoPass); // ['A', 'B-', 4, 5]实现中有两个值得注意的设计决策:
类型分派(type dispatch)放在最外层。因为 JavaScript 是动态类型语言,同一个数组里可以合法地混放字符串和数字(这正是本任务的核心难点),所以"先问类型、再选规则"必须发生在任何规则判断之前。反过来,如果你先写grade >= 3,字符串'A'参与比较时行为将完全不可预期。
字母判断用===而非==。配套课程 README 明确指出:严格相等===会同时检查值和类型(5 === '5'返回false),而宽松相等==会先做隐式类型转换,"Always use===and!==for equality checks unless you specifically need type conversion"。在本任务中字符串与字符串比较,用===是最稳妥的写法。
作业点名的五项关键技术工具
assignment 的 "Helpful Code Techniques" 一节明确列出了本课要练习的五个技术点。结合上面参考实现,它们各自承担的职责如下:
| 技术点 | 作业原话要点 | 在本任务中的职责 |
|---|---|---|
typeof运算符 | typeof grade === 'number'判断是否数字成绩 | 最外层分派:决定走数字规则分支还是字母规则分支 |
| 比较运算符 | 用>=比较数字成绩 | 数字体系的及格线判定grade >= 3 |
| 逻辑运算符 | 用||检查多个字母成绩条件 | 串联六个字母的白名单成员判断 |
if...else语句 | 处理不同的评分体系 | 两套规则互斥执行,保证每个成绩只走一条路径 |
数组方法.push() | 把及格成绩加入新数组 | 结果收集器 |
其中||(Logical OR)值得再展开一句:作业要求用它检查多个字母条件,从源码行为上看它做短路求值——六个等值判断中只要有一个为true,后面的表达式就不再求值,直接得到true。配套课程在讲&&时特别提到了这一特性("Performance tip: JavaScript uses 'short-circuit evaluation'"),||同理。把最可能命中的条件放在链条前端,在性能敏感场景下是惯例写法;本任务六个分支概率接近,按字母顺序排列即可。
测试你的解决方案
assignment 的 "Testing Your Solution" 一节要求用不同组合的成绩验证代码,原文给出的两组测试数据是:
// Test with different grade combinations let testGrades1 = ['A-', 3, 'C', 1, 'B']; let testGrades2 = [5, 'A', 2, 'C-', 4]; // Your solution should work with any combination of valid grades按作业定义的两套规则推导,这两组数据的期望输出分别是:
| 测试输入 | 逐元素判定 | 期望的studentsWhoPass |
|---|---|---|
['A-', 3, 'C', 1, 'B'] | 'A-'通过;3 >= 3通过;'C'通过;1 < 3失败;'B'通过 | ['A-', 3, 'C', 'B'] |
[5, 'A', 2, 'C-', 4] | 5通过;'A'通过;2 < 3失败;'C-'通过;4通过 | [5, 'A', 'C-', 4] |
验证方法很简单:把循环逻辑封装成可复用的形式(或直接在每次运行前重置studentsWhoPass = []),用console.log/console.table输出结果与期望值比对。除了这两组,提交前还应考虑 assignment "Submission Guidelines" 提示的边界场景,例如空数组(应得到[]而不是报错)和意外的数据类型(参考实现中else if (typeof grade === 'string')之后的隐式兜底分支就是为这类输入准备的)。
附加挑战:校验、统计与成绩换算
assignment 在基础任务之外给出了三个扩展方向(Bonus Challenges),逐个说明实现思路:
1)加入校验(Add validation)。检测非法成绩,例如负数字或不在白名单里的字母。核心做法是在两套规则内部再各加一层合法性判断:数字制要求grade >= 1 && grade <= 5,字母制则检查grade是否属于六个有效字母,两者都不满足时console.log出告警而不是静默丢弃。
2)统计通过/失败人数(Count statistics)。借助条件表达式把判定结果收敛为布尔值,再累加计数。这里可以顺带练习课程中的三元运算符:
let passCount = 0; let failCount = 0; for (const grade of allStudents) { // 三元运算符:数字制看 >= 3,字母制看白名单 const isPassing = (typeof grade === 'number') ? grade >= 3 : (grade === 'A' || grade === 'A-' || grade === 'B' || grade === 'B-' || grade === 'C' || grade === 'C-'); if (isPassing) { passCount++; } else { failCount++; } } console.log(`Passing: ${passCount}, Failing: ${failCount}`); // 对 allStudents:Passing: 4, Failing: 23)成绩换算(Grade conversion)。把所有成绩统一到数字制(A=5、B=4、C=3等)。用switch实现还能顺带练习课程中讲的多分支结构,并且演示了break的用途——多个case连续叠放、共用一个出口(fall-through),正是"忘记 break 会发生什么"这个问题的反面教材:
function convertToNumber(grade) { if (typeof grade === 'number') { return grade; // 已经是数字制,原样返回 } switch (grade) { case 'A': case 'A-': return 5; case 'B': case 'B-': return 4; case 'C': case 'C-': return 3; default: return null; // 无法识别的字母成绩 } } // convertToNumber('B-') -> 4 // convertToNumber(4) -> 4 // convertToNumber('D') -> null注意switch中每个分支用return而非break提前退出,避免了 fall-through 带来的连锁执行——课程 README 强调switch适用于"一个变量对比多个离散值"的场景,而范围判断(如>= 3)仍应使用if...else,本任务的字母白名单恰好落在前者。
评分标准(Rubric)与提交清单
assignment 附带的评分表从四个维度、每维 1–4 分评估作业,完整继承如下:
| 维度 | 优秀 (4) | 熟练 (3) | 发展中 (2) | 起步 (1) |
|---|---|---|---|---|
| 功能性 | 程序正确识别两套体系中的全部及格成绩 | 程序基本可用,存在小问题或边界瑕疵 | 程序部分可用但有逻辑错误 | 程序存在严重错误或无法运行 |
| 代码结构 | 代码整洁、组织良好,if...else逻辑得当 | 结构良好,条件语句使用恰当 | 结构尚可但有一定组织问题 | 结构差,逻辑难以跟随 |
| 概念运用 | 有效运用比较运算符、逻辑运算符与条件语句 | 良好运用课程概念,存在小缺口 | 运用了部分课程概念但缺少关键要素 | 课程概念运用有限 |
| 问题解决 | 对问题理解清晰,解法思路优雅 | 解题思路良好,逻辑扎实 | 解决问题能力尚可,略有混乱 | 思路不清,未体现出理解 |
提交前的四项检查清单(引自 assignment "Submission Guidelines"):
- 用题目给出的示例充分测试代码;
- 为逻辑(尤其是条件语句)添加注释;
- 核对输出与期望结果
['A', 'B-', 4, 5]一致; - 考虑边界情况,如空数组、意外数据类型。
作业最后还附了一条 Pro Tip:Start simple! 先让基础功能跑起来,再叠加更复杂的功能——目标是运用本课学到的工具练习决策逻辑,而不是追求炫技。
现实印证:同一决策模式在仓库真实项目中的应用
本作业的分支模式并非孤立练习。仓库中的 memory-game/components/MemoryGame.jsx(Next.js 记忆翻牌游戏组件)里可以看到与作业完全同构的决策代码:
- 范围 + 整除的复合校验:网格尺寸输入处理中用
if (2 <= size && size <= 10 && (size % 2 === 0))同时约束范围与奇偶性,不满足时进入else分支给出错误提示(见 MemoryGame.jsx#L26-L34)——这正是作业 Bonus 1"加校验"思路的实战版; - 多分支状态机:
handleClick依据disabled、won守卫条件和flipped.length的取值(0 或 1)走不同分支,逻辑骨架与作业的"类型分派 → 规则判定"一致(见 MemoryGame.jsx#L74-L92); - 短路求值:胜利判定
if (selectedPairs.length === array.length && array.length > 0)中,&&左侧为false(空数组)时右侧根本不会被求值,避免了空数组场景下的无谓比较(见 MemoryGame.jsx#L97-L101); - 三元表达式:渲染层用
{isFlipped(card.id) ? card.number : "?"}与{won ? "You Won!" : ""}做行内二选一(见 MemoryGame.jsx#L142),对应课程中"简单条件赋值可用三元替代 if...else"的写法。
从源码结构看,作业里"先判类型/状态、再按规则分支"的写法与真实项目中的输入校验、状态守卫、UI 条件渲染一脉相承——完成这份作业等于提前接触了后续项目(如本仓库的 memory-game 与 quiz-app)中会反复出现的决策骨架。
相关学习资源
- 本课完整讲义(布尔、比较运算符表、
if/if...else/switch、逻辑运算符、三元表达式与短路求值):2-js-basics/3-making-decisions/README.md - 本作业原文:2-js-basics/3-making-decisions/assignment.md
- 下一课《Arrays and Loops》(作业中
.push()与循环遍历的延伸):2-js-basics/4-arrays-loops/README.md - JavaScript 基础模块总览(共 4 课):2-js-basics/README.md
- 真实决策逻辑参考实现:memory-game/components/MemoryGame.jsx
【免费下载链接】Web-Dev-For-Beginners24 Lessons, 12 Weeks, Get Started as a Web Developer项目地址: https://gitcode.com/GitHub_Trending/we/Web-Dev-For-Beginners
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考