A04 条件分支与循环

2026-10-31 17:260 阅读约 10 分钟JavaScript
AI 摘要30 秒读完

讲解JS条件分支与循环的常见缺陷及替代写法,用映射表替代长链if,用集合方法替代循环

  • 规则型分支应改为映射表+默认值,新增状态只改数据不改结构
  • 非法输入用卫语句前置返回,避免深层嵌套缩进
  • 数组遍历用for...of,副作用操作不用for...in
  • map/filter/find/reduce按需求选择,reduce必须给初始值
  • 循环中修改集合会导致遍历不确定,应先filter再赋值
AI 生成 · 仅供参考

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. 执行脚本,确认输出 [1,3] 600 0 与 retry 2。
  2. 给 STATUS_TEXT 增加 canceled: '已取消',确认无需改动函数体即可生效。
  3. 在 for...of 循环中 push 新元素,观察是否被遍历,理解遍历快照与实时访问的差别。
  4. 把 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

参考文献

[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

0/2000

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