用 For 循环反向计数(Count Backwards With a For Loop)——freeCodeCamp 基础 JavaScript 阶梯课程详解
导读
for 循环并不是只能从小到大正序递增,只要恰当设置初始化(initialization)、循环条件(condition)与最终表达式(final-expression)三要素,它同样可以从 10 一路倒数到 1。本文以 freeCodeCamp 开源课程仓库 basic-javascript 区块中的挑战“Count Backwards With a For Loop”(curriculum/challenges/english/blocks/basic-javascript/56105e7b514f539506016a5e.md)为主线,讲解递减 for 循环的原理、三要素如何协同改写、数组 push 的正确用法,并对照源码逐条拆解该挑战的自动化测试规则与参考解答,帮你彻底吃透“带步长的倒序遍历”。
挑战背景:正序步进只是 for 循环的默认姿势
在进入本挑战之前,课程已经通过两条前序挑战建立起了循环的基本心智模型:
- “Iterate with JavaScript For Loops”(
cf1111c1c11feddfaeb5bdef):介绍 for 循环的标准三段式结构; - “Iterate Odd Numbers With a For Loop”(curriculum/challenges/english/blocks/basic-javascript/56104e9e514f539506016a5c.md):示范如何通过修改
final-expression,让计数器按 2 递增,从而产出奇数。
本挑战正是“按 2 递增”思路的镜像版本:既然能按 2 递增,自然也能按 2 递减。从课程结构文件 basic-javascript.json 可以看到,本挑战紧跟在 “Iterate Odd Numbers With a For Loop” 之后、位于 “Iterate Through an Array with a For Loop” 之前,属于同一组“循环迭代”序列,其 challengeType: 1 表示这是一道经典的纯代码编写型挑战。
递减循环的三要素改写:一句三改
原挑战说明开宗明义:“A for loop can also count backwards, so long as we can define the right conditions.”(只要条件定义正确,for 循环也能倒数。)
for 循环的结构本质上由三个表达式决定:初始化、条件、最终表达式。要实现每轮递减 2,三个部分必须同时配合修改,缺一不可:
| 表达式段 | 正序示例(按 2 递增) | 倒序示例(按 2 递减) | 含义 |
|---|---|---|---|
initialization |
let i = 0 |
let i = 10 |
从终点值起步 |
condition |
i < 10 |
i > 0 |
只要大于 0 就继续 |
final-expression |
i += 2 |
i -= 2 |
每轮结束把 i 减 2 |
课程给出的标准演示代码如下:
const ourArray = [];
for (let i = 10; i > 0; i -= 2) {
ourArray.push(i);
}
逐轮推演这段代码的执行过程:
- 初始化:
i = 10; - 检查条件
10 > 0成立,进入循环体,ourArray.push(10),随后执行i -= 2,i变为 8; - 检查
8 > 0成立,push 8,i变为 6; - 依此类推,依次 push 6、4、2;
- 当
i = 0时,0 > 0为假,循环终止。
最终 ourArray 的值为 [10, 8, 6, 4, 2]。注意数字 0 并没有被写入数组——这正是条件选用 > 0 而非 >= 0 的直接后果,理解这一点是把握“边界条件决定输出范围”的关键。
任务指令解析:构造降序奇数数组
原挑战的 --instructions-- 段落给出的任务非常简短而明确:
Push the odd numbers from 9 through 1 to
myArrayusing aforloop.
把这句话拆成三条可验证的需求:
- 数字范围:9 到 1 之间的奇数,即
9, 7, 5, 3, 1; - 目标载体:写入已声明好的数组
myArray; - 手段约束:必须使用
for循环(不得用while、Array.from、filter等手段)。
值得注意的是,初始化时仓库已为你声明好容器:
// Setup
const myArray = [];
// Only change code below this line
myArray 使用 const 声明。这没问题——const 只禁止对变量本身重新赋值,并不禁止调用数组方法修改其内部内容,因此 myArray.push(...) 完全合法。这与前面课程“用 const 声明只读变量”以及“push 向数组末尾添加元素”两节的知识点形成呼应。
对照前驱挑战:只改两处即可完成任务
前驱挑战“Iterate Odd Numbers With a For Loop”已示范如何构建 [1, 3, 5, 7, 9]:
const myArray = [];
for (let i = 1; i < 10; i += 2) {
myArray.push(i);
}
本挑战要求的 [9, 7, 5, 3, 1] 与其互为镜像。要做的事情只有两件:
- 把初始化值从
i = 1改为i = 9(从最大奇数起步); - 把最终表达式从
i += 2改为i -= 2(改为递减)。
而条件表达式可以保持不变,仍然使用 i > 0:
for (let i = 9; i > 0; i -= 2) {
为什么 i > 0 依然正确?因为目标序列的最小值是 1,当 i = 1 时满足条件并 push 1,随后 i 变为 -1,-1 > 0 为假,循环恰好终止——不会多写入任何多余元素。当然,写成 i >= 1 同样能通过,但 i > 0 是更贴合“奇数不小于 1”语义的常见写法。
自动化测试如何验证你的代码
freeCodeCamp 的每道挑战都带有 --hints-- 段落,这些断言会被课程测试运行器逐条执行。查看原文档(56105e7b514f539506016a5e.md),本挑战包含三条测试:
第 1 条:必须使用 for 循环
assert(/for\s*\([^)]+?\)/.test(__helpers.removeJSComments(code)));
该正则会在“移除所有 JS 注释之后”的提交代码里搜索 for ( 形式的语句,验证你是否真的用上了 for 循环。调用 removeJSComments 的目的,是防止有人把 for 字样藏在注释中投机取巧。
第 2 条:必须使用数组方法 push
assert(__helpers.removeJSComments(code).match(/myArray.push/));
检测提交代码中是否包含 myArray.push 调用,确保元素是通过 push 追加进数组的。
第 3 条:结果值必须精确匹配
assert.deepEqual(myArray, [9, 7, 5, 3, 1]);
deepEqual 会做深度相等比较:不仅数组长度要一致,每个下标位置的元素也必须逐一相等、顺序分毫不差。因此任何“顺序颠倒”“漏项”“多一项”的输出都会在这一条被拦截。
参考解答与自检清单
原文档的 --solutions-- 段给出标准解答:
const myArray = [];
for (let i = 9; i > 0; i -= 2) {
myArray.push(i);
}
写完代码后,建议用下述清单自检:
- [ ] 循环体使用的变量确实是
i(与初始化声明一致,避免把i误写成iI等笔误); - [ ] 初始化值是 9,最终表达式是
i -= 2; - [ ] push 的目标是
myArray,且 push 语句位于循环体花括号{ }内部; - [ ] 循环条件保证在 push 完 1 后不会再继续(不会出现负数入数组);
- [ ] 未在
// Only change code below this line上方改动初始化代码。
也可以自行在浏览器控制台或 Node REPL 中逐步验证:在循环体内临时加上 console.log(i),观察控制台应依次打印 9 7 5 3 1,随后打印结束。
易错点与延伸思考
递减循环常见三类错误
- 条件方向写反:
for (let i = 9; i < 0; i -= 2)由于初始化时9 < 0即为假,循环体一次都不会执行,myArray保持为空。 - 最终表达式写成递增:
i += 2与i = 9组合会得到9 < 0恒为假之外的另一个极端——i越来越大、永不停机(具体取决于条件写法),轻则死循环,重则让测试环境超时。 - 错用
const声明计数器:for (const i = 9; ...)会在第二轮尝试给i重新赋值时报错,因为循环变量每轮都需更新,必须使用let。
递减循环的真实用途
倒序遍历在工程中非常常见,例如:从数组末尾往前查找满足条件的元素、在正向遍历删除元素会引发索引错位时改用反向遍历、实现倒计时逻辑等。掌握“三要素联动修改”的技巧后,你可以在任何维度控制遍历方向与步长——甚至用 i -= 1 做步长为 1 的倒数、用 i /= 2 做指数衰减。
本挑战随后将进入“Iterate Through an Array with a For Loop”(basic-javascript.json),届时你会把这些循环技巧应用到真实数组的逐个元素访问上,本挑战打下的“参数联动”基本功将直接复用。
关联文件速查
- 挑战正文(含说明、测试断言、种子代码与标准解):curriculum/challenges/english/blocks/basic-javascript/56105e7b514f539506016a5e.md
- 前驱挑战“Iterate Odd Numbers With a For Loop”:curriculum/challenges/english/blocks/basic-javascript/56104e9e514f539506016a5c.md
- 区块内全部挑战的顺序与 id 登记表:curriculum/structure/blocks/basic-javascript.json
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 StartedRust0631
MiniCPM5-2BMiniCPM5-2B 是一款面向端侧、本地部署和资源受限场景的 2B 稠密 Transformer,能够达到同尺寸开源模型 SOTA 水平。Markdown00
video-shotcraftAI宣传片skill,使用 Remotion 制作电影级产品视频:提供106 张镜头配方卡和可复用的视频魔板。适用于 Claude Code 与 Codex以及所有其他智能体Markdown00
HivisionIDPhotos⚡️HivisionIDPhotos: a lightweight and efficient AI ID photos tools. 一个轻量级的AI证件照制作算法。Python09
DragonOSDragonOS is an operating system developed from scratch using Rust, with Linux compatibility. It is designed for **Serverless** scenarios. 使用Rust从0自研内核,具有Linux兼容性的操作系统,面向云计算Serverless场景而设计。Rust00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00