JavaScript条件判断与循环语句代码示例

作者:半夏

时间:2026-11-03

浏览:95

深入解析JavaScript中条件判断(if/else、switch、三元运算)与循环语句(for、while、do...while、for...of、for...in)的执行机制、代码示例、跳转控制与工程避坑指南。

文章目录
假设需要为电商结账页面编写一段订单核算脚本:程序既要根据用户会员等级与优惠券类型做出不同维度的折扣计算,又要遍历购物车数组核对每件商品的库存与单价;若遇到缺货商品需记录预警并跳过计价,遇到非法篡改的优惠券则需立刻终止整个核销流程。这类业务场景的核心支撑就是条件判断与循环语句。本文将通过具体可执行的代码示例,系统剖析 JavaScript 中主流分支判断结构与循环语句的执行机理、语法细节与工程避坑点。
JavaScript条件判断与循环语句代码示例
全景示意图:JavaScript 控制流核心架构与执行机制概览

一、条件判断语句核心语法与代码示例

JavaScript 提供了多种处理逻辑分支的方式。不同语句在匹配规则、求值时机和适用场景上存在清晰的边界。掌握它们有助于写出逻辑严密、易于维护的代码。

1.1 if...else if...else 级联判断

if...else 是最通用的分支结构。它按照代码顺序自上而下对表达式求值,一旦某个分支的条件计算为真值(Truthy),就会立即执行其包裹的代码块,并完全短路跳过后续的所有分支。如果所有分支均为假值(Falsy),则执行 else 兜底代码块。

在编写条件表达式时,必须格外注意避免隐式类型转换带来的意外问题,始终优先使用严格相等运算符 === 而不是松散相等 ==。

// 示例:根据订单金额与会员身份计算阶梯优惠
function calculateOrderDiscount(userType, orderAmount) {
  let discountRate = 0;
  let description = "无优惠";

  if (userType === "SUPER_VIP" && orderAmount >= 500) {
    discountRate = 0.25;
    description = "超级会员满500享75折";
  } else if (userType === "SUPER_VIP" || (userType === "VIP" && orderAmount >= 300)) {
    discountRate = 0.15;
    description = "会员满减享85折";
  } else if (orderAmount >= 200) {
    discountRate = 0.05;
    description = "普通用户满200享95折";
  } else {
    discountRate = 0;
    description = "未达到任何优惠门槛";
  }

  const finalPay = orderAmount * (1 - discountRate);
  return { description, finalPay };
}

// 示例调用与输出
console.log(calculateOrderDiscount("SUPER_VIP", 600));
console.log(calculateOrderDiscount("VIP", 350));
console.log(calculateOrderDiscount("NORMAL", 150));
示例运行输出
{ description: '超级会员满500享75折', finalPay: 450 }
{ description: '会员满减享85折', finalPay: 297.5 }
{ description: '未达到任何优惠门槛', finalPay: 150 }

1.2 switch...case 离散恒等分发

当判定目标是一个具体的离散枚举值、固定状态码或动作类型时,使用 switch 语句比级联 if...else 更具可读性与执行效率。switch 在底层对传入值与各个 case 采用严格恒等比较(===),不会触发任何隐式类型转换。

每个 case 块内部必须显式书写 break 语句以终止匹配流。若遗漏 break,程序将不受阻拦地直接落入下一个 case 执行,这种现象称为“穿透(Fallthrough)”。在某些场景下可以巧妙利用穿透合并同类逻辑,但绝大多数非故意遗漏都会引发逻辑缺陷。

// 示例:支付渠道类型解析与费率核销
function resolvePaymentChannel(channelCode) {
  let serviceFee = 0;
  let channelName = "";

  switch (channelCode) {
    case "ALIPAY":
    case "WECHAT_PAY":
      // 利用合理穿透合并移动端主流快捷支付渠道
      serviceFee = 0.006;
      channelName = "移动端快捷聚合支付";
      break;

    case "UNION_PAY":
      serviceFee = 0.004;
      channelName = "银联在线支付";
      break;

    case "CREDIT_CARD":
      serviceFee = 0.015;
      channelName = "国际双币信用卡结算";
      break;

    default:
      // 未知渠道兜底保障
      serviceFee = 0.02;
      channelName = "第三方离线转账渠道";
      break;
  }

  return { channelName, serviceFee };
}

console.log(resolvePaymentChannel("ALIPAY"));
console.log(resolvePaymentChannel("UNION_PAY"));
console.log(resolvePaymentChannel("UNKNOWN_PAY"));
示例运行输出
{ channelName: '移动端快捷聚合支付', serviceFee: 0.006 }
{ channelName: '银联在线支付', serviceFee: 0.004 }
{ channelName: '第三方离线转账渠道', serviceFee: 0.02 }

1.3 三元条件运算符与空值合并

对于简单的二选一单值求值,三元运算符 condition ? exprIfTrue : exprIfFalse 可以让代码紧凑清晰。此外,现代 JavaScript 还提供了空值合并操作符 ??,它仅在左侧操作数为 null 或 undefined 时才会取右侧默认值,完美解决了逻辑或 || 会将 0、"" 或 false 误当作空值覆盖的缺陷。

// 示例:三元运算与空值合并比较
const userCustomTimeout = 0; // 用户配置等待时间为 0 秒

// 错误做法:|| 会将数字 0 判定为假,导致默认值 30 覆盖了用户的合法配置 0
const timeoutA = userCustomTimeout || 30;

// 正确做法:?? 严格检查 null 和 undefined,保留合法的 0 秒设定
const timeoutB = userCustomTimeout ?? 30;

// 三元运算用于简洁状态文本返回
const statusText = timeoutB === 0 ? "立即执行" : `${timeoutB} 秒后超时`;

console.log(`timeoutA: ${timeoutA}`);
console.log(`timeoutB: ${timeoutB}`);
console.log(`状态说明: ${statusText}`);
示例运行输出
timeoutA: 30
timeoutB: 0
状态说明: 立即执行
JavaScript条件判断与循环语句代码示例
图 1:JavaScript 条件判断分支求值机制与多路分发模型

二、循环语句核心语法与代码示例

循环语句用于高效重复执行某段特定任务。JavaScript 提供了五种语法形态,分别对应不同的迭代控制需求和数据结构。

2.1 标准 for 计数循环

标准 for 循环由初始化语句、条件评估表达式和步进更新语句三部分构成。它是性能最高、对索引控制最细粒度的循环形态,常用于连续数值区间计算、双指针算法或按非标准步长递增/递减的场景。

// 示例:使用双指针与标准 for 循环实现数组原地元素翻转
function reverseArrayInPlace(items) {
  const len = items.length;
  // i 从前向后,j 从后向前,相遇即停止
  for (let i = 0, j = len - 1; i < j; i++, j--) {
    const temp = items[i];
    items[i] = items[j];
    items[j] = temp;
  }
  return items;
}

const list = ["A", "B", "C", "D", "E"];
console.log("翻转前:", list);
reverseArrayInPlace(list);
console.log("翻转后:", list);
示例运行输出
翻转前: [ 'A', 'B', 'C', 'D', 'E' ]
翻转后: [ 'E', 'D', 'C', 'B', 'A' ]

2.2 while 与 do...while 状态驱动循环

当循环执行次数在开始前无法预知,完全依赖运行时动态变化的状态时,while 是最直观的选择。while 在进入循环体前评估条件,若首次条件即为假,则循环体一次都不会执行。

而 do...while 则将条件评估放置在循环体末尾。这意味着无论初始条件是否满足,循环体内的代码至少会无条件执行一次。这在网络接口请求失败重试、交互式输入校验等场景中非常有效。

// 示例:使用 do...while 实现最大重试机制的接口轮询模拟
function pollTaskStatus(maxAttempts) {
  let attempt = 0;
  let isSuccess = false;
  let resultPayload = null;

  do {
    attempt++;
    console.log(`[尝试第 ${attempt} 次] 正在向服务器查询任务结果...`);
    
    // 模拟第三次请求时成功完成
    if (attempt >= 3) {
      isSuccess = true;
      resultPayload = { taskId: "TASK_8829", status: "COMPLETED" };
    }
  } while (!isSuccess && attempt < maxAttempts);

  return { success: isSuccess, totalAttempts: attempt, payload: resultPayload };
}

const pollResult = pollTaskStatus(5);
console.log("轮询最终结果:", pollResult);
示例运行输出
[尝试第 1 次] 正在向服务器查询任务结果...
[尝试第 2 次] 正在向服务器查询任务结果...
[尝试第 3 次] 正在向服务器查询任务结果...
轮询最终结果: { success: true, totalAttempts: 3, payload: { taskId: 'TASK_8829', status: 'COMPLETED' } }

2.3 for...of 迭代器循环

ES6 引入的 for...of 语法是遍历可迭代对象(如 Array、Set、Map、String 等实现了 Symbol.iterator 协议的数据结构)的标准做法。它直接获取集合中的具体元素值,语法自然易读,且完整支持 break 与 continue。

如果需要在 for...of 循环中同时访问元素的下标索引与元素值,可以配合数组的 entries() 方法进行解构赋值:

// 示例:使用 for...of 结合 entries() 批量校验商品重量
const packageItems = [
  { name: "机械键盘", weightKg: 1.2 },
  { name: "显示器支架", weightKg: 3.5 },
  { name: "降噪耳机", weightKg: 0.3 }
];

let totalWeight = 0;
for (const [index, item] of packageItems.entries()) {
  totalWeight += item.weightKg;
  console.log(`第 ${index + 1} 件装箱物品: ${item.name},重量: ${item.weightKg}kg,当前累计: ${totalWeight.toFixed(2)}kg`);
}
示例运行输出
第 1 件装箱物品: 机械键盘,重量: 1.2kg,当前累计: 1.20kg
第 2 件装箱物品: 显示器支架,重量: 3.5kg,当前累计: 4.70kg
第 3 件装箱物品: 降噪耳机,重量: 0.3kg,当前累计: 5.00kg

2.4 for...in 属性名枚举循环

for...in 用于枚举普通对象(Plain Object)上的所有可枚举属性键名(Keys)。与 for...of 获取值不同,for...in 每次迭代输出的是字符串形式的属性名。

注意:切勿将 for...in 用于遍历普通数组! for...in 遍历数组时返回的是字符串索引('0', '1'...),且不能保证严格按照数字递增顺序执行;更严重的是,它会顺着原型链把扩展在 Array.prototype 上的自定义可枚举属性一并枚举出来,极易引发难以排查的 Bug。在遍历普通对象时,务必使用 Object.hasOwn(obj, key) 过滤原型链属性。
// 示例:安全枚举配置对象的自身属性并进行过滤清洗
const baseConfig = { env: "production", region: "ap-east-1" };
const serverConfig = Object.create(baseConfig); // 建立原型链继承
serverConfig.port = 8080;
serverConfig.maxConnections = 5000;
serverConfig.sslEnabled = true;

const cleanedParams = {};

for (const propName in serverConfig) {
  // 严格过滤掉继承自 baseConfig 的原型链属性
  if (Object.hasOwn(serverConfig, propName)) {
    cleanedParams[propName] = serverConfig[propName];
    console.log(`保留自身配置项: [${propName}] = ${serverConfig[propName]}`);
  } else {
    console.log(`已跳过原型链继承项: [${propName}]`);
  }
}

console.log("最终清洗后的配置数据:", cleanedParams);
示例运行输出
保留自身配置项: [port] = 8080
保留自身配置项: [maxConnections] = 5000
保留自身配置项: [sslEnabled] = true
已跳过原型链继承项: [env]
已跳过原型链继承项: [region]
最终清洗后的配置数据: { port: 8080, maxConnections: 5000, sslEnabled: true }
JavaScript条件判断与循环语句代码示例
图 2:JavaScript 循环语句选型对比与底层迭代机制

三、循环跳转控制与执行步骤解构

在循环体运行过程中,经常需要根据特定条件动态改变执行流程。JavaScript 提供了 break、continue 以及带标签语句(Labeled Statements)来实现精确的跳转控制。

3.1 break 与 continue 的差异

  • continue 指令:立即放弃当前迭代循环体中尚未执行的代码,直接跳转到当前迭代的末尾,并触发步进表达式(如 i++),随后进入下一轮迭代的条件评估。
  • break 指令:立即彻底终止并跳出当前所在的整个循环结构,将程序控制权移交给循环块之后的第一行代码。
// 示例:过滤白名单用户(continue)与达到配额上限即刻终止(break)
const registrations = [
  { id: 101, username: "Alice", isBlocked: false },
  { id: 102, username: "SpamBot", isBlocked: true },
  { id: 103, username: "Bob", isBlocked: false },
  { id: 104, username: "Charlie", isBlocked: false },
  { id: 105, username: "David", isBlocked: false }
];

const admittedUsers = [];
const quotaLimit = 2; // 仅录取前 2 名合规用户

for (const user of registrations) {
  // 命中黑名单用户:跳过当前这一项,继续处理下一项
  if (user.isBlocked) {
    console.log(`拦截违规账号: ${user.username},跳过处理`);
    continue;
  }

  admittedUsers.push(user.username);
  console.log(`成功录取用户: ${user.username},当前已录取数: ${admittedUsers.length}`);

  // 名额已满:立刻终止遍历,后续用户不再耗费算力
  if (admittedUsers.length >= quotaLimit) {
    console.log("录取名额已达上限,停止后续名单扫描");
    break;
  }
}

console.log("最终录取名单:", admittedUsers);
示例运行输出
成功录取用户: Alice,当前已录取数: 1
拦截违规账号: SpamBot,跳过处理
成功录取用户: Bob,当前已录取数: 2
录取名额已达上限,停止后续名单扫描
最终录取名单: [ 'Alice', 'Bob' ]

3.2 带标签的语句(Labeled Statements)在多层嵌套中的跳出

在双重或多重嵌套循环中,单独书写 break 只能跳出当前最内层的一级循环。若在内层检测到某种触发全盘结束的条件,可以使用标签(Label)标识外层循环,从而在内层一步到位退出所有嵌套。

// 示例:在二维矩阵中定位目标值,找到后直接退出所有双重循环
const dataMatrix = [
  [12, 45, 67],
  [89, 99, 102],
  [33, 77, 88]
];

const targetValue = 99;
let foundCoords = null;

// 外层循环打上 searchGrid 标签
searchGrid: for (let rowIndex = 0; rowIndex < dataMatrix.length; rowIndex++) {
  for (let colIndex = 0; colIndex < dataMatrix[rowIndex].length; colIndex++) {
    const currentValue = dataMatrix[rowIndex][colIndex];
    if (currentValue === targetValue) {
      foundCoords = { row: rowIndex, col: colIndex };
      console.log(`已在第 ${rowIndex} 行第 ${colIndex} 列锁定目标 ${targetValue},直接退出外层总循环`);
      // 精确跳出指定外层循环
      break searchGrid;
    }
    console.log(`扫描中: 行 ${rowIndex}, 列 ${colIndex}, 数值 ${currentValue}`);
  }
}

console.log("搜索坐标结果:", foundCoords);
示例运行输出
扫描中: 行 0, 列 0, 数值 12
扫描中: 行 0, 列 1, 数值 45
扫描中: 行 0, 列 2, 数值 67
扫描中: 行 1, 列 0, 数值 89
已在第 1 行第 1 列锁定目标 99,直接退出外层总循环
搜索坐标结果: { row: 1, col: 1 }
JavaScript条件判断与循环语句代码示例
图 3:for 循环标准生命周期与控制跳转步进解构流程

四、综合实战案例:电商购物车结算与合规校验引擎

为了让条件判断与循环语句的配合在实际工程中落地,下面实现一个具备完整数据输入、异常短路、商品明细核算及状态分发的订单结算引擎:

// 综合实战:电商结算引擎
function processCartCheckout(cart, userProfile) {
  // 1. 防御性前置判断
  if (!Array.isArray(cart) || cart.length === 0) {
    return { status: "FAILED", message: "购物车为空,无法发起结算", totalAmount: 0 };
  }

  let subtotal = 0;
  let finalPayable = 0;
  let skippedItems = [];
  const processedItems = [];

  // 2. 遍历购物车进行逐项核算与过滤
  for (const item of cart) {
    // 基础有效性核查
    if (!item.id || item.price <= 0) {
      skippedItems.push({ id: item.id ?? "UNKNOWN", reason: "商品单价异常" });
      continue;
    }

    // 库存检测:缺货则跳过当前项
    if (item.stock < item.quantity) {
      skippedItems.push({ id: item.id, reason: `库存不足 (仅存${item.stock})` });
      continue;
    }

    // 严重违规商品检测:一旦发现风控违禁品,直接整体中断
    if (item.isRestricted) {
      console.warn(`[安全风控] 发现管制商品 ${item.name},即刻终止整笔交易`);
      return { status: "BLOCKED", message: `订单包含限制流通商品: ${item.name}`, totalAmount: 0 };
    }

    const itemSubtotal = item.price * item.quantity;
    subtotal += itemSubtotal;

    processedItems.push({
      id: item.id,
      name: item.name,
      quantity: item.quantity,
      subtotal: itemSubtotal
    });
  }

  // 3. 利用 switch 分发不同会员等级的计算规则
  let discountRate = 0;
  switch (userProfile.level) {
    case "DIAMOND":
      discountRate = 0.20; // 钻石会员 8 折
      break;
    case "GOLD":
      discountRate = 0.10; // 黄金会员 9 折
      break;
    case "SILVER":
      discountRate = 0.05; // 白银会员 95 折
      break;
    default:
      discountRate = 0;    // 普通用户无专属折扣
      break;
  }

  // 4. 满额减免与运费核算(三元与复合条件判断)
  const discountAmount = subtotal * discountRate;
  const shippingFee = (subtotal - discountAmount >= 99) ? 0 : 15; // 满 99 免运费
  finalPayable = subtotal - discountAmount + shippingFee;

  return {
    status: "SUCCESS",
    processedCount: processedItems.length,
    subtotal,
    discountAmount,
    shippingFee,
    finalPayable,
    skippedItems
  };
}

// 模拟测试数据
const mockCart = [
  { id: "P101", name: "无线降噪耳机", price: 299, quantity: 1, stock: 10, isRestricted: false },
  { id: "P102", name: "备用耳塞套", price: 0, quantity: 2, stock: 50, isRestricted: false }, // 无效单价
  { id: "P103", name: "人体工学电脑椅", price: 899, quantity: 1, stock: 0, isRestricted: false }, // 缺货
  { id: "P104", name: "便携收纳包", price: 49, quantity: 1, stock: 20, isRestricted: false }
];

const mockUser = { id: "U7701", level: "GOLD" };

const checkoutResult = processCartCheckout(mockCart, mockUser);
console.log("订单结算执行报告:", JSON.stringify(checkoutResult, null, 2));
示例运行输出
{
  "status": "SUCCESS",
  "processedCount": 2,
  "subtotal": 348,
  "discountAmount": 34.8,
  "shippingFee": 0,
  "finalPayable": 313.2,
  "skippedItems": [
    { "id": "P102", "reason": "商品单价异常" },
    { "id": "P103", "reason": "库存不足 (仅存0)" }
  ]
}

五、常见语法误区与工程避坑指南

即使掌握了基本语法,在实际编码中依然有一些由 JavaScript 语言底层特性引发的典型陷阱需要警惕:

5.1 误区一:循环内使用 var 带来的闭包与变量提升陷阱

在旧版 JavaScript 中使用 var i = 0 声明循环变量时,由于 var 具备函数作用域或全局作用域特性,所有迭代周期共享同一个变量 i。如果在循环体内绑定异步定时器或事件监听器,最终回调读取的将全都是循环结束后的最大值。现代代码必须始终使用 let 声明循环计数器,使每次迭代都绑定独立的块级作用域。

5.2 误区二:在 while 循环中使用 continue 导致死循环

在标准 for 循环中,continue 会跳转到 step 表达式(如 i++),计数器依然会自增。但在 while 循环中,计数器的自增语句通常写在循环体末尾;如果在自增语句之前执行了 continue,程序将直接跳回条件评估,计数器停滞不前,从而彻底陷入死循环并耗尽浏览器或 Node.js 进程的 CPU 资源。

// 错误示范:在 while 中随意使用 continue 导致死循环
let count = 0;
while (count < 5) {
  if (count === 2) {
    // 错误!此时 count 依然是 2,直接跳到开头后下一次依然是 2,造成永久死循环
    // continue; 
  }
  count++;
}

// 正确示范:在 continue 之前务必手动确保步进更新
let safeCount = 0;
while (safeCount < 5) {
  if (safeCount === 2) {
    safeCount++; // 先前进一步,再执行 continue
    continue;
  }
  console.log(`安全输出: ${safeCount}`);
  safeCount++;
}

5.3 误区三:在迭代过程中直接通过 splice 增删原数组

通过索引正序遍历数组时,如果直接在循环体内调用 arr.splice(i, 1) 删除元素,后续元素的索引会自动向前移位补齐,但循环计数器 i 依然按部就班自增,这会导致被删除元素的紧随其后的下一个元素被跳过检查。如果必须在遍历中原地删除,应采用倒序遍历(for (let i = arr.length - 1; i >= 0; i--))或采用 Array.prototype.filter() 生成新数组。

JavaScript条件判断与循环语句代码示例
图 4:JavaScript 条件与循环语句工程选型决策路径

六、核心语法与选型速查表

为了便于在编码与代码审查(Code Review)中快速对照,下表梳理了各类控制流语法的推荐应用场景与禁忌点:

控制语句 典型应用场景 取值 / 评估机制 核心禁忌与注意点
if ... else 范围区间、多变量复合判断 按 Truthy/Falsy 顺序单向短路 避免松散比较 ==,防止深层嵌套
switch ... case 离散状态码、事件类型派发 严格恒等匹配 (===) 每个 case 末尾务必检查 break
三元运算 ?: 基于条件的单变量/单表达式赋值 双向布尔求值 严禁多层嵌套,嵌套会导致可读性剧降
for (let i; ...) 数组顺序/倒序遍历、多维矩阵、双指针 前置条件 + 显式步进自增 禁止使用 var 声明计数器
while 未知循环次数、外部状态驱动轮询 每次迭代前置条件检查 使用 continue 前确保步进变量已更新
do ... while 接口网络重试、命令行交互输入 先执行一次,后置条件检查 末尾 while(condition); 需保留分号
for ... of 数组、Set、Map、字符串的值遍历 消费 Symbol.iterator 协议 普通 Object 不可直接使用,需要先转换
for ... in 纯字典对象(Plain Object)属性枚举 枚举键名(字符串),含原型链属性 严禁用于 Array;必须配合 Object.hasOwn

通过遵循上述规范与选型逻辑,不仅能够让代码在逻辑意图上保持清晰自洽,更能从根源上杜绝由于隐式类型转换、变量提升污染和无限循环导致的线上故障。

实用工具推荐

电子包浆

绿化、压缩、噪点和虚拟水印,自由调节做旧程度。

使用 ›
图片压缩

选择画质和输出格式,预览压缩前后的大小。

使用 ›
相关文章