A04 条件分支与循环
讲解JS条件分支与循环的常见缺陷及替代写法,用映射表替代长链if,用集合方法替代循环
- 规则型分支应改为映射表+默认值,新增状态只改数据不改结构
- 非法输入用卫语句前置返回,避免深层嵌套缩进
- 数组遍历用for...of,副作用操作不用for...in
- map/filter/find/reduce按需求选择,reduce必须给初始值
- 循环中修改集合会导致遍历不确定,应先filter再赋值
A04 条件分支与循环
摘要:条件分支用于在少数互斥路径中选择一条,循环用于对集合重复执行同一动作。嵌套过深与循环中修改集合是两类高频缺陷:前者让路径无法穷举,后者让遍历结果不确定。本文给出提前返回、卫语句、
for...of与集合遍历方法的替代写法,并以状态映射表替换长链if。
关键词:条件分支,提前返回,循环,for…of,集合方法
所属:frontend/js / A-入门 | 上一篇:A03-运算符与类型转换 | 下一篇:A05-函数与参数
1 引言
业务代码中的分支往往来自规则表而非真正嵌套的流程:会员等级、订单状态、权限位。写成三层 if 后,新增一个状态就要改动原有结构,且难以覆盖测试。
本文解决三个问题:分支怎么扁平化、循环怎么选、什么时候用集合方法替代循环。范围见表 1-1。
| 项 | 本文覆盖 | 本文不覆盖 |
|---|---|---|
| 分支 | if/else、switch、提前返回 |
异常控制流(B06) |
| 循环 | for、for...of、while |
异步循环(C04) |
| 集合 | map、filter、find、reduce |
迭代器协议(C01) |
2 预备知识
| 项 | 要求 | 核对方式 |
|---|---|---|
| 环境 | Node.js 20+ 或浏览器控制台 | 能执行多行脚本 |
| 前置 | A03 | 理解真假值与严格比较 |
| 文件 | js-practice/control-flow.js |
编码 UTF-8 |
3 原理与实现
3.1 分支扁平化
规则表型分支应转成查表,而不是继续叠加条件,如图 3-1 所示。
flowchart TD
A["输入 order.status"] --> B["状态映射表"]
B --> C["pending → 待付款"]
B --> D["paid → 待发货"]
B --> E["done → 已完成"]
B --> F["未命中 → 未知状态"]
C --> G["输出文案"]
D --> G
E --> G
F --> G
图 3-1 用映射表替代长链 if,新增状态只改数据
图上可见:分支数量不再影响代码行数,未命中路径统一兜底;条件判断从代码搬到了数据。
3.2 完整实现
// js-practice/control-flow.js;运行环境:Node.js 20+
const STATUS_TEXT = {
pending: '待付款',
paid: '待发货',
done: '已完成',
};
function statusText(status) {
return STATUS_TEXT[status] ?? '未知状态';
}
// 1 卫语句:先处理非法输入,主流程保持平铺
function discountLevel({ total, isVip }) {
if (!Number.isFinite(total) || total < 0) return 'invalid';
if (total >= 1000) return 'high';
if (isVip) return 'vip';
return 'normal';
}
// 2 for...of 遍历值,需要索引时用 entries
const items = ['a', 'b', 'c'];
for (const [index, value] of items.entries()) {
if (value === 'b') continue;
console.log(index, value);
}
// 3 集合方法替代循环
const orders = [
{ id: 1, total: 120, paid: true },
{ id: 2, total: 0, paid: false },
{ id: 3, total: 480, paid: true },
];
const paidIds = orders.filter((o) => o.paid).map((o) => o.id);
const sum = orders.reduce((acc, o) => acc + o.total, 0);
const found = orders.find((o) => o.id === 2);
console.log(paidIds, sum, found?.total); // [1,3] 600 0
console.log(statusText('paid'), discountLevel({ total: 1000 }));
// 4 while 只用于次数未知且必须显式退出的场景
let retry = 0;
while (retry < 3) {
retry += 1;
if (retry === 2) break;
}
console.log('retry', retry); // retry 2
循环与集合方法的取舍见表 3-1。
| 需求 | 推荐写法 | 返回值 | 说明 |
|---|---|---|---|
| 逐个执行副作用 | for...of |
无 | 可 break、continue |
| 生成新数组 | map |
新数组 | 长度与原数组一致 |
| 筛选子集 | filter |
新数组 | 不改动原数组 |
| 找单个元素 | find / findIndex |
元素或索引 | 找不到返回 undefined/-1 |
| 累积成单值 | reduce |
任意类型 | 必须给初始值 |
| 判断存在 | some / every |
布尔值 | 短路,命中即返回 |
4 验证与讨论
- 执行脚本,确认输出
[1,3] 600 0与retry 2。 - 给
STATUS_TEXT增加canceled: '已取消',确认无需改动函数体即可生效。 - 在
for...of循环中push新元素,观察是否被遍历,理解遍历快照与实时访问的差别。 - 把
reduce的初始值删除,用空数组执行,确认抛出Reduce of empty array with no initial value。
| 检查项 | 预期结果 | 说明 |
|---|---|---|
| 映射表兜底 | 未知状态返回「未知状态」 | ?? 处理未命中 |
| 卫语句顺序 | 非法输入先返回 | 主流程不缩进 |
| 集合方法纯度 | 原 orders 未被修改 |
map/filter 返回新数组 |
| 短路判断 | some 命中后不再遍历 |
大数组上更省时 |
讨论与局限。 reduce 表达力强但可读性差,超过三步累积时拆成多个 map/filter 更易读。另,集合方法每次返回新数组,百万级数据在循环里连续链式调用会产生大量中间数组,需改用单次循环。
5 常见问题
| 现象 | 原因 | 处理 |
|---|---|---|
| 循环中删除元素漏项 | 边遍历边改长度 | 倒序遍历,或先 filter 再赋值 |
reduce 结果为 NaN |
累加项含字符串或 undefined |
入口用 Number 转换并给初始值 |
| 分支覆盖不全 | 用 if 链且无兜底 |
改为映射表 + 默认值 |
for...in 遍历数组拿到字符串索引 |
用错循环语法 | 数组用 for...of |
| 无限循环 | while 缺少退出条件 |
引入计数上限并 break |
6 结论
| 序号 | 结论 | 依据 |
|---|---|---|
| 1 | 规则型分支改为映射表 | 图 3-1 |
| 2 | 非法输入用卫语句前置返回 | 3.2 节 |
| 3 | 遍历值用 for...of,数组不用 for...in |
表 3-1 |
| 4 | reduce 必须给初始值 |
4 节步骤 4 |
- 上一篇:A03-运算符与类型转换
- 下一篇:A05-函数与参数
- 返回:A00-总结 | 科目 README
参考文献
[1] MDN Web Docs · 语句和声明,https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Statements(访问于 2026-10-31)
[2] MDN Web Docs · Array.prototype.reduce,https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Array/reduce(访问于 2026-10-31)

评论 0
还没有评论,快来抢沙发吧~