控制流语句决定代码的执行顺序。条件语句用于分支选择,循环语句用于重复执行。
🎯 if 语句#
基本用法#
const age = 18
if (age >= 18) { console.log('成年人')}
// if...elseif (age >= 18) { console.log('成年人')} else { console.log('未成年人')}
// if...else if...elseconst score = 85
if (score >= 90) { console.log('优秀')} else if (score >= 80) { console.log('良好')} else if (score >= 60) { console.log('及格')} else { console.log('不及格')}条件表达式#
// 任何表达式都可以作为条件if (true) console.log('执行')if (1) console.log('执行') // 非零数字是真值if ('hello') console.log('执行') // 非空字符串是真值if ([]) console.log('执行') // 空数组也是真值!if ({}) console.log('执行') // 空对象也是真值!
// 假值:false, 0, "", null, undefined, NaNif (0) console.log('不执行')if ('') console.log('不执行')if (null) console.log('不执行')简化写法#
// 单行语句可以省略花括号(不推荐)if (age >= 18) console.log('成年')
// 三元运算符替代简单 if...elseconst status = age >= 18 ? '成年' : '未成年'
// && 短路替代 ifage >= 18 && console.log('成年')
// || 短路设置默认值const name = inputName || '游客'🔶 始终使用花括号,避免后续维护时出错:
// 危险写法if (condition) doSomething()doSomethingElse() // 这行总是会执行!
// 安全写法if (condition) { doSomething() doSomethingElse()}switch 语句#
基本用法#
const day = 3
switch (day) { case 1: console.log('星期一') break case 2: console.log('星期二') break case 3: console.log('星期三') break default: console.log('其他')}注意事项#
// 1. 严格相等比较const value = '1'switch (value) { case 1: console.log('数字1') // 不会执行 break case '1': console.log('字符串1') // 执行这个 break}
// 2. 忘记 break 会穿透const fruit = 'apple'switch (fruit) { case 'apple': console.log('苹果') // 执行 case 'banana': console.log('香蕉') // 也执行! break case 'orange': console.log('橘子') break}
// 3. 利用穿透合并 caseconst month = 2switch (month) { case 1: case 3: case 5: case 7: case 8: case 10: case 12: console.log('31天') break case 4: case 6: case 9: case 11: console.log('30天') break case 2: console.log('28或29天') break}替代方案#
// 用对象映射替代 switchconst dayNames = { 1: '星期一', 2: '星期二', 3: '星期三', 4: '星期四', 5: '星期五', 6: '星期六', 0: '星期日',}
const today = new Date().getDay()console.log(dayNames[today] ?? '未知')
// 用函数映射处理复杂逻辑const handlers = { add: (a, b) => a + b, subtract: (a, b) => a - b, multiply: (a, b) => a * b, divide: (a, b) => a / b,}
function calculate(operation, a, b) { const handler = handlers[operation] if (!handler) throw new Error('未知操作') return handler(a, b)}for 循环#
基本语法#
// for (初始化; 条件; 更新)for (let i = 0; i < 5; i++) { console.log(i) // 0, 1, 2, 3, 4}
// 倒序for (let i = 4; i >= 0; i--) { console.log(i) // 4, 3, 2, 1, 0}
// 步长为 2for (let i = 0; i < 10; i += 2) { console.log(i) // 0, 2, 4, 6, 8}
// 多个变量for (let i = 0, j = 10; i < j; i++, j--) { console.log(i, j) // 0 10, 1 9, 2 8, 3 7, 4 6}遍历数组#
const fruits = ['苹果', '香蕉', '橘子']
// 传统 for 循环for (let i = 0; i < fruits.length; i++) { console.log(fruits[i])}
// 缓存长度(微优化)for (let i = 0, len = fruits.length; i < len; i++) { console.log(fruits[i])}
// for...of(推荐)for (const fruit of fruits) { console.log(fruit)}
// 需要索引时用 entries()for (const [index, fruit] of fruits.entries()) { console.log(index, fruit)}无限循环#
// 三个部分都可以省略for (;;) { console.log('无限循环') break // 必须有退出条件}
// 等价于while (true) { console.log('无限循环') break}while 循环#
基本用法#
// while:先判断后执行let count = 0while (count < 5) { console.log(count) count++}
// do...while:先执行后判断(至少执行一次)let num = 0do { console.log(num) num++} while (num < 5)
// 区别展示let x = 10while (x < 5) { console.log('while:', x) // 不执行}
let y = 10do { console.log('do-while:', y) // 执行一次} while (y < 5)实际应用#
// 读取用户输���直到有效function getValidInput() { let input do { input = prompt('请输入一个正数') } while (isNaN(input) || input <= 0) return Number(input)}
// 处理异步重试async function fetchWithRetry(url, maxRetries = 3) { let attempts = 0 while (attempts < maxRetries) { try { return await fetch(url) } catch (error) { attempts++ if (attempts === maxRetries) throw error await new Promise((r) => setTimeout(r, 1000 * attempts)) } }}for…in 和 for…of#
for…in:遍历键名#
// 遍历对象属性const person = { name: '张三', age: 25, city: '北京' }
for (const key in person) { console.log(key, person[key])}// name 张三// age 25// city 北京
// 🔶 也会遍历继承的可枚举属性function Animal(name) { this.name = name}Animal.prototype.speak = function () {}
const dog = new Animal('小黑')for (const key in dog) { console.log(key) // name, speak(包含原型属性)}
// 只遍历自身属性for (const key in dog) { if (dog.hasOwnProperty(key)) { console.log(key) // 只有 name }}for…of:遍历值#
// 遍历数组const arr = ['a', 'b', 'c']for (const value of arr) { console.log(value) // a, b, c}
// 遍历字符串for (const char of 'hello') { console.log(char) // h, e, l, l, o}
// 遍历 Mapconst map = new Map([ ['name', '张三'], ['age', 25],])for (const [key, value] of map) { console.log(key, value)}
// 遍历 Setconst set = new Set([1, 2, 3])for (const value of set) { console.log(value)}
// 🔶 普通对象不能直接用 for...of// for (const value of person) {} // TypeError
// 需要转换for (const key of Object.keys(person)) { console.log(key)}for (const value of Object.values(person)) { console.log(value)}for (const [key, value] of Object.entries(person)) { console.log(key, value)}对比总结#
| 特性 | for…in | for…of |
|---|---|---|
| 遍历对象 | ✅ 键名 | ❌ 需要转换 |
| 遍历数组 | ⚠️ 索引(不推荐) | ✅ 值 |
| 原型属性 | 会遍历 | 不会 |
| 可迭代对象 | ❌ | ✅ |
| 性能 | 较慢 | 较快 |
break 和 continue#
break:跳出循环#
// 找到目标后停止const numbers = [1, 3, 5, 7, 9, 2, 4]let firstEven = null
for (const num of numbers) { if (num % 2 === 0) { firstEven = num break // 找到第一个偶数后退出 }}console.log(firstEven) // 2continue:跳过本次迭代#
// 跳过偶数for (let i = 0; i < 10; i++) { if (i % 2 === 0) continue console.log(i) // 1, 3, 5, 7, 9}
// 跳过无效数据const data = [1, null, 3, undefined, 5]const valid = []for (const item of data) { if (item == null) continue valid.push(item)}console.log(valid) // [1, 3, 5]标签语句#
用于跳出多层循环:
// 没有标签:只能跳出内层循环for (let i = 0; i < 3; i++) { for (let j = 0; j < 3; j++) { if (i === 1 && j === 1) break console.log(i, j) }}// 输出:0,0 0,1 0,2 1,0 2,0 2,1 2,2
// 使用标签:跳出外层循环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循环最佳实践#
选择合适的循环#
// 1. 已知次数:用 forfor (let i = 0; i < 10; i++) { // ...}
// 2. 遍历数组值:用 for...offor (const item of array) { // ...}
// 3. 遍历对象属性:用 for...in 或 Object.keysfor (const key in object) { // ...}
// 4. 条件循环:用 whilewhile (condition) { // ...}
// 5. 至少执行一次:用 do...whiledo { // ...} while (condition)避免修改循环中的数组#
// 🔶 危险:遍历时修改数组const arr = [1, 2, 3, 4, 5]for (let i = 0; i < arr.length; i++) { if (arr[i] % 2 === 0) { arr.splice(i, 1) // 删除偶数 }}console.log(arr) // [1, 3, 5]?不一定!
// ✅ 安全:倒序遍历for (let i = arr.length - 1; i >= 0; i--) { if (arr[i] % 2 === 0) { arr.splice(i, 1) }}
// ✅ 更好:使用 filter 创建新数组const odds = arr.filter((n) => n % 2 !== 0)使用数组方法替代循环#
const numbers = [1, 2, 3, 4, 5]
// 代替 for:forEachnumbers.forEach((n) => console.log(n))
// 代替 for + push:mapconst doubled = numbers.map((n) => n * 2)
// 代替 for + if + push:filterconst evens = numbers.filter((n) => n % 2 === 0)
// 代替 for + 累加:reduceconst sum = numbers.reduce((acc, n) => acc + n, 0)
// 代替 for + break(查找):findconst firstBig = numbers.find((n) => n > 3)
// 代替 for + 条件判断:some/everyconst hasEven = numbers.some((n) => n % 2 === 0)const allPositive = numbers.every((n) => n > 0)常见陷阱#
🙋 闭包与循环变量#
// 经典问题:var 没有块级作用域for (var i = 0; i < 3; i++) { setTimeout(() => console.log(i), 100)}// 输出:3, 3, 3
// 解决方案1:使用 letfor (let i = 0; i < 3; i++) { setTimeout(() => console.log(i), 100)}// 输出:0, 1, 2
// 解决方案2:IIFE(ES5 写法)for (var i = 0; i < 3; i++) { ;(function (j) { setTimeout(() => console.log(j), 100) })(i)}🙋 修改循环条件#
// 无限循环!const arr = [1, 2, 3]for (let i = 0; i < arr.length; i++) { arr.push(i) // 数组不断变长}
// 应该缓存长度const len = arr.lengthfor (let i = 0; i < len; i++) { arr.push(i)}🙋 浮点数作为循环条件#
// 可能不会停止!for (let i = 0; i !== 1; i += 0.1) { console.log(i)}// 因为 0.1 + 0.1 + ... 可能永远不等于 1
// 使用 < 而不是 !==for (let i = 0; i < 1; i += 0.1) { console.log(i.toFixed(1))}总结#
| 语句 | 适用场景 | 要点 |
|---|---|---|
| if…else | 条件分支 | 用 包裹 |
| switch | 多值匹配 | 记得 break |
| for | 已知次数 | 缓存 length |
| for…of | 遍历可迭代对象 | 推荐遍历数组 |
| for…in | 遍历对象键 | 注意原型属性 |
| while | 条件循环 | 确保能退出 |
| do…while | 至少执行一次 | 先执行后判断 |
核心建议:
- 用
for...of遍历数组,for...in遍历对象 - 用数组方法(map/filter/reduce)替代简单循环
- 使用
let而不是var声明循环变量 - 复杂逻辑用对象映射替代长 switch