跟着 MDN 学JavaScript day_15:循环吧,代码——让重复变得优雅

跟着 MDN 学JavaScript day_15:循环吧,代码——让重复变得优雅 引言如果说条件语句赋予了程序判断的能力那么循环语句则赋予了程序重复执行的能力。在编程中我们经常需要处理大量相似的任务遍历数组中的每一项、重复执行某个操作直到满足特定条件、或者生成一系列有序的数据。手动为每一次重复编写代码不仅是低效的体力活更会让代码变得臃肿且难以维护。循环结构的出现让我们能够用寥寥数行代码优雅地驾驭成千上万次的重复操作。从绘制一百个随机圆到遍历整个联系人列表循环都是我们最忠实的伙伴。本文将带你深入理解 JavaScript 中循环的核心概念与各种实现方式。一、来一起循环吧循环的概念根植于我们日常生活的重复性任务中。想象一位农夫为家庭制定一周的食物计划他从零开始每小时收集两份食物不断重复这个收集过程直到食物总量达到家庭所需的标准。这个过程天然包含了循环的三个基本要素要素含义农夫示例开始条件初始化器循环的起点最初没有任何食物0份结束条件退出条件循环停止的标准食物数量达到10份迭代器最终表达式每次循环使计数器向结束条件靠近每小时增加2份食物这个朴素的模型完美映射了编程中循环的工作方式初始化一个起点 → 反复执行一段代码 → 在每次执行后更新状态 → 直到满足退出条件。在伪代码中农夫的逻辑可以这样表达循环开始食物数量 0需要总量 10 检查食物是否已足够 是 → 退出循环回家 否 → 花一小时收集2份食物食物 2继续循环为什么循环如此重要让我们用一个具体的例子来说明。假设我们需要在一个画布元素上绘制100个随机圆如果不使用循环我们不得不将绘制圆的四行代码复制粘贴100次——这是极其枯燥的体力劳动更可怕的是如果将来需求变成绘制1000个圆代码量就会爆炸式增长而通过一个简单的for循环核心的绘制逻辑被包裹在一次迭代中循环条件只需指定从0到99共100次执行即可。无论绘制100个、1000个还是10000个圆核心代码始终只有寥寥几行唯一需要改变的仅仅是循环次数那个数字。循环让我们实现了代码数量与任务规模之间的解耦这正是它强大魅力的根本所在。二、循环的标准for 循环在 JavaScript 中for循环是我们使用频率最高的循环结构它的语法将循环的三个核心要素整齐地封装在一对圆括号内结构紧凑而优雅。2.1 for 循环的语法结构for(初始化器;退出条件;最终表达式){// 每次迭代要执行的代码}三个要素以分号分隔要素执行时机典型写法初始化器循环开始前仅执行一次let i 0退出条件每次迭代开始前求值i array.length最终表达式每次循环体执行完毕后运行i这种将初始化、条件检查和状态更新集中放置的设计使得循环的行为一目了然非常易于阅读和调试。2.2 遍历数组的经典示例我们有一个存储猫咪名字的数组想要将它们拼接成一个完整的句子varcats[Bill,Jeff,Pete,Biggles,Jasmin];varinfoMy cats are called ;varparadocument.querySelector(p);for(vari0;icats.length;i){infocats[i], ;}para.textContentinfo;循环执行流程迭代i 的值cats[i]info 拼接结果第1次0Bill...Bill, 第2次1Jeff...Jeff, 第3次2Pete...Pete, 第4次3Biggles...Biggles, 第5次4Jasmin...Jasmin, 终止5—退出条件5 5为false停止2.3 循环边界的常见陷阱 ⚠️这里有一个关键的细节值得注意退出条件使用严格小于而不是小于等于。// ✅ 正确for(vari0;icats.length;i){...}// ❌ 错误当 i 达到 5 时循环仍会执行但 cats[5] 是 undefinedfor(vari0;icats.length;i){...}// ❌ 错误首次迭代 i 0条件立即为假循环一次都不会执行for(vari0;icats.length;i){...}这是因为数组的索引从0开始计数最后一个元素的索引是长度 - 1。所以当计数器等于长度时它已经超出了数组的有效范围。这个细节提醒我们循环边界条件的设定必须与数据结构的具体特性紧密配合。三、处理循环中的特殊情况3.1 对最后一次迭代做特殊处理在上面的例子中输出结果是My cats are called Bill, Jeff, Pete, Biggles, Jasmin,末尾多出了一个多余的逗号看起来不够完美。理想情况下我们希望在最后一个名字前加上and并以句号结尾。for(vari0;icats.length;i){if(icats.length-1){infoand cats[i].;}else{infocats[i], ;}}// 输出My cats are called Bill, Jeff, Pete, Biggles, and Jasmin.通过检查i cats.length - 1来判断当前是否处于最后一次迭代是最后一次→ 使用带有and和句号的拼接方式不是最后一次→ 仍然使用常规的逗号加空格分隔。这个模式非常实用它展示了条件语句与循环语句如何协同工作以应对序列处理中边界情况的特殊需求。在编写任何需要格式化列表输出的代码时这个技巧都会频繁派上用场。四、使用 break 退出循环break语句提供了一个在满足特定条件时提前终止循环的机制。它的作用可以用一个生活化的场景来理解假设你在一个通讯录中查找某个特定联系人的电话号码一旦找到了目标就没有必要继续翻看后面的条目了。break正是为了这种找到了就停下来的逻辑而设计的。当程序执行到break语句时它会立即跳出整个循环不再执行剩余的迭代直接开始执行循环体之后的后续代码。4.1 联系人搜索示例首先准备 HTML 结构一个文本输入框供用户输入搜索姓名、一个按钮触发搜索、以及一个段落显示结果。constcontacts[Chris:2232322,Sarah:3453456,Bill:7654322,Mary:9998769,Dianne:9384975,];constparadocument.querySelector(p);constinputdocument.querySelector(input);constbtndocument.querySelector(button);btn.addEventListener(click,function(){letsearchNameinput.value.toLowerCase();input.value;input.focus();for(leti0;icontacts.length;i){letsplitContactcontacts[i].split(:);if(splitContact[0].toLowerCase()searchName){para.textContentsplitContact[0]s number is splitContact[1].;break;}elseif(icontacts.length-1){para.textContentContact not found.;}}});执行流程分析程序先将当前联系人的字符串按冒号:分割提取出姓名部分与用户输入进行大小写不敏感的比对都转为小写匹配成功→ 显示该联系人的电话号码执行break立即终止循环遍历到底仍未匹配i contacts.length - 1 → 显示未找到联系人的提示。这个双层判断结构——匹配成功用break提前退出遍历到底仍未匹配则给出反馈——构成了一种非常常见且实用的搜索模式。五、使用 continue 跳过迭代与break彻底终止循环不同continue语句的作用是跳过当前迭代的剩余代码直接进入下一次循环。它适用于这样的场景我们只对循环中的一部分元素感兴趣对于不满足特定条件的元素我们希望快速跳过不执行后续处理逻辑但循环本身仍然要继续运行下去。5.1 筛选平方根为整数的数字一个绝佳的示例是筛选出指定范围内所有平方根为整数的数字即完全平方数varnuminput.value;for(vari1;inum;i){varsqRootMath.sqrt(i);if(Math.floor(sqRoot)!sqRoot){continue;}para.textContenti ;}执行流程计算当前数字i的平方根检查平方根向下取整后是否与原值相等不相等平方根不是整数 → 执行continue跳过本次迭代中剩余的代码即不追加到输出直接进入i开始下一次循环相等平方根是整数 →if块不执行正常将数字追加到输出结果中。这种方式使得循环的逻辑非常清晰不符合条件的项被干净利落地过滤掉只有满足条件的数据才能走到最终的输出环节。5.2 break vs continue 对比语句作用典型场景break彻底终止整个循环搜索到目标后停止continue跳过当前迭代继续下一次循环过滤掉不满足条件的项六、while 语句和 do…while 语句除了for循环JavaScript 还提供了while和do...while这两种循环结构。它们在本质上与for循环等价都能实现相同的迭代逻辑但在语法组织上有所不同适用于不同风格的场景。6.1 while 循环while循环将初始化器提到循环之前单独声明将退出条件放在while关键字后面的圆括号里而最终表达式则移到循环体内部的末尾。vari0;while(icats.length){if(icats.length-1){infoand cats[i].;}else{infocats[i], ;}i;}关键特性退出条件在循环体执行之前就被检查。如果初始条件一开始就是false循环体内的代码将一次都不会执行。这种先判断再执行的特性使得while循环特别适合处理不确定需要迭代多少次的场景可能零次迭代的场景如等待用户输入有效值、处理可能为空的数据集。6.2 do…while 循环do...while循环将do关键字放在循环体之前退出条件的检查被挪到了循环体的末尾。vari0;do{if(icats.length-1){infoand cats[i].;}else{infocats[i], ;}i;}while(icats.length);关键特性循环体总是至少会被执行一次即使退出条件一开始就是false。因为检查发生在代码执行之后而不是之前。这非常适合那些先做一次再问要不要继续的模式比如先显示一个菜单然后根据用户的选择决定是否再次显示。6.3 三种循环对比特性forwhiledo...while初始化器位置括号内第一项循环之前循环之前退出条件位置括号内第二项括号内循环体末尾最终表达式位置括号内第三项循环体末尾循环体末尾最少执行次数0次0次1次适用场景已知循环次数不确定次数/可能0次至少执行一次⚠️铁律无论选择哪种循环类型你必须确保初始变量能够在每次迭代中逐步趋近退出条件否则就会陷入无限循环的深渊最终导致浏览器强制终止脚本甚至崩溃。七、主动学习启动倒计时第一个主动学习任务是编写一个从10倒数到0的倒计时程序。与之前的例子不同这次计数器是向下走而不是向上走因此最终表达式不再是i而应该是i--。在每次迭代中我们需要动态创建一个新的段落元素并将其追加到输出区域中。段落中显示的文本内容取决于当前的计数值letoutputdocument.querySelector(.output);output.innerHTML;for(leti10;i0;i--){constparadocument.createElement(p);if(i10){para.textContentCountdown 10;}elseif(i0){para.textContentBlast off!;}else{para.textContenti;}output.appendChild(para);}三种情况的处理i 的值显示内容条件10Countdown 10i 109 ~ 1数字本身else兜底0Blast off!i 0这个练习完美地融合了循环递减、DOM 动态操作以及条件分支判断等多个知识点是对前面所学内容的综合运用。八、主动学习填写来宾列表第二个主动学习任务模拟了一个真实的分组筛选场景。我们有一个包含多个人名的数组需要根据特定规则将不同的人分配到允许进入和拒绝进入两个列表中。黑名单规则菲尔Phil和洛拉Lola被列入了黑名单因为他们总是吃光所有食物。constpeople[Chris,Anne,Colin,Terri,Phil,Lola,Sam,Kay,Bruce];constadmitteddocument.querySelector(.admitted);constrefuseddocument.querySelector(.refused);admitted.textContentAdmit: ;refused.textContentRefuse: ;for(leti0;ipeople.length;i){if(people[i]Phil||people[i]Lola){refused.textContentpeople[i], ;}else{admitted.textContentpeople[i], ;}}额外优化挑战两个列表的末尾都会多出一个多余的逗号。解决方案与之前的猫咪例子相同——在最后一次迭代时改用句号来结尾for(leti0;ipeople.length;i){if(people[i]Phil||people[i]Lola){if(ipeople.length-1){refused.textContentpeople[i].;}else{refused.textContentpeople[i], ;}}else{if(ipeople.length-1){admitted.textContentpeople[i].;}else{admitted.textContentpeople[i], ;}}}这个练习将循环遍历、条件判断、逻辑运算符||以及字符串拼接等知识点有机地结合在一起还引导我们思考边界情况的优雅处理方式。九、应该使用哪种循环类型面对for、while和do...while这三种循环初学者常常会困惑于如何做出选择。事实上对于绝大多数基础任务来说它们在功能上完全可以互换。选择指南场景推荐循环理由遍历已知长度的数组for三个要素紧凑在一起一眼把握全貌退出条件依赖外部状态while将条件判断单独突出更自然循环体至少执行一次do...while唯一正确的选择初学阶段的一般任务for最不容易遗漏三要素出错概率最低我们建议在初学阶段以for循环为主要工具因为它将初始化器、退出条件和最终表达式整齐地放入括号方便检查是否遗漏要素。随着经验的积累你会逐渐培养出对每种循环类型适用场景的直觉在合适的地方自如地选用最恰当的结构。总结循环语句是编程中不可或缺的基础构建块它将我们从重复劳动的泥潭中解放出来让我们能够以简洁的代码处理大规模的数据和操作。知识点核心内容循环三要素初始化器、退出条件、最终表达式for循环最常用的循环结构三要素集中放置边界特殊处理使用if (i array.length - 1)处理最后一次迭代break提前终止整个循环搜索匹配后停止continue跳过当前迭代继续下一次过滤不符合条件的项while先判断后执行适合不确定次数/可能0次的场景do...while先执行后判断保证至少执行一次循环边界陷阱数组索引用 length而非 length本文我们从循环的核心概念出发深入掌握了for循环的标准语法和遍历数组的经典模式学习了如何使用break提前终止循环以及使用continue跳过特定的迭代。我们还对比了while和do...while两种循环的特性与适用场景理清了它们与for循环之间的异同。通过倒计时和来宾列表两个主动学习任务我们也将循环与实际编程场景紧密结合体会到了循环在解决真实问题时的强大力量。理解这些不同的循环结构并根据具体需求做出恰当选择是每个 JavaScript 开发者都必须掌握的基本功。当你能够自如地让代码在数组中穿梭、在条件满足时果断跳出、在不需要时轻巧跳过你就真正体会到了用代码驾驭重复之美的乐趣。