流程控制
程序默认从上到下逐行执行,流程控制语句允许我们根据条件跳转分支或重复执行某段代码。JavaScript 的流程控制分为两大类:条件分支(if/else、switch、三元表达式)与循环(for、while、do-while 及遍历系列)。本文将系统讲解这些语句的语法、特性和使用场景。
一、条件判断中的真值与假值
任何值都可以放在条件位置参与判断,JavaScript 会先将其隐式转换为布尔值。以下是全部 6 个假值:
false, 0, -0, "" (空字符串), null, undefined, NaN其余所有值——包括 "0"、"false"、空数组 []、空对象 {}——都是真值:
if ("0") console.log("执行"); // 执行,非空字符串为真
if ([]) console.log("执行"); // 执行,空数组为真
if ({}) console.log("执行"); // 执行,空对象为真
if (0) console.log("不执行"); // 不输出
if (null) console.log("不执行"); // 不输出| 表达式 | 布尔值 | 说明 |
|---|---|---|
!!0 | false | 数字 0 为假 |
!!NaN | false | NaN 为假 |
!!"" | false | 空字符串为假 |
!!"0" | true | 非空字符串为真 |
!![] | true | 空数组为真 |
!!{} | true | 空对象为真 |
二、if/else 语句
2.1 基本语法
if (条件) {
// 条件为真时执行
} else {
// 条件为假时执行
}const score = 85;
if (score >= 90) {
console.log("优秀");
} else if (score >= 60) {
console.log("及格");
} else {
console.log("不及格");
}
// 输出:及格2.2 省略大括号的陷阱
单语句分支可以省略大括号,但极易埋下隐患:
let status = "success";
if (status === "success")
console.log("请求成功");
console.log("这行总会执行"); // 不在 if 分支内!
// 强烈建议始终使用大括号
if (status === "success") {
console.log("请求成功");
}2.3 判断对象是否存在
结合真值判断与可选链,可以安全访问嵌套数据:
const user = { name: "张三", address: { city: "北京" } };
if (user && user.address && user.address.city) {
console.log(user.address.city); // 北京
}
// ES2020 之后可简写
if (user?.address?.city) {
console.log(user.address.city);
}三、switch 语句
3.1 基本语法
switch 适合对同一个表达式的多个取值做分支判断:
const day = 3;
let weekName;
switch (day) {
case 1:
weekName = "星期一";
break;
case 2:
weekName = "星期二";
break;
case 3:
weekName = "星期三";
break;
default:
weekName = "其他";
}
console.log(weekName); // 星期三3.2 匹配规则:严格相等
case 使用 === 与表达式比较,不存在类型转换:
const value = "1";
switch (value) {
case 1: // 数字 1,与字符串 "1" 严格不相等
console.log("数字");
break;
case "1": // 字符串 "1" 才匹配
console.log("字符串");
break;
}
// 输出:字符串3.3 fallthrough 穿透
忘记写 break 时,匹配到的分支执行完后会继续向下穿透执行后续分支:
const score = "B";
switch (score) {
case "A":
console.log("90-100 分");
case "B":
console.log("80-89 分");
case "C":
console.log("70-79 分");
default:
console.log("其他");
}
// 输出:
// 80-89 分
// 70-79 分
// 其他fallthrough 也能被利用——多个分支共享同一段逻辑:
const month = 2;
let days;
switch (month) {
case 1:
case 3:
case 5:
case 7:
case 8:
case 10:
case 12:
days = 31;
break;
case 4:
case 6:
case 9:
case 11:
days = 30;
break;
case 2:
days = 28;
break;
}
console.log(days); // 283.4 default 分支
default 在所有 case 都不匹配时执行,位置可以放在任意处,习惯放在最后:
const action = "delete";
switch (action) {
case "add":
console.log("新增");
break;
default:
console.log("未知操作:" + action);
}
// 输出:未知操作:delete3.5 switch 与 if/else 的选择
| 维度 | switch | if/else |
|---|---|---|
| 适用场景 | 同一表达式多取值 | 复杂条件、范围判断 |
| 匹配方式 | === 严格相等 | 任意表达式求值 |
| 可读性 | 分支多时更清晰 | 条件复杂时更灵活 |
| 常见陷阱 | 忘记 break 造成穿透 | 条件顺序错误被提前截断 |
四、三元表达式替代 if/else
简单的二选一赋值场景可用三元表达式,代码更紧凑:
const age = 20;
const type = age >= 18 ? "成年" : "未成年";
console.log(type); // 成年
// 等价写法
let type2;
if (age >= 18) {
type2 = "成年";
} else {
type2 = "未成年";
}三元表达式是表达式而非语句,可以嵌套在模板字符串中:
const isLogin = true;
console.log(`欢迎,${isLogin ? "亲爱的用户" : "游客"}`);
// 欢迎,亲爱的用户五、for 循环
5.1 经典 for 循环
for (初始化; 条件; 更新) {
// 循环体
}for (let i = 0; i < 5; i++) {
console.log(i); // 0 1 2 3 4
}
// 倒序遍历数组
const arr = [10, 20, 30];
for (let i = arr.length - 1; i >= 0; i--) {
console.log(arr[i]); // 30 20 10
}5.2 for-in 循环
for-in 遍历对象可枚举属性(包括继承属性),顺序不保证:
const obj = { name: "张三", age: 30, city: "北京" };
for (const key in obj) {
console.log(key, obj[key]);
}
// name 张三
// age 30
// city 北京
// 遍历数组时 key 是字符串下标,不推荐
const arr = ["a", "b", "c"];
for (const index in arr) {
console.log(index); // "0" "1" "2"
}5.3 for-of 循环(ES2015)
for-of 遍历可迭代对象的值,支持数组、字符串、Map、Set 等:
const arr = ["苹果", "香蕉", "橙子"];
for (const item of arr) {
console.log(item);
}
// 遍历字符串
for (const ch of "你好 JS") {
console.log(ch); // 按码点逐字符输出
}
// 配合 entries 获取下标与值
for (const [index, value] of arr.entries()) {
console.log(index, value);
}
// 0 苹果
// 1 香蕉
// 2 橙子5.4 forEach 方法
forEach 是数组实例方法,回调函数接收值、下标与数组本身:
["a", "b"].forEach(function (value, index, array) {
console.log(index, value);
});
// 0 a
// 1 b注意 forEach 无法用 break/continue 中断,若需要中途退出应改用 for-of。
5.5 四种遍历方式对比
| 方式 | 适用对象 | 可 break | 获取下标 | 推荐度 |
|---|---|---|---|---|
经典 for | 数组 | 是 | 是 | 需要下标或倒序时 |
for-in | 对象属性 | 是 | 键名 | 遍历对象时 |
for-of | 可迭代对象 | 是 | 借助 entries() | 遍历数组/字符串最常用 |
forEach | 数组 | 否 | 回调参数 | 简单全量遍历 |
六、while 与 do-while 循环
6.1 while 循环
先判断条件再执行,条件为假则一次都不执行:
let count = 0;
while (count < 3) {
console.log(count);
count++;
}
// 0 1 2典型应用:读取数据直到满足条件。
let dice = 0;
while (dice !== 6) {
dice = Math.floor(Math.random() * 6) + 1;
console.log("掷出:" + dice);
}
// 直到掷出 6 为止6.2 do-while 循环
先执行一次循环体再判断条件,至少执行一次:
let num = 10;
do {
console.log(num);
num++;
} while (num < 5);
// 输出:10(即使条件一开始为假,也先执行一次)| 维度 | while | do-while |
|---|---|---|
| 执行时机 | 先判断后执行 | 先执行后判断 |
| 最小执行次数 | 0 次 | 1 次 |
| 适用场景 | 条件决定是否进入 | 必须至少执行一次的交互确认 |
七、break、continue 与标签语句
7.1 break
break 立即退出当前循环,程序继续执行循环后的语句:
// 查找第一个满足条件的元素
const scores = [62, 45, 88, 90];
let found = -1;
for (let i = 0; i < scores.length; i++) {
if (scores[i] >= 60) {
found = scores[i];
break; // 找到即退出
}
}
console.log(found); // 627.2 continue
continue 跳过本次循环的剩余语句,进入下一次迭代:
// 只输出偶数
for (let i = 1; i <= 6; i++) {
if (i % 2 !== 0) continue;
console.log(i); // 2 4 6
}7.3 标签语句
标签(Label)为循环起名,配合 break / continue 可以跳出或跳过多层循环:
outer: for (let i = 0; i < 3; i++) {
for (let j = 0; j < 3; j++) {
if (i === 1 && j === 1) {
break outer; // 直接退出外层循环
}
console.log(i, j);
}
}
// 0 0
// 0 1
// 0 2
// 1 0// continue 跳转到外层循环的下一次迭代
outer: for (let i = 0; i < 3; i++) {
for (let j = 0; j < 3; j++) {
if (j === 1) {
continue outer;
}
console.log(i, j);
}
}
// 0 0
// 1 0
// 2 0八、循环控制应用场景
8.1 分页渲染
const items = [];
for (let i = 1; i <= 100; i++) items.push("项目" + i);
function paginate(data, page, pageSize) {
const start = (page - 1) * pageSize;
return data.slice(start, start + pageSize);
}
console.log(paginate(items, 2, 10)); // 第 2 页,10 条8.2 嵌套循环与提前终止
// 判断两个数组是否有公共元素
const listA = [3, 7, 9];
const listB = [1, 5, 7, 11];
let hasCommon = false;
outer: for (const a of listA) {
for (const b of listB) {
if (a === b) {
hasCommon = true;
break outer;
}
}
}
console.log(hasCommon); // true,7 是公共元素8.3 循环中的安全防护
循环条件若永远为真且没有 break,会形成死循环导致页面卡死,务必确保更新语句能推进条件收敛:
// 正确:条件会逐步收敛
let n = 10;
while (n > 0) {
n--;
}
// 危险:n 不变,条件恒真
// let m = 10;
// while (m > 0) {
// console.log("死循环");
// }九、综合实战:成绩评级工具
综合运用条件分支、switch 与循环,实现一个成绩评级与统计工具:
const grades = [95, 82, 67, 45, 88, 59];
// 用 switch 结构统计等级分布
const count = { 优秀: 0, 良好: 0, 及格: 0, 不及格: 0 };
for (const g of grades) {
const level =
g >= 90 ? "优秀" :
g >= 80 ? "良好" :
g >= 60 ? "及格" : "不及格";
// switch 判断等级归属
switch (level) {
case "优秀":
count.优秀++;
break;
case "良好":
count.良好++;
break;
case "及格":
count.及格++;
break;
default:
count.不及格++;
}
}
console.log(count); // { 优秀: 1, 良好: 2, 及格: 1, 不及格: 2 }
// 汇总总分,用 continue 跳过异常数据
let sum = 0;
for (const g of grades) {
if (g < 0 || g > 100) continue;
sum += g;
}
console.log("总分:" + sum); // 总分:436流程控制是程序组织的骨架:用条件分支决定"走哪条路",用循环处理"重复的事"。建议把每种循环都手写一遍并观察输出差异,尤其注意 for-in 与 for-of、while 与 do-while 之间的区别。