JavaScript条件判断与循环语句代码示例
深入解析JavaScript中条件判断(if/else、switch、三元运算)与循环语句(for、while、do...while、for...of、for...in)的执行机制、代码示例、跳转控制与工程避坑指南。
一、条件判断语句核心语法与代码示例
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));
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"));
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}`);
二、循环语句核心语法与代码示例
循环语句用于高效重复执行某段特定任务。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);
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);
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`);
}
2.4 for...in 属性名枚举循环
for...in 用于枚举普通对象(Plain Object)上的所有可枚举属性键名(Keys)。与 for...of 获取值不同,for...in 每次迭代输出的是字符串形式的属性名。
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);
三、循环跳转控制与执行步骤解构
在循环体运行过程中,经常需要根据特定条件动态改变执行流程。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);
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);
四、综合实战案例:电商购物车结算与合规校验引擎
为了让条件判断与循环语句的配合在实际工程中落地,下面实现一个具备完整数据输入、异常短路、商品明细核算及状态分发的订单结算引擎:
// 综合实战:电商结算引擎
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() 生成新数组。
六、核心语法与选型速查表
为了便于在编码与代码审查(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 |
通过遵循上述规范与选型逻辑,不仅能够让代码在逻辑意图上保持清晰自洽,更能从根源上杜绝由于隐式类型转换、变量提升污染和无限循环导致的线上故障。
JavaScript闭包与类(Class)的区别及适用场景对比
2026-11-05 15:48:57
JavaScript入门要学ES6吗?let与箭头函数等新特性解析
2026-11-05 14:49:16
JavaScript 8种数据类型详解:基本类型与引用类型的区别
2026-11-05 14:25:15
怎么在JavaScript中使用async/await优化Ajax异步流程
2026-11-05 13:56:15
JavaScript replace回调函数实现动态正则替换逻辑
2026-11-05 13:52:13








