1.运算符
1.1 赋值运算符
已经学过的赋值运算符: = 将等号右边的值赋予给左边, 要求左边必须是一个容器
其他赋值运算符: +=、-=、*=、/=、%=
代码示例
//原始写法
let num = 1
num = num + 1
console.log(num)//输出结果是2
//简单写法
let num = 1
num += 1
console.log(num)//输出结果21.2 一元运算符
众多的JavaScript的运算符可以根据所需表达式的个数
分为一元运算符、二元运算符、三元运算符
二元运算符
let num = 10 + 20一元运算符 正号
let num = +'123'自增自减
//符号 ++ 作用 让变量值+1
//符号 -- 作用 让变量值-1
//前置自增
let num = 1
++num
//后置自增
let num1 = 1
num1++注意:前置自增后后置自增单独运算没有区别,但是参与运算会有区别
前置自增: 先自加再使用++在前是前置自增
let i = 1
console.log(++i + 2)//结果是4 i是2 自增之后在进行运算后置自增: 先运算后再自增++在后是后自增
let i = 1
console.log(i++ + 2)//结果是3 先和2相加运算输出完毕之后i再自加是2面试题
let i = 1
//运算过程
//1. i++ 参与运算结果是1 i自增i + 1 = 2
//2. ++i i = 2 + 1 参与运算就是3 i = 3
//3. 所以运算结果是 1 + 3 + 3 = 7 i = 3
console.log(i++ + ++i + i)//结果是7
console.log(i)//结果是31.3 比较运算符
比较运算符用于比较两个值,并返回一个布尔值(true 或 false)。
1.3.1 比较运算符表格
| 运算符 | 名称 | 作用 | 示例 | 结果 | 说明 |
|---|---|---|---|---|---|
> | 大于 | 判断左边是否大于右边 | 5 > 3 | true | 数值比较 |
< | 小于 | 判断左边是否小于右边 | 5 < 3 | false | 数值比较 |
>= | 大于等于 | 判断左边是否大于或等于右边 | 5 >= 5 | true | 包含等于的情况 |
<= | 小于等于 | 判断左边是否小于或等于右边 | 5 <= 3 | false | 包含等于的情况 |
== | 相等(弱类型) | 判断两边值是否相等,会进行类型转换 | '5' == 5 | true | 自动类型转换 |
=== | 全等(强类型) | 判断两边值和类型是否都相等 | '5' === 5 | false | 不进行类型转换,推荐使用 |
!= | 不相等(弱类型) | 判断两边值是否不相等,会进行类型转换 | '5' != 5 | false | 自动类型转换 |
!== | 不全等(强类型) | 判断两边值或类型是否不相等 | '5' !== 5 | true | 不进行类型转换,推荐使用 |
1.3.2 代码示例
// 1. 大于运算符 >
console.log(5 > 3) // true
console.log(3 > 5) // false
// 2. 小于运算符 <
console.log(5 < 3) // false
console.log(3 < 5) // true
// 3. 大于等于运算符 >=
console.log(5 >= 5) // true
console.log(5 >= 3) // true
console.log(3 >= 5) // false
// 4. 小于等于运算符 <=
console.log(5 <= 5) // true
console.log(3 <= 5) // true
console.log(5 <= 3) // false
// 5. 相等运算符 == (弱类型,会自动转换)
console.log(5 == 5) // true
console.log('5' == 5) // true (字符串'5'转换为数字5)
console.log(true == 1) // true (true转换为1)
console.log(false == 0) // true (false转换为0)
console.log(null == undefined) // true (两者相等)
console.log('' == 0) // true (空字符串转换为0)
// 6. 全等运算符 === (强类型,不转换类型,推荐使用)
console.log(5 === 5) // true
console.log('5' === 5) // false (类型不同,字符串 vs 数字)
console.log(true === 1) // false (类型不同,布尔 vs 数字)
console.log(null === undefined) // false (类型不同)
// 7. 不相等运算符 != (弱类型)
console.log(5 != 3) // true
console.log('5' != 5) // false (自动转换后相等)
// 8. 不全等运算符 !== (强类型,推荐使用)
console.log(5 !== 3) // true
console.log('5' !== 5) // true (类型不同)1.3.3 重要注意事项
类型转换陷阱
| 比较表达式 | == 结果 | === 结果 | 说明 |
|---|---|---|---|
'' == 0 | true | false | 空字符串转换为0 |
'0' == 0 | true | false | 字符串'0'转换为0 |
false == 0 | true | false | false转换为0 |
true == 1 | true | false | true转换为1 |
null == undefined | true | false | 两者相等但类型不同 |
NaN == NaN | false | false | NaN不等于任何值,包括自身 |
最佳实践
- 推荐使用
===和!==:避免类型转换带来的意外结果 - 避免使用
==和!=:除非有特殊需求需要类型转换 - 注意
NaN的特殊性:判断NaN需要使用isNaN()函数 - 浮点数比较:
0.1 + 0.2 === 0.3返回false(精度问题)
1.3.4 NaN 特殊情况
// NaN (Not a Number) 特殊情况
console.log(NaN == NaN) // false NaN不等于任何值
console.log(NaN === NaN) // false NaN不等于任何值
console.log(isNaN(NaN)) // true 使用isNaN判断NaN
console.log(isNaN('hello')) // true 'hello'转换为数字为NaN
// 浮点数精度问题
console.log(0.1 + 0.2 === 0.3) // false
console.log(0.1 + 0.2) // 0.30000000000000004
// 正确的浮点数比较方法
const absDiff = Math.abs((0.1 + 0.2) - 0.3)
console.log(absDiff < 0.000001) // true 允许一定误差1.3.5 实际应用场景
// 1. 条件判断
let age = 18
if (age >= 18) {
console.log('已成年')
}
// 2. 数组筛选
let numbers = [1, 2, 3, 4, 5]
let bigNumbers = numbers.filter(num => num > 3)
console.log(bigNumbers) // [4, 5]
// 3. 表单验证
let password = '123456'
if (password.length >= 6) {
console.log('密码长度符合要求')
}
// 4. 循环控制
for (let i = 0; i < 5; i++) {
console.log(i) // 0, 1, 2, 3, 4
}1.4 逻辑运算符
逻辑运算符用于对布尔值进行运算,返回的结果也是布尔值。在实际开发中常用于条件判断。
1.4.1 逻辑运算符表格
| 运算符 | 名称 | 作用 | 示例 | 结果 | 说明 |
|---|---|---|---|---|---|
&& | 逻辑与(AND) | 两边都为true时返回true | true && true | true | 全真才真,一假即假 |
|| | 逻辑或(OR) | 两边只要有一个为true就返回true | true || false | true | 一真即真,全假才假 |
! | 逻辑非(NOT) | 取反,true变false,false变true | !true | false | 真变假,假变真 |
1.4.2 基础代码示例
// 1. 逻辑与 &&
// 全真才真,一假即假
console.log(true && true) // true
console.log(true && false) // false
console.log(false && true) // false
console.log(false && false) // false
// 2. 逻辑或 ||
// 一真即真,全假才假
console.log(true || true) // true
console.log(true || false) // true
console.log(false || true) // true
console.log(false || false) // false
// 3. 逻辑非 !
// 真变假,假变真
console.log(!true) // false
console.log(!false) // true
console.log(!!true) // true (双重否定,还原为true)1.4.3 逻辑运算符的真值表
| 表达式A | 表达式B | A && B | A || B | !A |
|---|---|---|---|---|
true | true | true | true | false |
true | false | false | true | false |
false | true | false | true | true |
false | false | false | false | true |
1.4.4 逻辑运算符的短路特性
重要概念:逻辑运算符具有短路特性,会根据第一个表达式的值决定是否执行第二个表达式。
4.1 逻辑与 && 的短路特性
// 如果第一个表达式为false,则不会执行第二个表达式
// 因为结果注定为false
console.log(false && console.log('不会执行')) // false
console.log(true && console.log('会执行')) // 会执行
// 实际应用示例
let x = 10
// 如果x大于5,则执行函数
x > 5 && console.log('x大于5') // 输出:x大于5
// 如果x不大于5,则不会执行
x > 20 && console.log('x大于20') // 不输出4.2 逻辑或 || 的短路特性
// 如果第一个表达式为true,则不会执行第二个表达式
// 因为结果注定为true
console.log(true || console.log('不会执行')) // true
console.log(false || console.log('会执行')) // 会执行
// 实际应用示例
let y = 10
// 如果y不大于20,则执行函数
y > 20 || console.log('y不大于20') // 输出:y不大于20
// 如果y大于20,则不会执行
y > 5 || console.log('y不大于5') // 不输出1.4.5 逻辑运算符的返回值
注意:逻辑运算符返回的不一定是布尔值,而是参与运算的某个值。
// 逻辑与 &&:返回第一个为false的值,如果都为true则返回最后一个
console.log(0 && 1) // 0 (第一个为false,直接返回)
console.log(1 && 2) // 2 (都为true,返回最后一个)
console.log(1 && 0 && 2) // 0 (返回第一个为false的值)
console.log(1 && 2 && 3) // 3 (都为true,返回最后一个)
// 逻辑或 ||:返回第一个为true的值,如果都为false则返回最后一个
console.log(0 || 1) // 1 (返回第一个为true的值)
console.log(0 || 0) // 0 (都为false,返回最后一个)
console.log(1 || 2 || 0) // 1 (返回第一个为true的值)
console.log(0 || 0 || 2) // 2 (都为false才检查下一个)
// 逻辑非 !:总是返回布尔值
console.log(!'hello') // false
console.log(!0) // true
console.log(!'') // true1.4.6 常见实际应用场景
6.1 参数默认值设置
// 方法1:使用逻辑或设置默认值(传统方式)
function greet(name) {
// 如果name为假值(null、undefined、''、0等),则使用'朋友'
name = name || '朋友'
console.log('你好,' + name)
}
greet() // 输出:你好,朋友
greet('小明') // 输出:你好,小明
greet('') // 输出:你好,朋友(空字符串是假值)6.2 多条件判断
// 场景1:用户登录验证
let username = 'admin'
let password = '123456'
let isActivated = true
// 只有用户名、密码都正确且账号已激活,才允许登录
if (username === 'admin' && password === '123456' && isActivated) {
console.log('登录成功')
} else {
console.log('登录失败')
}
// 场景2:权限检查
let role = 'admin' // admin、editor、viewer
// admin 或 editor 可以编辑
if (role === 'admin' || role === 'editor') {
console.log('有编辑权限')
}
// 场景3:表单验证
let username = ''
let email = 'test@example.com'
// 如果用户名或邮箱为空,提示错误
if (!username || !email) {
console.log('用户名和邮箱不能为空')
}6.3 简洁的条件执行
// 使用 && 简化条件判断
let age = 20
// 传统写法
if (age >= 18) {
console.log('已成年')
}
// 简洁写法(只有条件为true时才执行)
age >= 18 && console.log('已成年')
// 使用 || 简化默认值处理
let userName = localStorage.getItem('user') || '游客'
console.log('欢迎,' + userName)6.4 数组和对象的判断
// 判断数组是否有内容
let arr = [1, 2, 3]
if (arr && arr.length > 0) {
console.log('数组不为空')
}
// 判断对象是否有属性
let obj = { name: '小明' }
if (obj && obj.name) {
console.log('对象有name属性')
}
// 安全访问嵌套对象属性
let user = {
profile: {
name: '小明'
}
}
// 只有user和user.profile都存在时才访问name
let name = user && user.profile && user.profile.name
console.log(name) // '小明'1.4.7 类型转换规则
在逻辑运算中,JavaScript会将值转换为布尔值:
| 类型 | 转换为false的值 | 转换为true的值 |
|---|---|---|
| 数字 | 0、NaN | 其他数字(如 1、-1、3.14) |
| 字符串 | ''(空字符串) | 非空字符串(如 'hello'、'0') |
| 布尔值 | false | true |
| 对象/数组 | - | 对象和数组总是true(即使是空对象/数组) |
| null | null | - |
| undefined | undefined | - |
// 假值
console.log(!0) // true
console.log(!'') // true
console.log(!false) // true
console.log(!null) // true
console.log(!undefined) // true
console.log(!NaN) // true
// 真值
console.log(!1) // false
console.log(!'hello') // false
console.log(!true) // false
console.log(![]) // false (空数组是真值)
console.log(!{}) // false (空对象是真值)1.4.8 面试题
// 面试题1
console.log(0 && 1) // 0
console.log(1 && 0) // 0
console.log(1 && 2 && 3) // 3
// 面试题2
console.log(0 || 1) // 1
console.log(1 || 0) // 1
console.log(0 || 0 || 2) // 2
// 面试题3:运算优先级
let a = 1, b = 2, c = 3
console.log(a > b && c > a) // false (先比较,再进行逻辑运算)
console.log(a < b || c > a) // true
// 面试题4:复杂逻辑
let x = 5
console.log(x > 3 && x < 10 || x === 0) // true
// 等价于:(x > 3 && x < 10) || x === 0
// 解释:先计算 &&(x > 3 && x < 10 = true),再计算 ||(true || false = true)
// 面试题5:短路求值
let y = 10
let z = (y > 5 && y++) || y
console.log(y, z) // 11, 11
// 解释:y > 5为true,执行y++(y变为11),返回11,所以z=111.4.9 最佳实践
推荐做法:
- 使用
&&简化条件判断(condition && doSomething()) - 使用
||设置默认值(let val = input || 'default') - 使用
!!将值转换为布尔值(let bool = !!value) - 复杂条件建议使用括号提高可读性
注意事项:
- 注意短路特性,避免副作用
- 理解逻辑运算符返回值不一定是布尔值
- 使用
||设置默认值时,假值(如0、'')会被替换 - 对于更复杂的逻辑,考虑使用三元运算符或if语句
1.5 运算符优先级
运算符优先级决定了表达式中运算的执行顺序。优先级高的先执行,优先级低的后执行。相同优先级的运算符从左到右执行。
1.5.1 运算符优先级表格
| 优先级 | 运算符 | 说明 | 结合性 |
|---|---|---|---|
| 1(最高) | . [] () | 成员访问、数组下标、函数调用 | 从左到右 |
| 2 | new | 创建对象 | 从右到左 |
| 3 | ++ -- | 后置自增自减 | 从左到右 |
| 4 | ! ~ + - typeof void delete | 逻辑非、按位非、一元加减等 | 从右到左 |
| 5 | ** | 指数运算 | 从右到左 |
| 6 | * / % | 乘、除、取余 | 从左到右 |
| 7 | + - | 加、减 | 从左到右 |
| 8 | << >> >>> | 左移、右移、无符号右移 | 从左到右 |
| 9 | < <= > >= | 小于、小于等于、大于、大于等于 | 从左到右 |
| 10 | == != === !== | 相等、不等、全等、不全等 | 从左到右 |
| 11 | & | 按位与 | 从左到右 |
| 12 | ^ | 按位异或 | 从左到右 |
| 13 | | | 按位或 | 从左到右 |
| 14 | && | 逻辑与 | 从左到右 |
| 15 | || | 逻辑或 | 从左到右 |
| 16 | ? : | 条件运算符(三元) | 从右到左 |
| 17 | = += -= *= /= %= **= 等 | 赋值运算符 | 从右到左 |
| 18(最低) | , | 逗号运算符 | 从左到右 |
说明:
- 结合性:相同优先级的运算符,"从左到右"表示先算左边的,"从右到左"表示先算右边的
- 优先级数字越小,优先级越高
1.5.2 常见优先级对比
优先级从高到低(常用运算符):
括号 () 最高
成员访问 . []
一元运算符 ! ++ -- typeof
乘除模 * / %
加减 + -
比较 < > <= >=
相等 == != === !==
逻辑与 &&
逻辑或 ||
赋值 = += -= ...
条件运算符 ? :
逗号运算符 , 最低1.5.3 代码示例
示例1:算术运算符优先级
// 乘除优先级高于加减
console.log(1 + 2 * 3) // 7 (先算 2*3=6,再算 1+6=7)
console.log(10 - 2 * 3) // 4 (先算 2*3=6,再算 10-6=4)
console.log(10 / 2 + 3) // 8 (先算 10/2=5,再算 5+3=8)
console.log(10 / (2 + 3)) // 2 (括号优先,先算 2+3=5,再算 10/5=2)示例2:比较运算符与逻辑运算符
// 比较运算符优先级高于逻辑运算符
console.log(1 < 2 && 2 < 3) // true
// 等价于: (1 < 2) && (2 < 3)
// true && true = true
console.log(1 > 2 || 2 < 3) // true
// 等价于: (1 > 2) || (2 < 3)
// false || true = true
console.log(1 === 1 && 2 === 2) // true
// 等价于: (1 === 1) && (2 === 2)
// true && true = true示例3:逻辑与 vs 逻辑或
// 逻辑与 && 优先级高于逻辑或 ||
console.log(true || false && false) // true
// 等价于: true || (false && false)
// true || false = true
console.log(false && false || true) // true
// 等价于: (false && false) || true
// false || true = true
console.log(true && true || false) // true
// 等价于: (true && true) || false
// true || false = true示例4:赋值运算符优先级
// 赋值运算符优先级很低
let a = 2 + 3 * 4
console.log(a) // 14 (先算 2+12=14,再赋值)
// 连续赋值
let x = y = 5
console.log(x, y) // 5, 5
// 等价于: let x = (y = 5)
// 复合赋值
let b = 10
b += 2 * 3
console.log(b) // 16
// 等价于: b = b + (2 * 3)
// b = 10 + 6 = 16示例5:自增自减与其他运算符
let a = 5
let b = a++ + 2
console.log(a, b) // 6, 7
// 解释:先使用a的值(5)参与运算,所以b=5+2=7,然后a自增为6
let c = ++a + 2
console.log(a, c) // 7, 9
// 解释:先a自增为7,然后使用a的值(7)参与运算,所以c=7+2=9示例6:成员访问与函数调用
let obj = { name: '小明', age: 18 }
// 成员访问优先级高于赋值
let age = obj.age + 1
console.log(age) // 19
// 等价于: let age = (obj.age) + 1
// 函数调用优先级最高
let result = Math.max(1, 2, 3) * 2
console.log(result) // 6
// 等价于: let result = (Math.max(1, 2, 3)) * 2
// let result = 3 * 2 = 6示例7:三元运算符优先级
// 三元运算符优先级高于赋值
let score = 85
let level = score >= 90 ? '优秀' : score >= 60 ? '及格' : '不及格'
console.log(level) // 及格
// 等价于: let level = (score >= 90) ? '优秀' : ((score >= 60) ? '及格' : '不及格')
// 三元运算符优先级低于逻辑运算符
let x = true && false ? 'A' : 'B'
console.log(x) // B
// 等价于: let x = (true && false) ? 'A' : 'B'
// let x = false ? 'A' : 'B' = 'B'1.5.4 结合性示例
从左到右(多数运算符)
// 乘除、加减:从左到右
console.log(10 - 3 - 2) // 5
// 等价于: (10 - 3) - 2 = 7 - 2 = 5
console.log(20 / 4 / 2) // 2.5
// 等价于: (20 / 4) / 2 = 5 / 2 = 2.5
console.log(1 + 2 - 3 + 4) // 4
// 等价于: ((1 + 2) - 3) + 4 = 4从右到左(赋值、一元运算符等)
// 赋值运算符:从右到左
let a = b = c = 10
console.log(a, b, c) // 10, 10, 10
// 等价于: let a = (b = (c = 10))
// 一元运算符:从右到左
let x = -+-5
console.log(x) // 5
// 等价于: let x = -(-(-5)) = -(-5) = 5
// 指数运算:从右到左
console.log(2 ** 3 ** 2) // 512
// 等价于: 2 ** (3 ** 2) = 2 ** 9 = 512
// 三元运算符:从右到左
let result = 1 ? 2 : 3 ? 4 : 5
console.log(result) // 2
// 等价于: 1 ? 2 : (3 ? 4 : 5)
// true ? 2 : 4 = 21.5.5 复杂表达式解析
// 示例1:综合运算
let result1 = 1 + 2 * 3 > 4 && 5 < 6
console.log(result1) // true
// 解析步骤:
// 1. 2 * 3 = 6
// 2. 1 + 6 = 7
// 3. 7 > 4 = true
// 4. 5 < 6 = true
// 5. true && true = true
// 示例2:带括号的复杂表达式
let result2 = (1 + 2) * (3 + 4) > 10 && false || true
console.log(result2) // true
// 解析步骤:
// 1. (1 + 2) = 3
// 2. (3 + 4) = 7
// 3. 3 * 7 = 21
// 4. 21 > 10 = true
// 5. true && false = false
// 6. false || true = true
// 示例3:混合运算
let a = 5
let result3 = a++ * 2 + 3 >= 10 && a < 10 || false
console.log(result3) // true
// 解析步骤:
// 1. a++ 使用a的值5,然后a变为6
// 2. 5 * 2 = 10
// 3. 10 + 3 = 13
// 4. 13 >= 10 = true
// 5. 6 < 10 = true (注意:此时a已经是6)
// 6. true && true = true
// 7. true || false = true1.5.6 面试题
// 面试题1
console.log(1 + '1') // '11' (字符串拼接,+两侧有字符串时)
console.log(1 - '1') // 0 (减法会转为数字)
console.log(1 * '1') // 1 (乘法会转为数字)
console.log(1 / '1') // 1 (除法会转为数字)
console.log(1 + 1 + '1') // '21' (1+1=2,然后2+'1'='21')
console.log('1' + 1 + 1) // '111' ('1'+1='11',然后'11'+1='111')
// 面试题2
console.log(1 > 2 > 3) // false
// 解析:(1 > 2) > 3
// false > 3
// 0 > 3 = false
console.log(1 < 2 < 3) // true
// 解析:(1 < 2) < 3
// true < 3
// 1 < 3 = true
// 面试题3
let a = 1
let b = a++ + ++a + a
console.log(b) // 6
// 解析:a=1
// 1. a++ 使用a的值1参与运算,然后a变为2
// 2. ++a a先自增为3,然后使用3参与运算
// 3. a 使用当前值3
// 4. 所以:1 + 3 + 3 = 7
// 等等,让我重新分析:
// a++时返回1,a变为2
// ++a时a变为3,返回3
// a是3
// 所以:1 + 3 + 3 = 7
// 面试题4
console.log(0 || 1 && 2) // 2
// 解析:0 || (1 && 2)
// 0 || 2 = 2
// &&优先级高于||
// 面试题5
console.log(1 && 2 || 0) // 2
// 解析:(1 && 2) || 0
// 2 || 0 = 2
// &&优先级高于||
// 面试题6
console.log(5 > 3 ? 2 : 3 + 4) // 2
// 解析:(5 > 3) ? 2 : (3 + 4)
// true ? 2 : 7 = 2
// 面试题7
let x = 1, y = 2
console.log(x += y += 3) // 6
// 解析:x += (y += 3)
// y += 3 => y = 2 + 3 = 5
// x += y => x = 1 + 5 = 6
// 返回6
// 面试题8
console.log(typeof typeof 1) // 'string'
// 解析:typeof 1 = 'number'
// typeof 'number' = 'string'1.5.7 最佳实践
推荐做法:
- 使用括号明确优先级:
// 不推荐(依赖运算符优先级,可读性差)
let result = 1 + 2 * 3 > 4 && 5 < 6
// 推荐(使用括号,清晰明了)
let result = ((1 + 2) * 3 > 4) && (5 < 6)- 复杂表达式拆分:
// 不推荐
let score = (a + b) * (c - d) > e && f < g ? h : i
// 推荐
let left = (a + b) * (c - d)
let condition1 = left > e && f < g
let score = condition1 ? h : i- 记住常用优先级:
- 括号
()优先级最高 - 一元运算符
!++--较高 - 乘除模
*/%高于加减+- - 比较运算符
>==等 - 逻辑与
&&高于逻辑或|| - 赋值运算符
=优先级最低
注意事项:
- 不确定的优先级时,使用括号是最保险的做法
+运算符:如果一侧是字符串,执行字符串拼接- 不要写出过于复杂的表达式,拆分为多行
- 理解
&&优先级高于||,这个在条件判断中很重要 - 记住赋值运算符是从右到左的结合性
1.5.8 快速参考表(简化版)
| 运算符类别 | 运算符 | 优先级(相对高低) |
|---|---|---|
| 括号 | () | 最高 |
| 一元运算符 | ! ++ -- typeof | 很高 |
| 指数 | ** | 很高 |
| 乘除模 | * / % | 高 |
| 加减 | + - | 中高 |
| 比较 | > < >= <= | 中 |
| 相等 | == != === !== | 中 |
| 逻辑与 | && | 中低 |
| 逻辑或 | || | 低 |
| 赋值 | = += -= 等 | 很低 |
| 逗号 | , | 最低 |
2.语句
2.1 表达式和语句
在JavaScript中,表达式(Expression)和语句(Statement)是两个重要的概念,理解它们的区别对于掌握JavaScript非常重要。
2.1.1 基本概念
什么是表达式?
表达式是可以被求值的代码片段,会产生一个值。表达式可以包含变量、运算符、函数调用等。
什么是语句?
语句是执行某种操作的代码指令,不会直接产生值。语句用于完成特定的任务,如声明变量、控制流程等。
2.1.2 表达式与语句对比表
| 特性 | 表达式(Expression) | 语句(Statement) |
|---|---|---|
| 定义 | 可以被求值的代码片段 | 执行某种操作的代码指令 |
| 返回值 | 有返回值,计算后产生一个值 | 无返回值,或返回undefined |
| 用途 | 用于计算、产生数据 | 用于控制程序流程、执行操作 |
| 位置 | 可以出现在任何需要值的地方 | 必须独立作为一行或块执行 |
| 嵌套 | 可以嵌套在其他表达式中 | 通常不嵌套(语句块除外) |
| 示例 | 1 + 2、x * y、fn() | if、for、while、var |
2.1.3 常见的表达式
算术表达式
// 基本算术运算
1 + 2 // 3
10 - 5 // 5
3 * 4 // 12
8 / 2 // 4
// 复杂表达式
(1 + 2) * 3 // 9
10 / 2 + 3 // 8字符串表达式
'Hello' + ' World' // 'Hello World'
'abc' + 123 // 'abc123'逻辑表达式
true && false // false
true || false // true
!true // false比较表达式
1 > 2 // false
1 === 1 // true
1 < 3 && 3 > 1 // true变量表达式
let x = 10
x // 10
x + 5 // 15函数调用表达式
function add(a, b) {
return a + b
}
add(1, 2) // 3
Math.max(1, 2, 3) // 3
console.log('hello') // undefined (返回值是undefined)对象和数组表达式
// 对象字面量
{ name: '小明', age: 18 }
// 数组字面量
[1, 2, 3, 4, 5]
// 属性访问表达式
let obj = { name: '小明' }
obj.name // '小明'
// 数组元素访问表达式
let arr = [1, 2, 3]
arr[0] // 1条件(三元)表达式
let age = 18
age >= 18 ? '成年' : '未成年' // '成年'
let score = 85
score >= 90 ? '优秀' : score >= 60 ? '及格' : '不及格' // '及格'赋值表达式
let x
x = 10 // 10 (赋值表达式返回赋的值)
let y
y = x = 5 // 5 (连续赋值)
console.log(x, y) // 5, 5
// 复合赋值表达式
let a = 10
a += 5 // 15 (返回新值)2.1.4 常见的语句
变量声明语句
// 声明语句
let x = 10
const y = 20
var z = 30
// 注意:这些都是语句,不是表达式
// let x = 10 不能直接参与运算
// 错误示例:console.log(let x = 10) // 报错条件语句
// if 语句
let age = 18
if (age >= 18) {
console.log('已成年')
} else {
console.log('未成年')
}
// switch 语句
let day = 1
switch (day) {
case 1:
console.log('周一')
break
case 2:
console.log('周二')
break
default:
console.log('其他')
}循环语句
// for 循环
for (let i = 0; i < 5; i++) {
console.log(i)
}
// while 循环
let i = 0
while (i < 5) {
console.log(i)
i++
}
// do-while 循环
let j = 0
do {
console.log(j)
j++
} while (j < 5)
// for...in 循环
let obj = { name: '小明', age: 18 }
for (let key in obj) {
console.log(key, obj[key])
}
// for...of 循环
let arr = [1, 2, 3]
for (let item of arr) {
console.log(item)
}控制语句
// break 语句
for (let i = 0; i < 10; i++) {
if (i === 5) {
break // 退出循环
}
console.log(i)
}
// continue 语句
for (let i = 0; i < 10; i++) {
if (i % 2 === 0) {
continue // 跳过本次循环
}
console.log(i)
}
// return 语句
function add(a, b) {
return a + b // 返回值并退出函数
}
// throw 语句
throw new Error('出错了')空语句
// 空语句(只有一个分号)
;
// 实际应用
function doNothing() {
// 空函数体
}块语句
{
let x = 10
let y = 20
console.log(x + y)
}2.1.5 表达式与语句的区别实例
实例1:赋值语句 vs 赋值表达式
// 赋值语句
let x = 10 // 这是语句
// let x = 10 不能放在其他表达式中
// 赋值表达式
let y
y = 5 // 这是表达式,返回5
console.log(y = 5) // 5 (输出赋值表达式的值)
// 连续赋值
let a, b
a = b = 10 // 赋值表达式从右到左执行
console.log(a, b) // 10, 10实例2:条件语句 vs 条件表达式
// if 语句(语句)
let score = 85
let level
if (score >= 90) {
level = '优秀'
} else if (score >= 60) {
level = '及格'
} else {
level = '不及格'
}
console.log(level) // 及格
// 三元表达式(表达式)
let level2 = score >= 90 ? '优秀' : score >= 60 ? '及格' : '不及格'
console.log(level2) // 及格
// 三元表达式可以嵌套在其他表达式中
let result = `你的成绩是${score >= 90 ? '优秀' : score >= 60 ? '及格' : '不及格'}`
console.log(result) // 你的成绩是及格
// if 语句不能嵌套在表达式中
// let result2 = `成绩${if (score >= 90) '优秀'}` // 报错实例3:函数声明语句 vs 函数表达式
// 函数声明(语句)
function greet1(name) {
return '你好,' + name
}
console.log(greet1('小明')) // 你好,小明
// 函数表达式
let greet2 = function(name) {
return '你好,' + name
}
console.log(greet2('小明')) // 你好,小明
// 箭头函数(本质是函数表达式)
let greet3 = (name) => '你好,' + name
console.log(greet3('小明')) // 你好,小明
// 箭头函数可以直接作为表达式使用
console.log(((name) => '你好,' + name)('小红')) // 你好,小红实例4:对象字面量 vs 对象初始化语句
// 对象字面量(表达式)
let obj1 = { name: '小明', age: 18 }
console.log(obj1)
// 可以直接使用对象字面量
console.log({ name: '小红', age: 20 })
// 可以作为函数参数
function logPerson(person) {
console.log(person.name, person.age)
}
logPerson({ name: '小李', age: 22 }) // 小李 22实例5:立即执行函数表达式(IIFE)
// 立即执行函数表达式
(function() {
console.log('立即执行')
})()
// 带参数的IIFE
let result = (function(a, b) {
return a + b
})(1, 2)
console.log(result) // 3
// 箭头函数版本的IIFE
(() => {
console.log('箭头函数立即执行')
})()2.1.6 实际应用场景
场景1:模板字符串中的使用
let name = '小明'
let age = 18
// 模板字符串中只能使用表达式
console.log(`姓名:${name},年龄:${age}`) // 正确
console.log(`姓名:${name.toUpperCase()},年龄:${age + 1}`) // 正确
// 错误示例:不能在模板字符串中使用语句
// console.log(`姓名:${let x = 10}`) // 报错场景2:函数参数
// 函数参数需要是表达式
function add(a, b) {
return a + b
}
console.log(add(1 + 2, 3 * 4)) // 15 (传入表达式)
console.log(add(10, 20)) // 30 (传入变量)
// 错误示例:不能使用语句作为参数
// add(let x = 10, let y = 20) // 报错场景3:数组元素初始化
// 数组元素需要是表达式
let arr = [
1 + 2, // 3
3 * 4, // 12
10 > 5, // true
'hello' + 'world' // 'helloworld'
]
console.log(arr) // [3, 12, true, 'helloworld']
// 错误示例:不能使用语句
// let arr2 = [let x = 10] // 报错场景4:对象属性值
// 对象属性值需要是表达式
let obj = {
sum: 1 + 2, // 3
product: 3 * 4, // 12
isTrue: 10 > 5, // true
greeting: 'hello' + ' world' // 'hello world'
}
console.log(obj) // { sum: 3, product: 12, isTrue: true, greeting: 'hello world' }
// 错误示例:不能使用语句
// let obj2 = { x: let y = 10 } // 报错2.1.7 面试题
// 面试题1:表达式 vs 语句
// 以下哪些是表达式,哪些是语句?
// 表达式:
1 + 2 // ✓ 表达式
x * y // ✓ 表达式
x > 3 // ✓ 表达式
fn() // ✓ 表达式
x = 5 // ✓ 表达式
x >= 18 ? '成年' : '未成年' // ✓ 表达式
{ name: '小明' } // ✓ 表达式(对象字面量)
[1, 2, 3] // ✓ 表达式(数组字面量)
function() {} // ✓ 表达式(函数表达式)
// 语句:
let x = 10 // ✓ 语句
const y = 20 // ✓ 语句
if (x > 0) {} // ✓ 语句
for (let i = 0; i < 10; i++) {} // ✓ 语句
function fn() {} // ✓ 语句(函数声明)
break // ✓ 语句
return // ✓ 语句
throw new Error() // ✓ 语句
// 面试题2:以下代码的输出是什么?
let x = 10
console.log(x = 5) // 5 (输出赋值表达式的值)
console.log(x) // 5 (x已经被赋值为5)
// 面试题3:三元表达式 vs if 语句
let age = 20
let level1 = age >= 18 ? '成年' : '未成年'
console.log(level1) // 成年
let level2
if (age >= 18) {
level2 = '成年'
} else {
level2 = '未成年'
}
console.log(level2) // 成年
// 面试题4:函数声明 vs 函数表达式
// 函数声明会提升
console.log(fn1()) // fn1
function fn1() {
return 'fn1'
}
// 函数表达式不会提升
// console.log(fn2()) // 报错:fn2 is not a function
let fn2 = function() {
return 'fn2'
}
console.log(fn2()) // fn2
// 面试题5:在模板字符串中
let a = 10, b = 20
console.log(`结果:${a + b}`) // 结果:30
console.log(`结果:${a > b}`) // 结果:false
// 错误示例
// console.log(`结果:${let x = a + b}`) // 报错2.1.8 总结与最佳实践
关键要点:
- 表达式产生值:表达式可以被求值,产生一个值
- 语句执行操作:语句用于执行操作,不直接产生值
- 表达式可以嵌套:表达式可以出现在任何需要值的地方
- 语句通常独立:语句通常作为独立的代码行或块执行
最佳实践:
- 需要值的地方用表达式:函数参数、模板字符串、数组元素、对象属性值等
- 需要控制流程用语句:条件判断、循环、声明等
- 理解提升机制:函数声明会提升,函数表达式不会
- 合理使用三元表达式:简单的条件判断可以用三元表达式替代if语句
- 区分对象字面量和块语句:javascript
// 对象字面量(表达式) let obj = { name: '小明' } // 块语句(语句) { let x = 10 console.log(x) }
注意事项:
let、const、var是语句,不能用在表达式中if、for、while等是语句,不能嵌套在表达式中- 函数声明和函数表达式有不同的提升行为
- 对象字面量在表达式中需要注意与块语句的区分
2.2 分支语句
分支语句用于根据条件控制程序的执行流程,JavaScript中主要的分支语句包括 if 语句、switch 语句和三元运算符。
2.2.1 if 单分支语句
if 单分支语句是最简单的条件判断语句,当条件为 true 时执行指定的代码块。
2.2.1.1 基本语法
if (条件) {
// 当条件为 true 时执行的代码
}语法说明:
if:关键字条件:一个表达式,会被转换为布尔值{}:代码块,包含要执行的语句- 当条件为
true时,执行代码块中的代码 - 当条件为
false时,跳过代码块,继续执行后面的代码
2.2.1.2 代码示例
示例1:基础用法
// 判断年龄是否成年
let age = 18
if (age >= 18) {
console.log('已成年')
}
// 判断数字是否为正数
let num = 5
if (num > 0) {
console.log('这是一个正数')
}
// 判断字符串是否为空
let str = 'hello'
if (str.length > 0) {
console.log('字符串不为空')
}示例2:使用比较运算符
// 大于
let score = 85
if (score > 60) {
console.log('成绩及格')
}
// 小于
let temperature = 25
if (temperature < 30) {
console.log('温度适宜')
}
// 大于等于
let age = 18
if (age >= 18) {
console.log('可以考驾照')
}
// 小于等于
let count = 10
if (count <= 100) {
console.log('数量在范围内')
}
// 等于
let day = 1
if (day === 1) {
console.log('今天是周一')
}
// 不等于
let status = 'active'
if (status !== 'inactive') {
console.log('账号状态正常')
}示例3:使用逻辑运算符
// 逻辑与 &&
let age = 20
let hasIdCard = true
if (age >= 18 && hasIdCard) {
console.log('可以办理业务')
}
// 逻辑或 ||
let isMember = true
let hasCoupon = false
if (isMember || hasCoupon) {
console.log('可以享受优惠')
}
// 逻辑非 !
let isRaining = false
if (!isRaining) {
console.log('可以外出')
}示例4:复杂条件
// 多个条件组合
let age = 25
let score = 85
let hasCertificate = true
if (age >= 18 && score >= 60 && hasCertificate) {
console.log('符合录取条件')
}
// 嵌套比较
let x = 5
if (x > 0 && x < 10) {
console.log('x 在 0 到 10 之间')
}
// 使用括号明确优先级
let a = 10
let b = 20
if ((a > 5 && b < 25) || a === 10) {
console.log('条件满足')
}2.2.1.3 执行流程
流程图:
┌─────────┐
│ 开始 │
└────┬────┘
│
▼
┌─────────┐
│ 判断条件 │
└────┬────┘
│
├─── true ───┐
│ ▼
│ ┌──────────┐
│ │ 执行代码 │
│ └────┬─────┘
│ │
│ ▼
│ ┌──────────┐
│ │ 跳出代码 │
│ └────┬─────┘
│ │
│ │
│ │
│ │
└── false ──┤
│
▼
┌──────────┐
│ 继续执行 │
└──────────┘2.2.1.4 实际应用场景
场景1:表单验证
// 用户名验证
let username = 'admin'
if (username.length >= 3) {
console.log('用户名长度符合要求')
}
// 密码验证
let password = '123456'
if (password.length >= 6) {
console.log('密码长度符合要求')
}
// 邮箱验证
let email = 'test@example.com'
if (email.includes('@') && email.includes('.')) {
console.log('邮箱格式正确')
}场景2:权限检查
// 检查管理员权限
let role = 'admin'
if (role === 'admin') {
console.log('拥有管理员权限')
}
// 检查是否登录
let isLoggedIn = true
if (isLoggedIn) {
console.log('用户已登录')
}
// 检查年龄限制
let userAge = 20
if (userAge >= 18) {
console.log('可以访问成人内容')
}场景3:数值处理
// 处理正数
let num = 10
if (num > 0) {
console.log('这是一个正数')
}
// 处理偶数
let number = 8
if (number % 2 === 0) {
console.log('这是一个偶数')
}
// 处理闰年
let year = 2024
if ((year % 4 === 0 && year % 100 !== 0) || year % 400 === 0) {
console.log('是闰年')
}场景4:数据判断
// 判断数组是否有元素
let arr = [1, 2, 3]
if (arr.length > 0) {
console.log('数组不为空')
}
// 判断对象是否有属性
let obj = { name: '小明' }
if (obj.name) {
console.log('对象有 name 属性')
}
// 判断字符串是否包含特定内容
let text = 'Hello World'
if (text.includes('Hello')) {
console.log('字符串包含 Hello')
}2.2.1.5 注意事项
重要注意事项:
- 代码块建议使用花括号:
// 推荐:始终使用花括号
if (age >= 18) {
console.log('已成年')
}
// 不推荐:省略花括号(只能控制一行)
if (age >= 18)
console.log('已成年')
// 危险:省略花括号可能导致bug
if (age >= 18)
console.log('已成年')
console.log('可以投票') // 这行会无条件执行!- 条件中的类型转换:
// 以下条件都会被转换为 true
if (1) { } // true
if ('hello') { } // true
if ([1, 2, 3]) { } // true
if ({ name: '小明' }) { } // true
// 以下条件都会被转换为 false
if (0) { } // false
if ('') { } // false
if (null) { } // false
if (undefined) { } // false
if (NaN) { } // false- 使用严格相等:
// 推荐:使用 ===
let num = '5'
if (num === 5) {
console.log('数字是5')
}
// 不推荐:使用 ==
if (num == 5) {
console.log('数字是5') // 字符串'5'会被转换为数字5
}- 复杂的条件使用括号:
// 推荐:使用括号提高可读性
if ((age >= 18 && hasIdCard) || isVIP) {
console.log('可以进入')
}
// 不推荐:可读性差
if (age >= 18 && hasIdCard || isVIP) {
console.log('可以进入')
}2.2.1.6 面试题
// 面试题1:以下代码输出什么?
let x = 10
if (x > 5) {
console.log('A')
}
if (x > 0) {
console.log('B')
}
if (x > 15) {
console.log('C')
}
// 输出:A B
// 解释:x > 5 为 true,输出 A
// x > 0 为 true,输出 B
// x > 15 为 false,不输出
// 面试题2:以下代码输出什么?
let y = 5
if (y = 10) { // 注意:这里用 = 不是 ==
console.log('y 被赋值了')
}
console.log(y)
// 输出:
// y 被赋值了
// 10
// 解释:y = 10 是赋值表达式,返回 10(true),所以会执行代码块
// 面试题3:以下代码输出什么?
let a = 0
if (a) {
console.log('A')
} else {
console.log('B')
}
// 输出:B
// 解释:0 是假值,条件为 false
// 面试题4:以下代码输出什么?
let str = ''
if (str.length) {
console.log('字符串不为空')
}
console.log(str.length)
// 输出:0
// 解释:空字符串的长度是 0(假值),不会执行 if 块
// 面试题5:以下代码输出什么?
let arr = []
if (arr.length) {
console.log('数组不为空')
}
console.log(arr.length)
// 输出:0
// 解释:空数组的长度是 0(假值),不会执行 if 块
// 面试题6:以下代码输出什么?
let obj = {}
if (obj) {
console.log('对象是true')
}
// 输出:对象是true
// 解释:空对象是真值,条件为 true
// 面试题7:以下代码输出什么?
let num = '10'
if (num === 10) {
console.log('A')
}
if (num == 10) {
console.log('B')
}
// 输出:B
// 解释:num === 10 为 false(类型不同)
// num == 10 为 true(类型转换)2.2.1.7 最佳实践
推荐做法:
- 始终使用花括号:
// 推荐
if (condition) {
// 代码
}- 使用严格相等:
// 推荐
if (age === 18) { }
// 不推荐
if (age == 18) { }- 复杂条件使用括号:
// 推荐
if ((a > 0 && b < 10) || c === 5) { }- 有意义的变量名:
// 推荐
let isAdult = age >= 18
if (isAdult) {
console.log('已成年')
}- 将复杂条件提取为变量:
// 推荐
let isValid = username.length >= 3 && password.length >= 6
if (isValid) {
console.log('验证通过')
}避免做法:
- 避免在条件中赋值:
// 错误:混淆了赋值和比较
if (x = 10) { } // 应该用 ===
// 正确
if (x === 10) { }- 避免过长的条件:
// 不推荐
if (age >= 18 && hasIdCard && hasCertificate && score >= 60) { }
// 推荐
let meetsRequirements = age >= 18 && hasIdCard && hasCertificate && score >= 60
if (meetsRequirements) { }- 避免嵌套过深:
// 不推荐:嵌套过深
if (condition1) {
if (condition2) {
if (condition3) {
// 代码
}
}
}
// 推荐:提前返回
if (!condition1) return
if (!condition2) return
if (!condition3) return
// 代码2.2.1.8 总结
单分支语句要点:
- 语法:
if (条件) { 代码块 } - 执行:条件为
true时执行代码块 - 注意:始终使用花括号,避免缩进错误
- 推荐:使用严格相等
===,复杂条件使用括号 - 最佳实践:将复杂条件提取为变量,提高可读性
2.2.2 if 双分支语句(if...else)
if 双分支语句在单分支的基础上增加了 else 部分,当条件为 false 时执行 else 后面的代码块。
2.2.2.1 基本语法
if (条件) {
// 当条件为 true 时执行的代码
} else {
// 当条件为 false 时执行的代码
}语法说明:
if:关键字,表示条件判断条件:一个表达式,会被转换为布尔值else:关键字,表示否则的情况{}:代码块,包含要执行的语句- 当条件为
true时,执行 if 代码块 - 当条件为
false时,执行 else 代码块 - if 和 else 代码块只会执行其中一个
2.2.2.2 代码示例
示例1:基础用法
// 判断年龄是否成年
let age = 18
if (age >= 18) {
console.log('已成年')
} else {
console.log('未成年')
}
// 判断数字是否为正数
let num = 5
if (num > 0) {
console.log('这是一个正数')
} else {
console.log('这不是一个正数')
}
// 判断字符串是否为空
let str = 'hello'
if (str.length > 0) {
console.log('字符串不为空')
} else {
console.log('字符串为空')
}示例2:成绩判断
// 简单判断及格
let score = 85
if (score >= 60) {
console.log('及格')
} else {
console.log('不及格')
}
// 输出:及格
// 不及格的情况
let score2 = 55
if (score2 >= 60) {
console.log('及格')
} else {
console.log('不及格')
}
// 输出:不及格示例3:登录判断
// 用户登录
let isLoggedIn = true
if (isLoggedIn) {
console.log('欢迎回来!')
} else {
console.log('请先登录')
}
// 输出:欢迎回来!
// 未登录情况
let isLoggedIn2 = false
if (isLoggedIn2) {
console.log('欢迎回来!')
} else {
console.log('请先登录')
}
// 输出:请先登录示例4:数字奇偶判断
// 判断奇偶数
let number = 7
if (number % 2 === 0) {
console.log('偶数')
} else {
console.log('奇数')
}
// 输出:奇数示例5:年龄阶段判断
// 判断是否成年
let age = 16
if (age >= 18) {
console.log('可以考驾照')
} else {
console.log('还未到考驾照年龄')
}
// 输出:还未到考驾照年龄2.2.2.3 执行流程
流程图:
┌─────────┐
│ 开始 │
└────┬────┘
│
▼
┌─────────┐
│ 判断条件 │
└────┬────┘
│
├─── true ───┐
│ ▼
│ ┌──────────┐
│ │ 执行if │
│ │ 代码块 │
│ └────┬─────┘
│ │
│ ▼
│ ┌──────────┐
│ │ 跳出分支 │
│ └────┬─────┘
│ │
│ │
│ ▼
│ ┌──────────┐
│ │ 继续执行 │
│ └────┬─────┘
│ │
│ │
└── false ────┤
│
▼
┌──────────┐
│ 执行else │
│ 代码块 │
└────┬─────┘
│
▼
┌──────────┐
│ 跳出分支 │
└────┬─────┘
│
▼
┌──────────┐
│ 继续执行 │
└──────────┘2.2.2.4 实际应用场景
场景1:表单验证
// 用户名验证
let username = 'admin'
if (username.length >= 3) {
console.log('用户名长度符合要求')
} else {
console.log('用户名长度太短')
}
// 密码验证
let password = '123'
if (password.length >= 6) {
console.log('密码长度符合要求')
} else {
console.log('密码长度不足6位')
}
// 邮箱验证
let email = 'test@example.com'
if (email.includes('@') && email.includes('.')) {
console.log('邮箱格式正确')
} else {
console.log('邮箱格式不正确')
}场景2:权限控制
// 检查管理员权限
let role = 'user'
if (role === 'admin') {
console.log('拥有管理员权限,可以访问管理页面')
} else {
console.log('只有普通用户权限')
}
// 检查是否登录
let isLoggedIn = false
if (isLoggedIn) {
console.log('用户已登录,可以访问个人中心')
} else {
console.log('用户未登录,跳转到登录页面')
}场景3:折扣计算
// 会员折扣
let isMember = true
let price = 100
if (isMember) {
console.log('会员价:' + (price * 0.8))
} else {
console.log('原价:' + price)
}
// 输出:会员价:80
// 非会员情况
let isMember2 = false
let price2 = 100
if (isMember2) {
console.log('会员价:' + (price2 * 0.8))
} else {
console.log('原价:' + price2)
}
// 输出:原价:100场景4:闰年判断
// 判断是否为闰年
let year = 2024
if ((year % 4 === 0 && year % 100 !== 0) || year % 400 === 0) {
console.log(year + '年是闰年')
} else {
console.log(year + '年不是闰年')
}
// 输出:2024年是闰年场景5:温度提示
// 温度提示
let temperature = 35
if (temperature > 30) {
console.log('天气很热,注意防暑')
} else {
console.log('天气适宜')
}
// 输出:天气很热,注意防暑2.2.2.5 双分支 vs 单分支
// 单分支 if:只处理 true 的情况
let age1 = 20
if (age1 >= 18) {
console.log('已成年')
}
// 当 age1 < 18 时,什么都不会输出
// 双分支 if-else:处理 true 和 false 两种情况
let age2 = 20
if (age2 >= 18) {
console.log('已成年')
} else {
console.log('未成年')
}
// 无论 age2 是什么值,都会有输出
// 使用场景:
// - 单分支:只需要处理特定条件满足时的情况
// - 双分支:需要处理条件满足和不满足两种情况2.2.2.6 注意事项
重要注意事项:
- else 不能单独使用:
// 错误:else 不能单独存在
else {
console.log('错误')
}
// 正确:else 必须跟在 if 后面
if (condition) {
console.log('条件成立')
} else {
console.log('条件不成立')
}- else 后面的条件不需要再次判断:
// 不推荐:冗余的判断
let age = 20
if (age >= 18) {
console.log('已成年')
} else if (age < 18) { // 冗余!else 已经隐含了 age < 18
console.log('未成年')
}
// 推荐:简洁的写法
if (age >= 18) {
console.log('已成年')
} else {
console.log('未成年')
}- 代码块建议使用花括号:
// 推荐:始终使用花括号
if (age >= 18) {
console.log('已成年')
} else {
console.log('未成年')
}
// 不推荐:省略花括号
if (age >= 18)
console.log('已成年')
else
console.log('未成年')- if 和 else 只会执行其中一个:
let x = 10
if (x > 5) {
console.log('A')
} else {
console.log('B')
}
// 只会输出 A,不会输出 B2.2.2.7 面试题
// 面试题1:以下代码输出什么?
let a = 10
if (a > 5) {
console.log('A')
} else {
console.log('B')
}
// 输出:A
// 解释:a > 5 为 true,执行 if 代码块
// 面试题2:以下代码输出什么?
let b = 3
if (b > 5) {
console.log('A')
} else {
console.log('B')
}
// 输出:B
// 解释:b > 5 为 false,执行 else 代码块
// 面试题3:以下代码输出什么?
let c = 5
if (c >= 5) {
console.log('A')
} else {
console.log('B')
}
// 输出:A
// 解释:c >= 5 为 true,执行 if 代码块
// 面试题4:以下代码输出什么?
let num = 0
if (num) {
console.log('A')
} else {
console.log('B')
}
// 输出:B
// 解释:0 是假值,条件为 false,执行 else
// 面试题5:以下代码输出什么?
let str = ''
if (str.length) {
console.log('字符串不为空')
} else {
console.log('字符串为空')
}
// 输出:字符串为空
// 解释:空字符串的长度是 0(假值)
// 面试题6:以下代码输出什么?
let arr = []
if (arr.length) {
console.log('数组不为空')
} else {
console.log('数组为空')
}
// 输出:数组为空
// 解释:空数组的长度是 0(假值)
// 面试题7:以下代码输出什么?
let x = 10
if (x === 10) {
console.log('A')
} else {
console.log('B')
}
// 输出:A
// 解释:x === 10 为 true
let y = '10'
if (y === 10) {
console.log('C')
} else {
console.log('D')
}
// 输出:D
// 解释:'10' === 10 为 false(类型不同)
// 面试题8:以下代码输出什么?
let score = 75
if (score >= 90) {
console.log('优秀')
} else {
console.log('非优秀')
}
// 输出:非优秀
// 解释:75 >= 90 为 false2.2.2.8 最佳实践
推荐做法:
- 始终使用花括号:
// 推荐
if (condition) {
// 代码块1
} else {
// 代码块2
}- 使用严格相等:
// 推荐
if (age === 18) {
// 代码
} else {
// 代码
}
// 不推荐
if (age == 18) {
// 代码
} else {
// 代码
}- 有意义的条件表达式:
// 推荐:使用有意义的变量名
let isAdult = age >= 18
if (isAdult) {
console.log('已成年')
} else {
console.log('未成年')
}- 将复杂条件提取为变量:
// 推荐
let isValid = username.length >= 3 && password.length >= 6
if (isValid) {
console.log('验证通过')
} else {
console.log('验证失败')
}- else 块不应该重复判断条件:
// 推荐
if (score >= 60) {
console.log('及格')
} else {
console.log('不及格')
}
// 不推荐
if (score >= 60) {
console.log('及格')
} else if (score < 60) { // 冗余
console.log('不及格')
}避免做法:
- 避免在条件中赋值:
// 错误:混淆了赋值和比较
if (x = 10) {
console.log('A')
} else {
console.log('B')
}
// 正确
if (x === 10) {
console.log('A')
} else {
console.log('B')
}- 避免嵌套过深:
// 不推荐:嵌套过深
if (condition1) {
if (condition2) {
if (condition3) {
// 代码
} else {
// 代码
}
} else {
// 代码
}
}
// 推荐:使用提前返回或多分支
if (!condition1) return
if (!condition2) return
if (!condition3) return
// 代码- 避免冗余的条件:
// 不推荐:else 中重复判断
if (age >= 18) {
console.log('已成年')
} else if (age < 18) { // 冗余
console.log('未成年')
}
// 推荐
if (age >= 18) {
console.log('已成年')
} else {
console.log('未成年')
}2.2.3 if 多分支语句(if...else if...else)
if 多分支语句用于处理多个不同的条件情况,根据不同的条件执行对应的代码块。
2.2.3.1 基本语法
if (条件1) {
// 当条件1为 true 时执行的代码
} else if (条件2) {
// 当条件1为 false 且条件2为 true 时执行的代码
} else if (条件3) {
// 当条件1、条件2都为 false 且条件3为 true 时执行的代码
} else {
// 当所有条件都为 false 时执行的代码(可选)
}语法说明:
if:第一个条件判断else if:额外的条件判断,可以有多个else:所有条件都不满足时的处理(可选)- 执行顺序:从上到下依次判断,执行第一个为 true 的条件对应的代码块
- 执行完成后,跳过剩余的所有条件判断
2.2.3.2 代码示例
示例1:成绩等级判断
// 判断成绩等级
let score = 85
if (score >= 90) {
console.log('优秀')
} else if (score >= 80) {
console.log('良好')
} else if (score >= 70) {
console.log('中等')
} else if (score >= 60) {
console.log('及格')
} else {
console.log('不及格')
}
// 输出:良好
// 其他情况测试
let score2 = 95
if (score2 >= 90) {
console.log('优秀')
} else if (score2 >= 80) {
console.log('良好')
} else if (score2 >= 70) {
console.log('中等')
} else if (score2 >= 60) {
console.log('及格')
} else {
console.log('不及格')
}
// 输出:优秀示例2:年龄段判断
// 判断年龄段
let age = 25
if (age < 6) {
console.log('儿童')
} else if (age < 12) {
console.log('少年')
} else if (age < 18) {
console.log('青少年')
} else if (age < 30) {
console.log('青年')
} else if (age < 50) {
console.log('中年')
} else {
console.log('老年')
}
// 输出:青年示例3:时间问候
// 根据时间输出问候语
let hour = 14
if (hour >= 5 && hour < 12) {
console.log('早上好')
} else if (hour >= 12 && hour < 14) {
console.log('中午好')
} else if (hour >= 14 && hour < 18) {
console.log('下午好')
} else if (hour >= 18 && hour < 22) {
console.log('晚上好')
} else {
console.log('夜深了,注意休息')
}
// 输出:下午好示例4:BMI 指数计算
// 计算并判断BMI指数
let height = 1.75 // 身高(米)
let weight = 65 // 体重(公斤)
let bmi = weight / (height * height)
if (bmi < 18.5) {
console.log('偏瘦,BMI:' + bmi.toFixed(2))
} else if (bmi < 24) {
console.log('正常,BMI:' + bmi.toFixed(2))
} else if (bmi < 28) {
console.log('超重,BMI:' + bmi.toFixed(2))
} else {
console.log('肥胖,BMI:' + bmi.toFixed(2))
}
// 输出:正常,BMI:21.22示例5:月份季节判断
// 根据月份判断季节
let month = 5
if (month >= 3 && month <= 5) {
console.log('春季')
} else if (month >= 6 && month <= 8) {
console.log('夏季')
} else if (month >= 9 && month <= 11) {
console.log('秋季')
} else if (month === 12 || month === 1 || month === 2) {
console.log('冬季')
} else {
console.log('无效的月份')
}
// 输出:春季2.2.3.3 执行流程
流程图:
┌─────────┐
│ 开始 │
└────┬────┘
│
▼
┌─────────┐
│ 条件1? │
└────┬────┘
│
├─── true ───┐
│ ▼
│ ┌──────────┐
│ │ 执行代码 │
│ └────┬─────┘
│ │
│ ▼
│ ┌──────────┐
│ │ 跳出分支 │
│ └────┬─────┘
│ │
│ │
│ ▼
│ ┌──────────┐
│ │ 继续执行 │
│ └────┬─────┘
│ │
│ │
└── false ──────┤
│
▼
┌─────────┐
│ 条件2? │
└────┬────┘
│
├─── true ───┐
│ ▼
│ ┌──────────┐
│ │ 执行代码 │
│ └────┬─────┘
│ │
│ ▼
│ ┌──────────┐
│ │ 跳出分支 │
│ └────┬─────┘
│ │
│ ▼
│ ┌──────────┐
│ │ 继续执行 │
│ └────┬─────┘
│ │
│ │
└── false ──────┤
│
▼
┌─────────┐
│ ... │
│更多条件 │
└────┬────┘
│
▼
┌─────────┐
│ else │
│执行代码 │
└────┬────┘
│
▼
┌──────────┐
│ 继续执行 │
└──────────┘2.2.3.4 实际应用场景
场景1:用户等级判断
// 根据积分判断用户等级
let points = 1250
if (points < 100) {
console.log('青铜会员')
} else if (points < 500) {
console.log('白银会员')
} else if (points < 1000) {
console.log('黄金会员')
} else if (points < 2000) {
console.log('铂金会员')
} else {
console.log('钻石会员')
}
// 输出:铂金会员场景2:折扣计算
// 根据购买数量计算折扣
let quantity = 15
if (quantity < 5) {
console.log('无折扣')
} else if (quantity < 10) {
console.log('9折优惠')
} else if (quantity < 20) {
console.log('8折优惠')
} else if (quantity < 50) {
console.log('7折优惠')
} else {
console.log('6折优惠')
}
// 输出:8折优惠场景3:温度提示
// 根据温度给出提示
let temperature = -5
if (temperature < 0) {
console.log('严寒,注意保暖')
} else if (temperature < 10) {
console.log('寒冷')
} else if (temperature < 20) {
console.log('凉爽')
} else if (temperature < 30) {
console.log('温暖')
} else if (temperature < 35) {
console.log('炎热')
} else {
console.log('酷热,注意防暑')
}
// 输出:严寒,注意保暖场景4:文件大小显示
// 根据文件大小选择合适的单位
let fileSize = 2048576 // 字节
if (fileSize < 1024) {
console.log(fileSize + ' B')
} else if (fileSize < 1024 * 1024) {
console.log((fileSize / 1024).toFixed(2) + ' KB')
} else if (fileSize < 1024 * 1024 * 1024) {
console.log((fileSize / (1024 * 1024)).toFixed(2) + ' MB')
} else {
console.log((fileSize / (1024 * 1024 * 1024)).toFixed(2) + ' GB')
}
// 输出:2.00 MB场景5:交通信号灯
// 模拟交通信号灯控制
let light = 'yellow'
if (light === 'green') {
console.log('绿灯,可以通行')
} else if (light === 'yellow') {
console.log('黄灯,请减速停车')
} else if (light === 'red') {
console.log('红灯,请停车等待')
} else {
console.log('无效的信号灯')
}
// 输出:黄灯,请减速停车2.2.3.5 多分支执行特性
重要特性:只执行第一个为 true 的条件
// 示例1:条件重叠的情况
let score = 85
if (score >= 60) {
console.log('及格')
} else if (score >= 70) {
console.log('中等')
} else if (score >= 80) {
console.log('良好')
} else if (score >= 90) {
console.log('优秀')
}
// 输出:及格
// 解释:score >= 60 为 true,执行后直接跳过后面所有条件
// 注意:这不是正确的写法,应该从高到低判断
// 正确写法:从高到低
let score2 = 85
if (score2 >= 90) {
console.log('优秀')
} else if (score2 >= 80) {
console.log('良好')
} else if (score2 >= 70) {
console.log('中等')
} else if (score2 >= 60) {
console.log('及格')
}
// 输出:良好条件判断的顺序很重要
// 示例2:不同的判断顺序
let age = 18
// 顺序1:从高到低
if (age >= 18) {
console.log('成年人')
} else if (age >= 12) {
console.log('青少年')
} else if (age >= 6) {
console.log('儿童')
}
// 输出:成年人
// 顺序2:从低到高
if (age >= 6) {
console.log('儿童')
} else if (age >= 12) {
console.log('青少年')
} else if (age >= 18) {
console.log('成年人')
}
// 输出:儿童(错误的顺序!)2.2.3.6 多分支 vs 多个独立 if
// 多分支 if-else if-else:只执行一个分支
let x = 10
if (x > 5) {
console.log('A')
} else if (x > 0) {
console.log('B')
} else {
console.log('C')
}
// 输出:A
// 多个独立 if:可能执行多个分支
let y = 10
if (y > 5) {
console.log('A')
}
if (y > 0) {
console.log('B')
}
if (y < 0) {
console.log('C')
}
// 输出:
// A
// B
// 使用场景:
// - 多分支:多个条件互斥,只需要执行一个
// - 多个独立if:多个条件独立,可能需要执行多个2.2.3.7 注意事项
重要注意事项:
- 条件顺序很重要:
// 推荐:从高到低或从特殊到一般
if (score >= 90) {
console.log('优秀')
} else if (score >= 60) {
console.log('及格')
}
// 不推荐:从低到高
if (score >= 60) {
console.log('及格') // 永远只会输出这个
} else if (score >= 90) {
console.log('优秀') // 永远不会执行
}- 避免重复条件:
// 不推荐:条件重复
if (age >= 18) {
console.log('成年')
} else if (age >= 18) { // 冗余
console.log('已成年')
}
// 推荐:简化条件
if (age >= 18) {
console.log('成年')
}- 考虑所有可能的情况:
// 推荐:使用 else 处理默认情况
if (day === 1) {
console.log('周一')
} else if (day === 2) {
console.log('周二')
} else if (day === 3) {
console.log('周三')
} else if (day === 4) {
console.log('周四')
} else if (day === 5) {
console.log('周五')
} else if (day === 6) {
console.log('周六')
} else if (day === 7) {
console.log('周日')
} else {
console.log('无效的日期')
}
// 不推荐:缺少默认情况
if (day === 1) {
console.log('周一')
} else if (day === 2) {
console.log('周二')
}
// 如果 day = 8,什么都不会输出- 条件之间应该互斥:
// 推荐:互斥条件
if (score >= 90) {
console.log('优秀')
} else if (score >= 80) {
console.log('良好')
}
// score >= 80 隐含了 score < 90
// 不推荐:条件不清晰
if (score >= 90) {
console.log('优秀')
} else if (score < 90 && score >= 80) {
console.log('良好') // < 90 是多余的
}- 代码块建议使用花括号:
// 推荐
if (score >= 90) {
console.log('优秀')
} else if (score >= 80) {
console.log('良好')
} else {
console.log('继续努力')
}
// 不推荐
if (score >= 90)
console.log('优秀')
else if (score >= 80)
console.log('良好')
else
console.log('继续努力')2.2.3.8 面试题
// 面试题1:以下代码输出什么?
let x = 10
if (x > 5) {
console.log('A')
} else if (x > 8) {
console.log('B')
} else if (x > 0) {
console.log('C')
}
// 输出:A
// 解释:x > 5 为 true,执行后跳过后面所有条件
// 面试题2:以下代码输出什么?
let score = 85
if (score >= 90) {
console.log('A')
} else if (score >= 80) {
console.log('B')
} else if (score >= 70) {
console.log('C')
} else if (score >= 60) {
console.log('D')
} else {
console.log('E')
}
// 输出:B
// 解释:85 >= 90 为 false,85 >= 80 为 true
// 面试题3:以下代码输出什么?
let y = 5
if (y > 10) {
console.log('A')
} else if (y > 5) {
console.log('B')
} else if (y === 5) {
console.log('C')
} else {
console.log('D')
}
// 输出:C
// 解释:y > 10 为 false,y > 5 为 false,y === 5 为 true
// 面试题4:以下代码输出什么?
let age = 18
if (age >= 6) {
console.log('儿童')
} else if (age >= 12) {
console.log('青少年')
} else if (age >= 18) {
console.log('成年人')
}
// 输出:儿童
// 解释:age >= 6 为 true,执行后跳过后面
// 这就是为什么条件顺序很重要!
// 面试题5:以下代码输出什么?
let num = 0
if (num > 0) {
console.log('正数')
} else if (num < 0) {
console.log('负数')
} else {
console.log('零')
}
// 输出:零
// 解释:0 既不是正数也不是负数
// 面试题6:以下代码输出什么?
let day = 8
if (day === 1) {
console.log('周一')
} else if (day === 2) {
console.log('周二')
} else if (day === 3) {
console.log('周三')
} else if (day === 4) {
console.log('周四')
} else if (day === 5) {
console.log('周五')
} else if (day === 6) {
console.log('周六')
} else if (day === 7) {
console.log('周日')
}
// 无输出
// 解释:没有 else,day = 8 不满足任何条件
// 面试题7:以下代码输出什么?
let a = 10
if (a > 5) {
console.log('1')
}
if (a > 0) {
console.log('2')
}
if (a > 15) {
console.log('3')
}
// 输出:
// 1
// 2
// 解释:这是三个独立的 if,不是多分支
// 面试题8:以下代码输出什么?
let str = 'hello'
if (str === 'hello') {
console.log('A')
} else if (str === 'world') {
console.log('B')
} else if (typeof str === 'string') {
console.log('C')
}
// 输出:A
// 解释:第一个条件就满足了2.2.3.9 最佳实践
推荐做法:
- 条件从特殊到一般,从高到低:
// 推荐:从高到低判断成绩
if (score >= 90) {
console.log('优秀')
} else if (score >= 80) {
console.log('良好')
} else if (score >= 70) {
console.log('中等')
} else if (score >= 60) {
console.log('及格')
} else {
console.log('不及格')
}- 使用 else 处理默认情况:
// 推荐:完整的条件覆盖
if (day === 1) {
console.log('周一')
} else if (day === 2) {
console.log('周二')
} else if (day === 3) {
console.log('周三')
} else if (day === 4) {
console.log('周四')
} else if (day === 5) {
console.log('周五')
} else if (day === 6) {
console.log('周六')
} else if (day === 7) {
console.log('周日')
} else {
console.log('无效的日期')
}- 将复杂条件提取为变量:
// 推荐:提高可读性
let isVIP = points > 1000
let isRegular = points > 100
let isNewUser = points === 0
if (isVIP) {
console.log('VIP用户')
} else if (isRegular) {
console.log('普通用户')
} else if (isNewUser) {
console.log('新用户')
} else {
console.log('其他')
}- 使用严格相等:
// 推荐
if (day === 1) {
console.log('周一')
} else if (day === 2) {
console.log('周二')
}
// 不推荐
if (day == 1) {
console.log('周一')
}- 考虑使用 switch 语句:
// 如果条件是同一个变量的不同值,考虑使用 switch
let day = 1
// 使用 if-else if
if (day === 1) {
console.log('周一')
} else if (day === 2) {
console.log('周二')
} else if (day === 3) {
console.log('周三')
} else if (day === 4) {
console.log('周四')
} else if (day === 5) {
console.log('周五')
} else if (day === 6) {
console.log('周六')
} else if (day === 7) {
console.log('周日')
}
// 使用 switch(更简洁)
switch (day) {
case 1:
console.log('周一')
break
case 2:
console.log('周二')
break
case 3:
case 4:
case 5:
console.log('工作日')
break
case 6:
case 7:
console.log('周末')
break
default:
console.log('无效')
}避免做法:
- 避免过多的 else if:
// 不推荐:条件过多
if (level === 1) {
console.log('等级1')
} else if (level === 2) {
console.log('等级2')
} else if (level === 3) {
console.log('等级3')
} else if (level === 4) {
console.log('等级4')
} else if (level === 5) {
console.log('等级5')
} else if (level === 6) {
console.log('等级6')
} else if (level === 7) {
console.log('等级7')
} else if (level === 8) {
console.log('等级8')
} else if (level === 9) {
console.log('等级9')
} else {
console.log('等级10')
}
// 推荐:使用对象或 switch
const levels = {
1: '等级1',
2: '等级2',
3: '等级3',
4: '等级4',
5: '等级5',
6: '等级6',
7: '等级7',
8: '等级8',
9: '等级9',
10: '等级10'
}
console.log(levels[level] || '无效等级')- 避免条件顺序错误:
// 不推荐:顺序错误
if (age >= 6) {
console.log('儿童')
} else if (age >= 12) {
console.log('青少年')
} else if (age >= 18) {
console.log('成年人')
}
// 推荐:正确顺序
if (age >= 18) {
console.log('成年人')
} else if (age >= 12) {
console.log('青少年')
} else if (age >= 6) {
console.log('儿童')
}- 避免嵌套过深:
// 不推荐:嵌套过深
if (condition1) {
if (condition2) {
if (condition3) {
console.log('A')
} else {
console.log('B')
}
} else {
console.log('C')
}
} else {
console.log('D')
}
// 推荐:扁平化
if (condition1 && condition2 && condition3) {
console.log('A')
} else if (condition1 && condition2) {
console.log('B')
} else if (condition1) {
console.log('C')
} else {
console.log('D')
}
// 或使用提前返回
if (!condition1) {
console.log('D')
return
}
if (!condition2) {
console.log('C')
return
}
if (!condition3) {
console.log('B')
return
}
console.log('A')2.2.3.10 总结
多分支语句要点:
- 语法:
if (条件1) { } else if (条件2) { } else { } - 执行:从上到下依次判断,执行第一个为 true 的条件对应的代码块
- 注意:条件顺序很重要,建议从特殊到一般、从高到低
- 推荐:使用 else 处理默认情况,将复杂条件提取为变量
- 最佳实践:避免过多的 else if,条件互斥,考虑使用 switch 语句
- 关键特性:只执行一个分支,执行后跳过剩余所有条件
2.2.4 三元运算符
三元运算符(条件运算符)是 JavaScript 中唯一需要三个操作数的运算符,它是 if...else 语句的简洁写法。
2.2.4.1 基本语法
条件 ? 表达式1 : 表达式2语法说明:
条件:一个表达式,会被转换为布尔值?:问号,分隔条件和结果表达式1:条件为true时执行的表达式::冒号,分隔两个结果表达式2:条件为false时执行的表达式- 三元运算符是表达式,会返回一个值
2.2.4.2 代码示例
示例1:基础用法
// 判断成年
let age = 18
let result = age >= 18 ? '成年' : '未成年'
console.log(result) // 成年
// 判断奇偶数
let number = 7
let evenOrOdd = number % 2 === 0 ? '偶数' : '奇数'
console.log(evenOrOdd) // 奇数
// 判断正负数
let num = -5
let positiveOrNegative = num > 0 ? '正数' : num < 0 ? '负数' : '零'
console.log(positiveOrNegative) // 负数示例2:成绩判断
// 简单判断及格
let score = 85
let level = score >= 60 ? '及格' : '不及格'
console.log(level) // 及格
// 成绩等级
let score2 = 85
let grade = score2 >= 90 ? '优秀' : score2 >= 80 ? '良好' : score2 >= 70 ? '中等' : score2 >= 60 ? '及格' : '不及格'
console.log(grade) // 良好示例3:默认值设置
// 设置默认值
let name = ''
let displayName = name ? name : '匿名用户'
console.log(displayName) // 匿名用户
let name2 = '小明'
let displayName2 = name2 ? name2 : '匿名用户'
console.log(displayName2) // 小明
// 更简洁的写法(使用 ||)
let name3 = ''
let displayName3 = name3 || '匿名用户'
console.log(displayName3) // 匿名用户示例4:计算折扣
// 计算折扣
let isMember = true
let price = 100
let finalPrice = isMember ? price * 0.8 : price
console.log(finalPrice) // 80
// 非会员情况
let isMember2 = false
let price2 = 100
let finalPrice2 = isMember2 ? price2 * 0.8 : price2
console.log(finalPrice2) // 100示例5:函数参数默认值
// 使用三元运算符设置默认值
function greet(name) {
name = name ? name : '朋友'
console.log('你好,' + name)
}
greet() // 输出:你好,朋友
greet('小明') // 输出:你好,小明
// 使用 || 更简洁
function greet2(name) {
name = name || '朋友'
console.log('你好,' + name)
}
greet2() // 输出:你好,朋友
greet2('小红') // 输出:你好,小红2.2.4.3 执行流程
流程图:
┌─────────┐
│ 开始 │
└────┬────┘
│
▼
┌─────────┐
│ 判断条件 │
└────┬────┘
│
├─── true ───┐
│ ▼
│ ┌──────────┐
│ │ 执行 │
│ │ 表达式1 │
│ └────┬─────┘
│ │
│ ▼
│ ┌──────────┐
│ │ 返回结果1 │
│ └────┬─────┘
│ │
│ │
│ ▼
│ ┌──────────┐
│ │ 继续执行 │
│ └────┬─────┘
│ │
│ │
└── false ────┤
│
▼
┌──────────┐
│ 执行 │
│ 表达式2 │
└────┬─────┘
│
▼
┌──────────┐
│ 返回结果2 │
└────┬─────┘
│
▼
┌──────────┐
│ 继续执行 │
└──────────┘2.2.4.4 三元运算符 vs if 语句
// 三元运算符(表达式)
let age = 20
let level = age >= 18 ? '成年' : '未成年'
console.log(level) // 成年
// if 语句(语句)
let age2 = 20
let level2
if (age2 >= 18) {
level2 = '成年'
} else {
level2 = '未成年'
}
console.log(level2) // 成年
// 三元运算符可以直接用于赋值
let result = age >= 18 ? '成年' : '未成年'
// if 语句不能直接用于赋值
// let result2 = if (age >= 18) { ... } // 报错
// 三元运算符可以嵌套在表达式中
let message = `你今年${age}岁,${age >= 18 ? '是' : '不是'}成年人`
console.log(message) // 你今年20岁,是成年人
// if 语句不能嵌套在表达式中
// let message2 = `你今年${age}岁,${if (age >= 18) '是'}成年人` // 报错使用场景对比:
|| 特性 | 三元运算符 | if 语句 | ||------|-----------|---------| || 类型 | 表达式 | 语句 | || 返回值 | 有返回值 | 无返回值 | || 嵌套 | 可以嵌套在表达式中 | 不能嵌套在表达式中 | || 可读性 | 简单场景可读性好 | 复杂逻辑更清晰 | || 适用场景 | 简单的条件赋值 | 复杂的条件逻辑 | || 代码长度 | 更简洁 | 较长 |
2.2.4.5 嵌套三元运算符
// 嵌套三元运算符
let score = 85
let level = score >= 90 ? '优秀' : score >= 80 ? '良好' : score >= 70 ? '中等' : score >= 60 ? '及格' : '不及格'
console.log(level) // 良好
// 等价的 if 语句
let score2 = 85
let level2
if (score2 >= 90) {
level2 = '优秀'
} else if (score2 >= 80) {
level2 = '良好'
} else if (score2 >= 70) {
level2 = '中等'
} else if (score2 >= 60) {
level2 = '及格'
} else {
level2 = '不及格'
}
console.log(level2) // 良好
// 嵌套结构解析
// score >= 90 ? '优秀' : (score >= 80 ? '良好' : (score >= 70 ? '中等' : (score >= 60 ? '及格' : '不及格')))
// └────────────────────────────────────────────────────────────────────────────────────┘
// 嵌套的三元表达式注意:嵌套过深会影响可读性,建议不超过 2 层嵌套。
2.2.4.6 实际应用场景
场景1:表单默认值
// 设置表单字段的默认值
let username = ''
let defaultValue = username ? username : '请输入用户名'
console.log(defaultValue) // 请输入用户名
let password = '123456'
let defaultPassword = password ? password : '请输入密码'
console.log(defaultPassword) // 123456场景2:动态类名
// 根据状态设置类名
let isActive = true
let className = isActive ? 'active' : 'inactive'
console.log(className) // active
let isDisabled = false
let disabledClass = isDisabled ? 'disabled' : 'enabled'
console.log(disabledClass) // enabled场景3:条件渲染
// 根据条件显示不同的内容
let isLoggedIn = true
let welcomeMessage = isLoggedIn ? '欢迎回来!' : '请先登录'
console.log(welcomeMessage) // 欢迎回来!
let hasPermission = false
let permissionMessage = hasPermission ? '有权限访问' : '没有权限访问'
console.log(permissionMessage) // 没有权限访问场景4:数组/对象操作
// 根据条件选择不同的数组
let type = 'admin'
let menu = type === 'admin' ? ['首页', '管理', '设置'] : ['首页', '个人中心']
console.log(menu) // ['首页', '管理', '设置']
// 根据条件选择不同的对象
let theme = 'dark'
let themeConfig = theme === 'dark' ? { bg: '#000', color: '#fff' } : { bg: '#fff', color: '#000' }
console.log(themeConfig) // { bg: '#000', color: '#fff' }场景5:模板字符串中使用
// 在模板字符串中使用三元运算符
let age = 20
let message = `你今年${age}岁,${age >= 18 ? '是' : '不是'}成年人`
console.log(message) // 你今年20岁,是成年人
// 另一个例子
let score = 85
let result = `你的成绩是${score}分,评级为${score >= 90 ? '优秀' : score >= 80 ? '良好' : '及格'}`
console.log(result) // 你的成绩是85分,评级为良好2.2.4.7 高级用法
多条件判断
// 多条件判断
let day = 1
let dayName = day === 1 ? '周一' : day === 2 ? '周二' : day === 3 ? '周三' : day === 4 ? '周四' : day === 5 ? '周五' : day === 6 ? '周六' : day === 7 ? '周日' : '无效'
console.log(dayName) // 周一
// 更清晰的写法:使用对象映射
const dayMap = {
1: '周一',
2: '周二',
3: '周三',
4: '周四',
5: '周五',
6: '周六',
7: '周日'
}
let day2 = 1
let dayName2 = dayMap[day2] || '无效'
console.log(dayName2) // 周一复杂逻辑简化
// 复杂逻辑
let user = {
name: '小明',
role: 'admin',
isActive: true
}
let canEdit = user.isActive && (user.role === 'admin' || user.role === 'editor') ? '可以编辑' : '不能编辑'
console.log(canEdit) // 可以编辑
// 提取条件,提高可读性
let isActive = user.isActive
let hasEditPermission = user.role === 'admin' || user.role === 'editor'
let canEdit2 = isActive && hasEditPermission ? '可以编辑' : '不能编辑'
console.log(canEdit2) // 可以编辑链式三元运算
// 链式三元运算
let num = 0
let result = num > 0 ? '正数' : num < 0 ? '负数' : '零'
console.log(result) // 零
// 另一个例子
let age = 25
let stage = age < 6 ? '儿童' : age < 12 ? '少年' : age < 18 ? '青少年' : age < 30 ? '青年' : age < 50 ? '中年' : '老年'
console.log(stage) // 青年2.2.4.8 注意事项
重要注意事项:
- 三元运算符是表达式:
// 正确:可以用于赋值
let result = age >= 18 ? '成年' : '未成年'
// 错误:不能单独作为语句使用
// age >= 18 ? '成年' : '未成年' // 不推荐
// 应该:let x = age >= 18 ? '成年' : '未成年'- 避免过度嵌套:
// 不推荐:嵌套过深,可读性差
let level = score >= 90 ? '优秀' : score >= 80 ? '良好' : score >= 70 ? '中等' : score >= 60 ? '及格' : '不及格'
// 推荐:使用 if 语句
let level
if (score >= 90) {
level = '优秀'
} else if (score >= 80) {
level = '良好'
} else if (score >= 70) {
level = '中等'
} else if (score >= 60) {
level = '及格'
} else {
level = '不及格'
}- 表达式1 和表达式2 的类型:
// 注意:两个表达式应该返回相同类型的值
// 不推荐:返回不同类型
let result = age >= 18 ? '成年' : false
// 推荐:返回相同类型
let result = age >= 18 ? '成年' : '未成年'- 优先级问题:
// 注意优先级
let a = 10
// 错误:优先级不明确
// let result = a > 5 ? 1 + 2 : 3 + 4 // 可能有问题
// 推荐:使用括号明确优先级
let result = (a > 5) ? (1 + 2) : (3 + 4)
console.log(result) // 3- 空值处理:
// 注意:null 和 undefined 的处理
let name = null
let displayName = name ? name : '匿名用户'
console.log(displayName) // 匿名用户
let age = undefined
let ageDisplay = age ? age : '年龄未知'
console.log(ageDisplay) // 年龄未知2.2.4.9 面试题
// 面试题1:以下代码输出什么?
let age = 20
let result = age >= 18 ? '成年' : '未成年'
console.log(result) // 成年
// 解释:age >= 18 为 true,返回 '成年'
// 面试题2:以下代码输出什么?
let score = 85
let level = score >= 90 ? '优秀' : score >= 80 ? '良好' : score >= 70 ? '中等' : score >= 60 ? '及格' : '不及格'
console.log(level) // 良好
// 解释:85 >= 90 为 false,85 >= 80 为 true,返回 '良好'
// 面试题3:以下代码输出什么?
let num = 0
let result = num > 0 ? '正数' : num < 0 ? '负数' : '零'
console.log(result) // 零
// 解释:0 > 0 为 false,0 < 0 为 false,返回 '零'
// 面试题4:以下代码输出什么?
let str = ''
let result = str ? str : '空字符串'
console.log(result) // 空字符串
// 解释:空字符串是假值,返回 '空字符串'
// 面试题5:以下代码输出什么?
let arr = []
let result = arr.length ? '非空' : '空'
console.log(result) // 空
// 解释:空数组的长度是 0(假值),返回 '空'
// 面试题6:以下代码输出什么?
let obj = {}
let result = Object.keys(obj).length ? '非空' : '空'
console.log(result) // 空
// 解释:空对象的 keys 数组长度为 0,返回 '空'
// 面试题7:以下代码输出什么?
let a = 10
let b = 20
let result = a > b ? 'A' : a < b ? 'B' : 'C'
console.log(result) // B
// 解释:10 > 20 为 false,10 < 20 为 true,返回 'B'
// 面试题8:以下代码输出什么?
let x = 5
let y = x > 0 ? 'positive' : x < 0 ? 'negative' : 'zero'
console.log(y) // positive
// 解释:5 > 0 为 true,返回 'positive'
// 面试题9:以下代码输出什么?
let day = 1
let dayName = day === 1 ? '周一' : day === 2 ? '周二' : day === 3 ? '周三' : '周四'
console.log(dayName) // 周一
// 解释:day === 1 为 true,返回 '周一'
// 面试题10:以下代码输出什么?
let isMember = true
let hasCoupon = false
let discount = isMember ? 0.8 : hasCoupon ? 0.9 : 1
console.log(discount) // 0.8
// 解释:isMember 为 true,返回 0.8
// 面试题11:以下代码输出什么?
let age = 18
let type = age < 6 ? '儿童' : age < 12 ? '少年' : age < 18 ? '青少年' : '成年'
console.log(type) // 成年
// 解释:age < 6、< 12、< 18 都为 false,返回 '成年'
// 面试题12:以下代码输出什么?
let score = 60
let result = score >= 90 ? 'A' : score >= 80 ? 'B' : score >= 70 ? 'C' : score >= 60 ? 'D' : 'E'
console.log(result) // D
// 解释:60 >= 90、>= 80、>= 70 都为 false,60 >= 60 为 true,返回 'D'2.2.4.10 最佳实践
推荐做法:
- 简单条件使用三元运算符:
// 推荐:简单的二元选择
let result = age >= 18 ? '成年' : '未成年'- 使用括号提高可读性:
// 推荐
let result = (score >= 60) ? '及格' : '不及格'- 复杂逻辑使用 if 语句:
// 推荐:复杂逻辑使用 if
let level
if (score >= 90) {
level = '优秀'
} else if (score >= 80) {
level = '良好'
} else if (score >= 70) {
level = '中等'
} else if (score >= 60) {
level = '及格'
} else {
level = '不及格'
}- 相同类型的返回值:
// 推荐
let result = age >= 18 ? '成年' : '未成年'- 提取复杂条件:
// 推荐
let isAdult = age >= 18
let hasPermission = role === 'admin' || role === 'editor'
let canAccess = isAdult && hasPermission ? '可以访问' : '不能访问'- 使用对象映射替代嵌套:
// 推荐
const dayMap = {
1: '周一',
2: '周二',
3: '周三',
4: '周四',
5: '周五',
6: '周六',
7: '周日'
}
let dayName = dayMap[day] || '无效'
// 不推荐:嵌套三元
let dayName = day === 1 ? '周一' : day === 2 ? '周二' : day === 3 ? '周三' : '无效'避免做法:
- 避免过度嵌套:
// 不推荐
let level = score >= 90 ? '优秀' : score >= 80 ? '良好' : score >= 70 ? '中等' : score >= 60 ? '及格' : '不及格'- 避免在不同类型之间切换:
// 不推荐:返回值类型不一致
let result = age >= 18 ? '成年' : false
// 推荐
let result = age >= 18 ? '成年' : '未成年'- 避免在复杂逻辑中使用三元:
// 不推荐
let result = condition1 && condition2 ? option1 : condition3 || condition4 ? option2 : option3
// 推荐:使用 if 语句
let result
if (condition1 && condition2) {
result = option1
} else if (condition3 || condition4) {
result = option2
} else {
result = option3
}- 避免三元运算符单独作为语句:
// 不推荐
// age >= 18 ? console.log('成年') : console.log('未成年')
// 推荐
if (age >= 18) {
console.log('成年')
} else {
console.log('未成年')
}2.2.4.11 三元运算符与 switch 对比
// 使用三元运算符(适合简单的值判断)
let day = 1
let dayName = day === 1 ? '周一' : day === 2 ? '周二' : day === 3 ? '周三' : '其他'
// 使用 switch(适合多个值的判断)
let day = 1
let dayName
switch (day) {
case 1:
dayName = '周一'
break
case 2:
dayName = '周二'
break
case 3:
dayName = '周三'
break
default:
dayName = '其他'
}
// 使用对象映射(最简洁)
const dayMap = {
1: '周一',
2: '周二',
3: '周三'
}
let dayName = dayMap[day] || '其他'对比表格:
|| 特性 | 三元运算符 | switch 语句 | 对象映射 | ||------|-----------|-------------|---------| || 简洁性 | 中等 | 较长 | 最简洁 | || 可读性 | 简单场景好 | 清晰 | 非常清晰 | || 性能 | 快 | 快 | 最快 | || 适用场景 | 简单二元选择 | 多值判断 | 已知键值对 | || 灵活性 | 高 | 中 | 中 | || 默认值 | 较复杂 | 简单(default) | 简单(||) |
2.2.4.12 总结
三元运算符要点:
- 语法:
条件 ? 表达式1 : 表达式2 - 特性:是表达式,会返回值
- 执行:条件为
true返回表达式1,为false返回表达式2 - 优势:简洁,可以嵌套在表达式中
- 注意:避免过度嵌套,复杂逻辑使用 if 语句
- 最佳实践:简单条件使用三元,复杂条件使用 if,多值判断使用对象映射
- 优先级:注意运算符优先级,必要时使用括号
2.2.5 switch 语句
switch 语句用于基于不同的条件来执行不同的代码块。它特别适合处理一个变量的多个可能值的情况。
2.2.5.1 基本语法
switch (表达式) {
case 值1:
// 当表达式的值等于值1时执行的代码
break
case 值2:
// 当表达式的值等于值2时执行的代码
break
case 值3:
// 当表达式的值等于值3时执行的代码
break
default:
// 当表达式的值不等于任何case时执行的代码(可选)
}语法说明:
switch:关键字,开始 switch 语句表达式:要判断的表达式case:关键字,表示一个可能的情况值:case 后面的值(可以是数字、字符串等)break:关键字,跳出 switch 语句(可选,但推荐使用)default:默认情况(可选)- 执行顺序:从上到下依次匹配,执行第一个匹配的 case 代码块
2.2.5.2 代码示例
示例1:基础用法
// 判断星期几
let day = 3
switch (day) {
case 1:
console.log('周一')
break
case 2:
console.log('周二')
break
case 3:
console.log('周三')
break
case 4:
console.log('周四')
break
case 5:
console.log('周五')
break
case 6:
console.log('周六')
break
case 7:
console.log('周日')
break
}
// 输出:周三
// 字符串值的情况
let fruit = 'apple'
switch (fruit) {
case 'apple':
console.log('苹果')
break
case 'banana':
console.log('香蕉')
break
case 'orange':
console.log('橙子')
break
}
// 输出:苹果示例2:使用 default
// 使用 default 处理默认情况
let day = 8
switch (day) {
case 1:
console.log('周一')
break
case 2:
console.log('周二')
break
case 3:
console.log('周三')
break
case 4:
console.log('周四')
break
case 5:
console.log('周五')
break
case 6:
console.log('周六')
break
case 7:
console.log('周日')
break
default:
console.log('无效的日期')
}
// 输出:无效的日期示例3:成绩等级判断
// 判断成绩等级
let score = 85
let level
switch (true) {
case score >= 90:
level = '优秀'
break
case score >= 80:
level = '良好'
break
case score >= 70:
level = '中等'
break
case score >= 60:
level = '及格'
break
default:
level = '不及格'
}
console.log(level) // 良好
// 另一种方式:先计算等级
let score2 = 95
let grade = Math.floor(score2 / 10)
switch (grade) {
case 10:
case 9:
console.log('优秀')
break
case 8:
console.log('良好')
break
case 7:
console.log('中等')
break
case 6:
console.log('及格')
break
default:
console.log('不及格')
}
// 输出:优秀示例4:月份季节判断
// 根据月份判断季节
let month = 5
switch (month) {
case 3:
case 4:
case 5:
console.log('春季')
break
case 6:
case 7:
case 8:
console.log('夏季')
break
case 9:
case 10:
case 11:
console.log('秋季')
break
case 12:
case 1:
case 2:
console.log('冬季')
break
default:
console.log('无效的月份')
}
// 输出:春季示例5:计算器
// 简单计算器
let operator = '+'
let a = 10
let b = 5
let result
switch (operator) {
case '+':
result = a + b
break
case '-':
result = a - b
break
case '*':
result = a * b
break
case '/':
result = a / b
break
case '%':
result = a % b
break
default:
console.log('无效的运算符')
}
console.log(result) // 15
// 另一个例子
let operator2 = '*'
switch (operator2) {
case '+':
result = a + b
break
case '-':
result = a - b
break
case '*':
result = a * b
break
case '/':
result = a / b
break
default:
console.log('无效的运算符')
}
console.log(result) // 502.2.5.3 执行流程
流程图:
┌─────────┐
│ 开始 │
└────┬────┘
│
▼
┌─────────┐
│ 计算表达式│
│ 的值 │
└────┬────┘
│
▼
┌─────────┐
│ case 值1 │
│ 匹配? │
└────┬────┘
│
├─── 匹配 ───┐
│ ▼
│ ┌──────────┐
│ │ 执行代码 │
│ └────┬─────┘
│ │
│ ▼
│ ┌──────────┐
│ │ 遇到break? │
│ └────┬─────┘
│ │
│ ┌─────┴─────┐
│ │ │
│ 是│ 否│
│ │ │
│ ▼ ▼
│ ┌──────┐ ┌──────────┐
│ │跳出 │ │ 继续下个 │
│ │switch│ │ case │
│ └──┬───┘ └────┬─────┘
│ │ │
│ │ │
│ │ ▼
│ │ ┌──────────┐
│ │ │ case 值2 │
│ │ │ 匹配? │
│ │ └────┬─────┘
│ │ │
│ │ ......(继续匹配)
│ │ │
│ │ ▼
│ │ ┌──────────┐
│ │ │ default │
│ │ │ 执行代码 │
│ │ └────┬─────┘
│ │ │
│ │ ▼
│ │ ┌──────────┐
│ └─────┤ 继续执行 │
│ └──────────┘
│
└── 不匹配 ──┤
│
▼
┌──────────┐
│ case 值2 │
│ 匹配? │
└────┬─────┘
│
│
......(继续匹配)2.2.5.4 break 语句的重要性
break 的作用:跳出 switch 语句,防止代码继续向下执行。
// 示例1:使用 break
let day = 2
switch (day) {
case 1:
console.log('周一')
break
case 2:
console.log('周二')
break
case 3:
console.log('周三')
break
}
// 输出:周二
// 示例2:不使用 break(case 穿透)
let day2 = 2
switch (day2) {
case 1:
console.log('周一')
case 2:
console.log('周二')
case 3:
console.log('周三')
}
// 输出:
// 周二
// 周三
// 解释:case 2 匹配后,因为没有 break,继续执行 case 3 的代码
// 示例3:有意利用 case 穿透
let month = 3
switch (month) {
case 3:
case 4:
case 5:
console.log('春季')
break
case 6:
case 7:
case 8:
console.log('夏季')
break
}
// 输出:春季
// 解释:month = 3 匹配 case 3,利用穿透继续执行,直到遇到 break2.2.5.5 实际应用场景
场景1:用户角色权限
// 根据用户角色显示不同的菜单
let role = 'admin'
switch (role) {
case 'admin':
console.log('用户管理')
console.log('系统设置')
case 'editor':
console.log('文章编辑')
console.log('内容审核')
case 'viewer':
console.log('浏览内容')
break
default:
console.log('访客')
}
// 输出:
// 用户管理
// 系统设置
// 文章编辑
// 内容审核
// 浏览内容场景2:交通信号灯
// 模拟交通信号灯控制
let light = 'green'
switch (light) {
case 'green':
console.log('绿灯,可以通行')
break
case 'yellow':
console.log('黄灯,请减速停车')
break
case 'red':
console.log('红灯,请停车等待')
break
default:
console.log('无效的信号灯')
}
// 输出:绿灯,可以通行场景3:网络状态
// 根据网络状态显示提示
let networkStatus = 'offline'
switch (networkStatus) {
case 'online':
console.log('网络已连接')
break
case 'offline':
console.log('网络已断开')
break
case 'loading':
console.log('正在连接...')
break
default:
console.log('未知状态')
}
// 输出:网络已断开场景4:游戏状态
// 游戏状态管理
let gameState = 'playing'
switch (gameState) {
case 'menu':
console.log('显示主菜单')
break
case 'playing':
console.log('游戏进行中')
break
case 'paused':
console.log('游戏暂停')
break
case 'gameover':
console.log('游戏结束')
break
default:
console.log('未知状态')
}
// 输出:游戏进行中场景5:HTTP 状态码
// 根据状态码返回不同的提示
let statusCode = 404
switch (statusCode) {
case 200:
console.log('成功')
break
case 201:
console.log('创建成功')
break
case 400:
console.log('请求错误')
break
case 401:
console.log('未授权')
break
case 403:
console.log('禁止访问')
break
case 404:
console.log('未找到资源')
break
case 500:
console.log('服务器错误')
break
default:
console.log('未知状态码')
}
// 输出:未找到资源2.2.5.6 switch vs if-else vs 三元运算符
// 场景:判断星期几
let day = 3
// 使用 switch
switch (day) {
case 1:
console.log('周一')
break
case 2:
console.log('周二')
break
case 3:
console.log('周三')
break
case 4:
console.log('周四')
break
case 5:
console.log('周五')
break
case 6:
console.log('周六')
break
case 7:
console.log('周日')
break
}
// 使用 if-else
if (day === 1) {
console.log('周一')
} else if (day === 2) {
console.log('周二')
} else if (day === 3) {
console.log('周三')
} else if (day === 4) {
console.log('周四')
} else if (day === 5) {
console.log('周五')
} else if (day === 6) {
console.log('周六')
} else if (day === 7) {
console.log('周日')
}
// 使用对象映射(最简洁)
const dayMap = {
1: '周一',
2: '周二',
3: '周三',
4: '周四',
5: '周五',
6: '周六',
7: '周日'
}
console.log(dayMap[day])
// 场景:简单的二元判断
let age = 18
// 使用三元运算符(最简洁)
let level = age >= 18 ? '成年' : '未成年'
console.log(level)
// 使用 if-else
if (age >= 18) {
console.log('成年')
} else {
console.log('未成年')
}
// 使用 switch(不推荐用于这种情况)
switch (true) {
case age >= 18:
console.log('成年')
break
default:
console.log('未成年')
}对比表格:
|| 特性 | switch | if-else | 三元运算符 | ||------|--------|---------|-----------| || 适用场景 | 同一变量的多个值 | 复杂条件判断 | 简单二元选择 | || 可读性 | 多值判断时清晰 | 复杂逻辑清晰 | 简单场景清晰 | || 代码长度 | 较长 | 较长 | 最短 | || 灵活性 | 中等 | 最高 | 较低 | || 性能 | 快 | 快 | 快 | || 嵌套 | 不支持嵌套 | 支持嵌套 | 支持嵌套 |
2.2.5.7 switch 的高级用法
多个 case 共用同一代码
// 多个月份对应同一季节
let month = 3
switch (month) {
case 3:
case 4:
case 5:
console.log('春季')
break
case 6:
case 7:
case 8:
console.log('夏季')
break
case 9:
case 10:
case 11:
console.log('秋季')
break
case 12:
case 1:
case 2:
console.log('冬季')
break
default:
console.log('无效的月份')
}
// 输出:春季表达式作为 case 值
// 使用表达式
let num = 10
switch (true) {
case num > 0:
console.log('正数')
break
case num < 0:
console.log('负数')
break
default:
console.log('零')
}
// 输出:正数
// 另一个例子
let score = 85
switch (true) {
case score >= 90:
console.log('优秀')
break
case score >= 80:
console.log('良好')
break
case score >= 70:
console.log('中等')
break
case score >= 60:
console.log('及格')
break
default:
console.log('不及格')
}
// 输出:良好字符串匹配
// 字符串匹配
let fruit = 'APPLE'
switch (fruit.toLowerCase()) {
case 'apple':
console.log('苹果')
break
case 'banana':
console.log('香蕉')
break
case 'orange':
console.log('橙子')
break
default:
console.log('未知水果')
}
// 输出:苹果2.2.5.8 注意事项
重要注意事项:
- 记得使用 break:
// 推荐:每个 case 都使用 break
switch (day) {
case 1:
console.log('周一')
break
case 2:
console.log('周二')
break
}
// 不推荐:忘记 break(除非是有意的 case 穿透)
switch (day) {
case 1:
console.log('周一')
// 忘记 break
case 2:
console.log('周二')
// 忘记 break
}- case 的值是严格相等:
// switch 使用严格相等 === 比较
let x = '10'
switch (x) {
case 10:
console.log('A') // 不会执行
break
case '10':
console.log('B') // 会执行
break
}
// 输出:B
// 解释:'10' === 10 为 false,'10' === '10' 为 true- default 可选:
// 不使用 default
switch (day) {
case 1:
console.log('周一')
break
case 2:
console.log('周二')
break
}
// 如果 day = 3,什么都不会执行
// 使用 default
switch (day) {
case 1:
console.log('周一')
break
case 2:
console.log('周二')
break
default:
console.log('其他')
}- case 值不能重复:
// 错误:case 值重复
switch (day) {
case 1:
console.log('周一')
break
case 1: // 重复,会报错
console.log('一月')
break
}- switch 表达式可以是任何类型:
// 数字
switch (num) { }
// 字符串
switch (str) { }
// 布尔值
switch (bool) { }
// 对象(引用比较)
switch (obj) { } // 注意:对象使用引用比较2.2.5.9 面试题
// 面试题1:以下代码输出什么?
let day = 3
switch (day) {
case 1:
console.log('A')
case 2:
console.log('B')
case 3:
console.log('C')
case 4:
console.log('D')
break
case 5:
console.log('E')
}
// 输出:C D
// 解释:day = 3 匹配 case 3,输出 C,由于没有 break,继续执行 case 4,输出 D,遇到 break 结束
// 面试题2:以下代码输出什么?
let x = '10'
switch (x) {
case 10:
console.log('A')
break
case '10':
console.log('B')
break
case 5 + 5:
console.log('C')
break
}
// 输出:B
// 解释:x = '10',严格匹配 '10' === '10' 为 true
// case 10:'10' === 10 为 false(类型不同)
// case 5 + 5:'10' === 10 为 false(5+5=10,数字10)
// 面试题3:以下代码输出什么?
let num = 2
switch (num) {
case 1:
console.log('A')
break
case 2:
console.log('B')
case 3:
console.log('C')
case 4:
console.log('D')
break
default:
console.log('E')
}
// 输出:B C D
// 解释:匹配 case 2,输出 B,继续执行 case 3、case 4
// 面试题4:以下代码输出什么?
let y = 0
switch (y) {
case 1:
console.log('A')
break
case 2:
console.log('B')
break
case 3:
console.log('C')
break
}
// 无输出
// 解释:没有 default,y = 0 不匹配任何 case
// 面试题5:以下代码输出什么?
let a = 5
switch (a) {
case 3:
console.log('A')
break
case 4:
console.log('B')
break
default:
console.log('C')
break
case 5:
console.log('D')
break
}
// 输出:D
// 解释:a = 5 匹配 case 5,输出 D
// 面试题6:以下代码输出什么?
let score = 75
switch (true) {
case score >= 90:
console.log('A')
break
case score >= 80:
console.log('B')
break
case score >= 70:
console.log('C')
break
case score >= 60:
console.log('D')
break
default:
console.log('E')
}
// 输出:C
// 解释:score >= 90、>= 80 为 false,score >= 70 为 true
// 面试题7:以下代码输出什么?
let month = 5
switch (month) {
case 3:
case 4:
case 5:
console.log('春季')
break
case 6:
case 7:
case 8:
console.log('夏季')
break
default:
console.log('其他季节')
}
// 输出:春季
// 解释:利用 case 穿透,3、4、5 都执行相同的代码
// 面试题8:以下代码输出什么?
let str = 'hello'
switch (str) {
case 'hello':
console.log('A')
break
case 'Hello':
console.log('B')
break
default:
console.log('C')
}
// 输出:A
// 解释:'hello' 严格匹配 'hello',注意大小写
// 面试题9:以下代码输出什么?
let num1 = 1
let num2 = '1'
switch (num1) {
case num2:
console.log('A')
break
case 1:
console.log('B')
break
default:
console.log('C')
}
// 输出:C
// 解释:num1 = 1(数字),num2 = '1'(字符串)
// case num2:1 === '1' 为 false
// case 1:1 === 1 为 true,输出 B
// 等等,让我重新分析
// switch (num1) = switch (1)
// case num2 = case '1':1 === '1' 为 false
// case 1 = case 1:1 === 1 为 true,输出 B
// 所以输出应该是 B
// 面试题10:以下代码输出什么?
let x = 0
switch (x) {
case 0:
console.log('A')
case false:
console.log('B')
break
case null:
console.log('C')
}
// 输出:A B
// 解释:x = 0 匹配 case 0,输出 A,由于没有 break,继续执行
// case false:0 === false 为 false
// 但这里有个问题,case 后面的值是 false,不是条件判断
// 实际上会执行 case false 这一行,但不会进入
// 让我重新理解:switch 使用严格相等 ===
// 0 === 0 为 true,执行 console.log('A')
// 没有遇到 break,继续向下执行 console.log('B')
// 遇到 break,结束
// 所以输出:A B2.2.5.10 最佳实践
推荐做法:
- 每个 case 都使用 break:
// 推荐
switch (day) {
case 1:
console.log('周一')
break
case 2:
console.log('周二')
break
default:
console.log('其他')
}- 使用 default 处理默认情况:
// 推荐
switch (status) {
case 'success':
console.log('成功')
break
case 'error':
console.log('错误')
break
default:
console.log('未知状态')
}- 有意使用 case 穿透时添加注释:
// 推荐
switch (month) {
case 3:
case 4:
case 5:
console.log('春季') // 3-5月为春季
break
}- 复杂条件考虑使用 if-else:
// 复杂条件更适合用 if-else
if (age >= 18 && hasIdCard && score >= 60) {
console.log('符合条件')
} else {
console.log('不符合条件')
}
// 不推荐用 switch 处理这种情况- 简单多值判断使用对象映射:
// 推荐:简单映射使用对象
const dayMap = {
1: '周一',
2: '周二',
3: '周三',
4: '周四',
5: '周五',
6: '周六',
7: '周日'
}
console.log(dayMap[day] || '无效')避免做法:
- 忘记 break(除非有意为之):
// 不推荐
switch (day) {
case 1:
console.log('周一')
// 忘记 break
case 2:
console.log('周二')
}- case 值重复:
// 错误:重复的 case
switch (day) {
case 1:
console.log('周一')
break
case 1: // 重复,报错
console.log('一月')
break
}- 过度使用 switch:
// 不推荐:简单二元判断
switch (true) {
case age >= 18:
console.log('成年')
break
default:
console.log('未成年')
}
// 推荐:使用三元运算符
let level = age >= 18 ? '成年' : '未成年'- switch 中有大量代码:
// 不推荐:每个 case 有大量代码
switch (type) {
case 'A':
// 几百行代码...
break
case 'B':
// 几百行代码...
break
}
// 推荐:提取为函数
switch (type) {
case 'A':
handleTypeA()
break
case 'B':
handleTypeB()
break
}2.2.5.11 switch 语句的适用场景
适合使用 switch 的场景:
- 同一变量的多个离散值:
switch (day) {
case 1: case 2: case 3: case 4: case 5:
console.log('工作日')
break
case 6: case 7:
console.log('周末')
break
}- 多个 case 共用同一逻辑:
switch (month) {
case 3: case 4: case 5:
console.log('春季')
break
case 6: case 7: case 8:
console.log('夏季')
break
}- 状态机模式:
switch (state) {
case 'idle':
console.log('空闲')
break
case 'running':
console.log('运行中')
break
case 'paused':
console.log('已暂停')
break
case 'stopped':
console.log('已停止')
break
}不适合使用 switch 的场景:
- 复杂的条件判断:
// 不适合:复杂条件
switch (true) {
case age >= 18 && hasIdCard:
console.log('可以办理')
break
case age < 18:
console.log('未成年')
break
}
// 推荐:使用 if-else
if (age >= 18 && hasIdCard) {
console.log('可以办理')
} else if (age < 18) {
console.log('未成年')
}- 简单的二元判断:
// 不适合:简单二元判断
switch (true) {
case age >= 18:
console.log('成年')
break
default:
console.log('未成年')
}
// 推荐:使用三元运算符
let level = age >= 18 ? '成年' : '未成年'- 简单的键值映射:
// 不适合:简单映射
switch (code) {
case 1:
console.log('成功')
break
case 2:
console.log('失败')
break
case 3:
console.log('进行中')
break
}
// 推荐:使用对象映射
const statusMap = {
1: '成功',
2: '失败',
3: '进行中'
}
console.log(statusMap[code])2.2.5.12 总结
switch 语句要点:
- 语法:
switch (表达式) { case 值: break; default: } - 匹配:使用严格相等
===进行匹配 - break:每个 case 推荐使用 break,防止 case 穿透
- 穿透:有意不使用 break 可以实现多个 case 共用同一代码
- default:可选,处理默认情况
- 适用场景:同一变量的多个离散值判断
- 优势:多值判断时代码清晰,可读性好
- 注意:记得使用 break,case 值不能重复
2.3 循环语句
在开发过程中,调试是发现和修复代码错误的重要技能。JavaScript 提供了多种调试方式,其中最常用的是断点调试和 console 方法。
2.3.1 断点调试
断点调试是一种强大的调试技术,它允许开发者在代码的特定位置暂停执行,以便检查变量值、执行流程和程序状态。
2.3.1.1 什么是断点调试
断点调试是通过在代码中设置"断点"(breakpoint),使程序执行到断点处暂停,从而可以逐步检查代码执行过程的一种调试方法。
核心概念:
- 断点:代码执行暂停的位置
- 调试器:用于控制代码执行的工具(浏览器开发者工具)
- 断点命中:程序执行到断点位置
- 单步执行:一次执行一行代码
2.3.1.2 浏览器开发者工具
现代浏览器都内置了强大的开发者工具,其中包含断点调试功能。
如何打开开发者工具
方法1:快捷键
- Windows/Linux:
F12或Ctrl + Shift + I - Mac:
Cmd + Option + I
方法2:右键菜单
- 在网页上右键点击
- 选择"检查"或"审查元素"
方法3:浏览器菜单
- Chrome:菜单 → 更多工具 → 开发者工具
- Firefox:菜单 → Web 开发者 → Web 控制台
2.3.1.3 Sources 面板介绍
打开开发者工具后,切换到 Sources(资源)面板,这是断点调试的主要界面。
Sources 面板主要区域:
┌─────────────────────────────────────────────────┐
│ 页面文件导航区 (Navigator) │
│ ├─ localhost:5500 │
│ │ ├─ index.html │
│ │ ├─ style.css │
│ │ └─ script.js ◄ 点击打开文件 │
├──────────────────┬──────────────────────────────┤
│ 代码编辑区 │ 调试控制区 │
│ (Editor Pane) │ (Debug Pane) │
│ │ │
│ let num = 10 │ Watch: │
│ debugger; │ num: 10 │
│ num += 5 │ │
│ console.log() │ Call Stack: │
│ │ (anonymous) │
│ │ │
│ │ Breakpoints: │
│ │ script.js:3 │
│ │ │
│ │ Scope: │
│ │ Local │
│ │ num: 10 │
└──────────────────┴──────────────────────────────┘
各区域功能说明:
| 区域 | 名称 | 功能 |
|---|---|---|
| Navigator | 文件导航 | 浏览和打开项目文件 |
| Editor Pane | 代码编辑器 | 显示和编辑代码,设置断点 |
| Watch | 监视面板 | 添加要监视的变量或表达式 |
| Call Stack | 调用栈 | 显示函数调用链 |
| Breakpoints | 断点列表 | 显示所有设置的断点 |
| Scope | 作用域 | 显示当前作用域的变量 |
2.3.1.4 设置断点的方法
方法1:在代码编辑器中点击行号
- 在 Sources 面板中打开要调试的文件
- 在代码行的行号上点击,会出现一个蓝色圆点(断点)
- 再次点击可以取消断点
// 示例代码:在行号 4 点击设置断点
let num = 10
let sum = 0
for (let i = 1; i <= 5; i++) {
debugger; // ← 可以在这行设置断点
sum += i
console.log('sum:', sum)
}方法2:在代码中使用 debugger 语句
debugger 语句会在代码执行到该位置时自动暂停,就像设置了一个断点。
function calculate(a, b) {
let result = a + b
debugger; // 程序会在这里暂停,打开调试器
result = result * 2
return result
}
calculate(3, 5) // 执行时会在 debugger 处暂停注意:
debugger语句只在浏览器开发者工具打开时生效- 如果没有打开调试器,
debugger语句会被忽略 - 可以在发布前的代码中移除所有
debugger语句
2.3.1.5 断点调试的基本操作
1. 触发断点
断点设置后,需要执行代码才能触发:
// 示例:点击按钮触发
<button onclick="handleClick()">点击调试</button>
<script>
function handleClick() {
let count = 0
count++ // 在这里设置断点
console.log(count)
}
</script>触发方式:
- 刷新页面(如果代码在页面加载时执行)
- 触发事件(如点击按钮)
- 调用函数(如在控制台输入函数名并执行)
2. 调试控制按钮
当代码在断点处暂停时,Sources 面板顶部会出现调试控制按钮:
| 按钮 | 名称 | 快捷键 | 功能 |
|---|---|---|---|
| ▶️ | 继续 | F8 / Ctrl + F8 | 继续执行,直到遇到下一个断点或程序结束 |
| ⏭️ | 单步跳过 | F10 | 执行当前行,不进入函数内部 |
| ⬇️ | 单步进入 | F11 | 执行当前行,如果遇到函数则进入函数内部 |
| ⬆️ | 单步跳出 | Shift + F11 | 执行当前函数剩余代码,返回调用处 |
| ⏸️ | 暂停 | - | 暂停脚本执行 |
| 🔄 | 重启 | Ctrl + Shift + F5 | 重新启动调试 |
操作示例:
function multiply(a, b) {
return a * b
}
function calculate() {
let x = 5
let y = 3
let z = multiply(x, y) // 断点设置在这行
console.log(z)
return z
}
calculate()
// 调试流程:
// 1. 点击 F9 在 let z = multiply(x, y) 处设置断点
// 2. 刷新页面,程序在断点处暂停
// 3. 按 F10 单步跳过:执行当前行,z = 15
// 4. 按 F10 单步跳过:执行 console.log(z)
// 5. 按 F10 单步跳过:执行 return z
// 6. 程序结束2.3.1.6 查看和修改变量
查看变量
在断点处暂停时,可以通过多种方式查看变量的值:
方法1:鼠标悬停
将鼠标悬停在代码中的变量上,会显示变量的值:
let num = 10
let result = num + 5
// 鼠标悬停在 num 上,会显示:num: 10
// 鼠标悬停在 result 上,会显示:result: 15方法2:Scope(作用域)面板
Scope 面板会自动显示当前作用域的所有变量:
Scope:
├─ Local
│ ├─ num: 10
│ └─ result: 15
├─ Closure
└─ Global
└─ window: Window方法3:Watch(监视)面板
手动添加要监视的变量或表达式:
- 在 Watch 面板点击
+号 - 输入变量名或表达式
- 可以监视多个变量
let a = 5
let b = 3
let c = a + b
let d = a * b
// Watch 面板可以监视:
// a → 5
// b → 3
// a + b → 8
// a * b → 15
// a > b → true
// a % b → 2方法4:Console(控制台)面板
在控制台中输入变量名查看值:
// 在断点暂停时,在控制台输入:
console.log(num) // 查看变量
num + 5 // 计算表达式
typeof num // 查看类型修改变量
在调试时可以修改变量的值,用于测试不同场景:
方法1:在 Scope 面板中直接修改
- 在 Scope 面板中找到变量
- 双击变量值
- 修改值并按回车
方法2:在控制台中赋值
// 在断点暂停时,在控制台输入:
num = 100 // 修改变量值
str = "hello" // 修改变量值// 实际应用示例
let age = 18
if (age >= 18) {
console.log('已成年')
}
// 调试时,在断点处修改 age = 16
// 然后继续执行,会看到不同的输出2.3.1.7 调用栈(Call Stack)
调用栈显示了函数的调用链,帮助理解程序的执行流程。
调用栈示例:
function funcA() {
funcB() // 断点在这里
}
function funcB() {
funcC()
}
function funcC() {
debugger // 暂停在这里
}
funcA()
// Call Stack 显示:
// funcC (script.js:10)
// funcB (script.js:6)
// funcA (script.js:2)
// (anonymous) (script.js:13)调用栈的作用:
- 查看函数调用顺序:了解代码是如何执行到当前位置的
- 导航到调用者:点击调用栈中的条目,可以跳转到对应的函数
- 理解递归:查看递归函数的调用深度
- 发现异常来源:当程序出错时,调用栈显示错误发生的位置
2.3.1.8 条件断点
条件断点只在满足特定条件时才会暂停执行,非常适合调试循环或特定场景。
设置条件断点
- 右键点击行号
- 选择"Add conditional breakpoint"(添加条件断点)
- 输入条件表达式
- 按回车确认
// 示例:循环调试
for (let i = 0; i < 1000; i++) {
let result = calculate(i)
console.log(i, result)
}
// 设置条件断点:只在 i === 500 时暂停
// 条件:i === 500条件断点示例:
// 示例1:调试特定循环次数
for (let i = 0; i < 100; i++) {
console.log('当前是第', i, '次')
}
// 条件:i === 50 只在第50次循环时暂停
// 示例2:调试特定条件
let scores = [85, 92, 78, 65, 88]
for (let score of scores) {
console.log('分数:', score)
}
// 条件:score < 70 只在分数低于70时暂停
// 示例3:复杂条件
function processUser(user) {
if (user.age < 18) {
console.log('未成年用户')
} else if (user.role === 'admin') {
console.log('管理员')
}
}
// 条件:user.age < 18 && user.role === 'vip'2.3.1.9 断点调试实战案例
案例1:调试数组遍历
// 问题:数组遍历结果不符合预期
let numbers = [1, 2, 3, 4, 5]
let sum = 0
for (let i = 0; i < numbers.length; i++) {
sum += numbers[i]
console.log('当前数字:', numbers[i], '累加和:', sum)
}
console.log('最终和:', sum)调试步骤:
- 在
sum += numbers[i]这行设置断点 - 刷新页面触发断点
- 使用
F10单步跳过,观察每次循环:- i=0: sum=0+1=1
- i=1: sum=1+2=3
- i=2: sum=3+3=6
- i=3: sum=6+4=10
- i=4: sum=10+5=15
- 观察变量变化,确认逻辑正确
案例2:调试函数参数传递
function calculatePrice(price, discount, quantity) {
let finalPrice = price * (1 - discount) * quantity
return finalPrice
}
let totalPrice = calculatePrice(100, 0.2, 3)
console.log('总价:', totalPrice)调试步骤:
- 在
function calculatePrice(price, discount, quantity)行设置断点 - 触发断点后,查看 Scope 面板的参数:
- price: 100
- discount: 0.2
- quantity: 3
- 单步执行,观察
finalPrice的计算过程 - 确认计算逻辑是否正确
案例3:调试条件判断
function getGrade(score) {
if (score >= 90) {
return '优秀'
} else if (score >= 80) {
return '良好'
} else if (score >= 70) {
return '中等'
} else if (score >= 60) {
return '及格'
} else {
return '不及格'
}
}
let myScore = 85
let grade = getGrade(myScore)
console.log('等级:', grade)调试步骤:
- 在
if (score >= 90)设置断点 - 触发断点,查看 score 的值(85)
- 单步跳过(F10),观察每个条件:
- score >= 90: 85 >= 90 = false
- score >= 80: 85 >= 80 = true ✓
- 确认返回 '良好'
案例4:调试嵌套循环
// 问题:打印乘法表
for (let i = 1; i <= 9; i++) {
for (let j = 1; j <= i; j++) {
console.log(`${i} × ${j} = ${i * j}`)
}
}调试步骤:
- 在内层循环的
console.log设置条件断点 - 设置条件:
i === 3 && j === 2(只在特定组合时暂停) - 观察循环变量的变化规律
- 理解嵌套循环的执行流程
2.3.1.10 断点调试技巧
技巧1:使用条件断点跳过大量循环
// 避免在每次循环都暂停
for (let i = 0; i < 10000; i++) {
processData(i)
}
// 设置条件断点:i % 100 === 0 每100次暂停一次技巧2:使用 Logpoints(日志点)
日志点不会暂停执行,只会在控制台输出信息:
- 右键点击行号
- 选择"Add logpoint"(添加日志点)
- 输入要输出的表达式
let user = { name: '小明', age: 18 }
let isValid = checkUser(user)
// 设置日志点:`用户:${user.name},验证结果:${isValid}`
// 控制台输出:用户:小明,验证结果:true技巧3:使用 Watch 监视复杂表达式
let students = [
{ name: '小明', score: 85 },
{ name: '小红', score: 92 },
{ name: '小刚', score: 78 }
]
// Watch 中添加:
// students.length → 3
// students[0].score → 85
// students.filter(s => s.score >= 80).length → 2
// students.map(s => s.score).reduce((a, b) => a + b) → 255技巧4:使用 Blackbox Script(黑盒脚本)
将第三方库或框架代码标记为黑盒,调试时会自动跳过:
- 在 Sources 面板右键点击文件
- 选择"Blackbox script"
- 单步执行时会自动跳过该文件
2.3.1.11 调试常见问题
问题1:断点没有命中
可能原因:
- 代码没有被执行到(如条件不满足)
- 文件被修改后没有刷新页面
- 使用了代码压缩或混淆
- 断点设置在注释或空行
解决方法:
// 添加 debugger 语句确认代码是否执行
function test() {
debugger; // 如果这行不暂停,说明函数没有被调用
console.log('代码执行到这里')
}问题2:变量显示 undefined
可能原因:
- 变量在当前作用域不存在
- 变量在断点之后才声明
- 代码执行流程跳过了变量声明
解决方法:
// 检查变量声明位置
let num = 10
debugger; // 在这里可以看到 num
console.log(num) // 如果断点设在这里,num 已经声明问题3:无法查看闭包变量
可能原因: 闭包变量不在当前的 Scope 中,需要向上查找作用域链。
解决方法:
function outer() {
let privateVar = 100
return function inner() {
debugger; // 在这里,privateVar 在 Closure 作用域中
console.log(privateVar)
}
}
const fn = outer()
fn() // 在断点处查看 Scope → Closure → privateVar2.3.1.12 断点调试 vs console.log
对比表格:
| 特性 | 断点调试 | console.log |
|---|---|---|
| 暂停执行 | ✓ 可以 | ✗ 不可以 |
| 查看变量 | ✓ 实时查看 | ✓ 手动输出 |
| 修改变量 | ✓ 可以 | ✗ 不可以 |
| 单步执行 | ✓ 支持 | ✗ 不支持 |
| 查看调用栈 | ✓ 支持 | ✗ 不支持 |
| 条件调试 | ✓ 条件断点 | ✗ 需要手动判断 |
| 性能影响 | 中等(暂停时) | 较小 |
| 使用场景 | 复杂逻辑调试 | 快速查看值 |
使用建议:
// 使用 console.log:快速查看变量值
let sum = calculate()
console.log('计算结果:', sum)
// 使用断点调试:复杂逻辑分析
function complexLogic(data) {
debugger; // 在这里暂停,逐步检查每一步
let processed = processData(data)
let filtered = filterData(processed)
let result = transformData(filtered)
return result
}2.3.1.13 调试快捷键总结
| 快捷键 | 功能 | 说明 |
|---|---|---|
F8 / Ctrl + F8 | 切换断点 | 在当前行添加或移除断点 |
F9 | 继续执行 | 继续执行到下一个断点 |
F10 | 单步跳过 | 执行当前行,不进入函数 |
F11 | 单步进入 | 执行当前行,进入函数内部 |
Shift + F11 | 单步跳出 | 跳出当前函数 |
Ctrl + Shift + F5 | 重启调试 | 重新开始调试 |
Ctrl + P | 搜索文件 | 在项目中搜索文件 |
Ctrl + Shift + F | 全局搜索 | 在所有文件中搜索 |
Ctrl + G | 跳转行号 | 跳转到指定行 |
2.3.1.14 面试题
// 面试题1:以下代码输出什么?如何使用断点调试验证?
let i = 0
while (i < 3) {
i++
}
console.log(i)
// 输出:3
// 调试方法:在 i++ 设置断点,观察 i 的变化:0→1→2→3
// 面试题2:如何调试递归函数?
function factorial(n) {
if (n <= 1) return 1
return n * factorial(n - 1)
}
console.log(factorial(5))
// 调试方法:
// 1. 在 return n * factorial(n - 1) 设置断点
// 2. 观察调用栈,查看递归深度
// 3. 单步进入,观察每次递归的 n 值
// 面试题3:如何调试闭包?
function counter() {
let count = 0
return function() {
count++
return count
}
}
const c1 = counter()
const c2 = counter()
console.log(c1()) // 1
console.log(c1()) // 2
console.log(c2()) // 1
// 调试方法:
// 1. 在 count++ 设置断点
// 2. 查看调用栈,确认调用的是 c1 还是 c2
// 3. 查看 Scope → Closure → count 的值
// 面试题4:如何调试异步代码?
setTimeout(() => {
console.log('定时器执行')
}, 1000)
console.log('同步代码')
// 调试方法:
// 1. 在定时器回调函数设置断点
// 2. 观察调用栈,确认是异步回调
// 3. 注意异步代码的执行时机
// 面试题5:如何调试事件处理?
document.getElementById('btn').addEventListener('click', function() {
console.log('按钮被点击')
})
// 调试方法:
// 1. 在事件处理函数设置断点
// 2. 点击按钮触发断点
// 3. 查看事件对象 event,了解事件详情2.3.1.15 最佳实践
推荐做法:
- 合理设置断点:
// 推荐:在关键逻辑处设置断点
if (user.loginStatus === 'success') {
debugger; // 在这里检查登录后的逻辑
redirectToHome()
}- 使用条件断点提高效率:
// 推荐:避免在每次循环都暂停
for (let i = 0; i < 10000; i++) {
// 条件断点:i === 5000
processItem(i)
}- 利用 Watch 面板监控变量:
// 推荐:添加复杂表达式到 Watch
// Watch: users.filter(u => u.active).length
// Watch: performance.now()- 调试完成后清理:
// 记得移除 debugger 语句
// function test() {
// debugger; // ← 发布前删除
// return result
// }- 使用日志点替代部分断点:
// 推荐:使用日志点记录信息,不中断执行
// Logpoint: `处理到第 ${i} 项,值:${value}`
for (let item of items) {
processItem(item)
}避免做法:
- 避免过多的断点:
// 不推荐:在每行都设置断点,影响调试效率
let a = 1 // 断点
let b = 2 // 断点
let c = 3 // 断点
// 推荐:只在关键位置设置断点- 避免忘记清理调试代码:
// 不推荐:发布代码中包含 debugger
function APIHandler() {
debugger; // ← 发布前必须删除
return fetch(...)
}- 避免过度依赖断点调试:
// 不推荐:用断点调试解决所有问题
// 简单的值查看应该用 console.log
let result = calculate()
debugger; // 过度使用
// 推荐:简单查看用 console.log
console.log(result)2.3.1.16 总结
断点调试要点:
- 定义:在代码中设置断点,暂停程序执行以检查状态
- 工具:使用浏览器开发者工具的 Sources 面板
- 操作:设置断点、单步执行、查看变量、修改变量
- 技巧:条件断点、日志点、监视表达式
- 场景:复杂逻辑、循环、递归、闭包、异步代码
- 优势:可以暂停执行、单步调试、查看调用栈、实时修改
- 注意:调试完成后记得移除调试代码
核心概念:
- 断点是代码执行暂停的位置
- 单步执行可以逐步检查代码
- Scope 面板显示当前作用域的所有变量
- Call Stack 显示函数调用链
- 条件断点可以只在特定条件时暂停
2.3.2 while 循环
while 循环是最基本的循环结构,它会在条件为 true 时重复执行代码块,直到条件变为 false。
2.3.2.1 基本语法
while (条件) {
// 循环体:当条件为 true 时重复执行的代码
}语法说明:
while:关键字,表示循环条件:一个表达式,会被转换为布尔值{}:循环体,包含要重复执行的代码- 执行流程:先判断条件,如果为
true则执行循环体,然后再次判断条件 - 循环结束:当条件变为
false时,退出循环
2.3.2.2 执行流程
流程图:
┌─────────┐
│ 开始 │
└────┬────┘
│
▼
┌─────────┐
│ 判断条件 │
└────┬────┘
│
├─── true ───┐
│ ▼
│ ┌──────────┐
│ │ 执行循环体 │
│ └────┬─────┘
│ │
│ ▼
│ ┌──────────┐
│ │ 返回判断 │
│ └────┬─────┘
│ │
│ │
└───────────┘
│
└── false ──┤
│
▼
┌──────────┐
│ 退出循环 │
└──────────┘执行特点:
- 先判断,后执行:条件不满足时,循环体一次都不会执行
- 循环次数不确定:根据条件决定循环次数
- 需要手动更新条件:在循环体中改变条件值,否则会造成死循环
2.3.2.3 代码示例
示例1:基础用法
// 打印 1 到 5
let i = 1
while (i <= 5) {
console.log(i)
i++
}
// 输出:
// 1
// 2
// 3
// 4
// 5// 计算 1 到 10 的和
let sum = 0
let i = 1
while (i <= 10) {
sum += i
i++
}
console.log('1到10的和:', sum) // 1到10的和: 55// 计算阶乘 5! = 5 × 4 × 3 × 2 × 1
let factorial = 1
let n = 5
let i = 1
while (i <= n) {
factorial *= i
i++
}
console.log('5的阶乘:', factorial) // 5的阶乘: 120示例2:倒序输出
// 从 5 倒序打印到 1
let i = 5
while (i >= 1) {
console.log(i)
i--
}
// 输出:
// 5
// 4
// 3
// 2
// 1// 倒序输出数组元素
let fruits = ['苹果', '香蕉', '橙子', '葡萄']
let i = fruits.length - 1
while (i >= 0) {
console.log(fruits[i])
i--
}
// 输出:
// 葡萄
// 橙子
// 香蕉
// 苹果示例3:字符串操作
// 统计字符串中字符的个数
let str = 'Hello World'
let count = 0
let i = 0
while (i < str.length) {
count++
i++
}
console.log('字符串长度:', count) // 字符串长度: 11// 查找字符首次出现的位置
let text = 'JavaScript'
let target = 'a'
let foundIndex = -1
let i = 0
while (i < text.length && foundIndex === -1) {
if (text[i] === target) {
foundIndex = i
}
i++
}
console.log('字符 a 的位置:', foundIndex) // 字符 a 的位置: 1示例4:数组操作
// 计算数组元素的平均值
let numbers = [85, 90, 78, 92, 88]
let sum = 0
let i = 0
while (i < numbers.length) {
sum += numbers[i]
i++
}
let average = sum / numbers.length
console.log('平均分:', average) // 平均分: 86.6// 查找数组中的最大值
let nums = [23, 45, 12, 67, 34, 89]
let max = nums[0]
let i = 1
while (i < nums.length) {
if (nums[i] > max) {
max = nums[i]
}
i++
}
console.log('最大值:', max) // 最大值: 89// 筛选数组中的偶数
let arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
let evens = []
let i = 0
while (i < arr.length) {
if (arr[i] % 2 === 0) {
evens.push(arr[i])
}
i++
}
console.log('偶数数组:', evens) // 偶数数组: [2, 4, 6, 8, 10]示例5:数学计算
// 判断素数
function isPrime(num) {
if (num <= 1) return false
if (num === 2) return true
let i = 2
while (i <= Math.sqrt(num)) {
if (num % i === 0) {
return false
}
i++
}
return true
}
console.log(isPrime(7)) // true
console.log(isPrime(10)) // false
console.log(isPrime(17)) // true// 斐波那契数列前 n 项
function fibonacci(n) {
if (n <= 0) return []
if (n === 1) return [0]
if (n === 2) return [0, 1]
let result = [0, 1]
let i = 2
while (i < n) {
result.push(result[i-1] + result[i-2])
i++
}
return result
}
console.log(fibonacci(10))
// 输出: [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]// 计算最大公约数(欧几里得算法)
function gcd(a, b) {
while (b !== 0) {
let temp = b
b = a % b
a = temp
}
return a
}
console.log(gcd(48, 18)) // 6
console.log(gcd(100, 75)) // 25示例6:条件判断循环
// 输入验证(模拟)
let password = ''
let attempts = 0
const maxAttempts = 3
while (password !== '123456' && attempts < maxAttempts) {
console.log('请输入密码:')
password = '123456' // 模拟输入正确密码
attempts++
console.log(`尝试次数: ${attempts}`)
}
if (password === '123456') {
console.log('密码正确,登录成功!')
} else {
console.log('尝试次数超限,登录失败!')
}// 寻找第一个大于 1000 的 2 的幂
let power = 1
let exponent = 0
while (power <= 1000) {
power *= 2
exponent++
}
console.log(`2 的 ${exponent} 次方 = ${power}`) // 2 的 10 次方 = 10242.3.2.4 注意事项
重要注意事项
- 必须更新循环变量:
// 正确示例
let i = 0
while (i < 5) {
console.log(i)
i++ // 必须更新变量,否则死循环
}
// 错误示例:死循环
let i = 0
while (i < 5) {
console.log(i)
// 忘记更新 i,造成死循环
}- 循环条件要能变为 false:
// 正确示例
let count = 10
while (count > 0) {
console.log(count)
count--
}
// 错误示例:永远为 true
let count = 10
while (count > 0) {
console.log(count)
// count 没有变化,条件永远为 true
}- 初始化循环变量:
// 正确示例
let i = 0
while (i < 5) {
console.log(i)
i++
}
// 错误示例:未初始化
let i
while (i < 5) { // i 是 undefined,条件判断可能出错
console.log(i)
i++
}- 避免死循环:
// 死循环示例1:条件永远为 true
while (true) {
console.log('这会一直执行')
// 需要手动 break 才能退出
}
// 死循环示例2:条件永远不会改变
let flag = true
while (flag) {
console.log('执行中')
// flag 没有改变,死循环
}
// 正确做法:使用 break
let flag = true
let count = 0
while (flag) {
console.log('执行中')
count++
if (count >= 5) {
break // 手动退出循环
}
}2.3.2.5 实际应用场景
场景1:用户输入验证
// 模拟用户输入,直到输入有效值
let input = ''
while (input.length < 3 || input.length > 10) {
console.log('请输入用户名(3-10个字符):')
input = 'admin' // 模拟输入
}
console.log('用户名有效:', input)// 猜数字游戏
let target = Math.floor(Math.random() * 100) + 1
let guess = 0
let attempts = 0
console.log('猜一个1到100之间的数字')
while (guess !== target) {
attempts++
guess = 50 // 模拟猜测
if (guess < target) {
console.log('太小了!')
} else if (guess > target) {
console.log('太大了!')
}
}
console.log(`恭喜!你用了 ${attempts} 次猜对了!`)场景2:数据处理
// 过滤并转换数据
let students = [
{ name: '小明', score: 85 },
{ name: '小红', score: 92 },
{ name: '小刚', score: 78 },
{ name: '小丽', score: 65 }
]
let excellentStudents = []
let i = 0
while (i < students.length) {
if (students[i].score >= 90) {
excellentStudents.push({
name: students[i].name,
score: students[i].score,
grade: '优秀'
})
}
i++
}
console.log('优秀学生:', excellentStudents)// 数组去重
let arr = [1, 2, 3, 2, 4, 5, 3, 6, 1]
let uniqueArr = []
let i = 0
while (i < arr.length) {
let isDuplicate = false
let j = 0
while (j < uniqueArr.length) {
if (arr[i] === uniqueArr[j]) {
isDuplicate = true
break
}
j++
}
if (!isDuplicate) {
uniqueArr.push(arr[i])
}
i++
}
console.log('去重后的数组:', uniqueArr) // [1, 2, 3, 4, 5, 6]场景3:搜索算法
// 二分查找
function binarySearch(arr, target) {
let left = 0
let right = arr.length - 1
while (left <= right) {
let mid = Math.floor((left + right) / 2)
if (arr[mid] === target) {
return mid
} else if (arr[mid] < target) {
left = mid + 1
} else {
right = mid - 1
}
}
return -1 // 未找到
}
let sortedArray = [1, 3, 5, 7, 9, 11, 13, 15]
console.log('查找 7 的位置:', binarySearch(sortedArray, 7)) // 3
console.log('查找 10 的位置:', binarySearch(sortedArray, 10)) // -1// 线性查找
function linearSearch(arr, target) {
let i = 0
while (i < arr.length) {
if (arr[i] === target) {
return i
}
i++
}
return -1
}
let array = [4, 2, 7, 1, 9, 3]
console.log('查找 7 的位置:', linearSearch(array, 7)) // 2
console.log('查找 5 的位置:', linearSearch(array, 5)) // -1场景4:游戏开发
// 倒计时
function countdown(seconds) {
while (seconds > 0) {
console.log(`${seconds} 秒`)
seconds--
// 这里可以添加延迟,如 setTimeout
}
console.log('时间到!')
}
countdown(5)
// 输出:
// 5 秒
// 4 秒
// 3 秒
// 2 秒
// 1 秒
// 时间到!// 简单的生命值系统
let health = 100
let damage = 15
while (health > 0) {
console.log(`当前生命值: ${health}`)
health -= damage
if (health < 0) {
health = 0
}
}
console.log('游戏结束!生命值归零')2.3.2.6 while vs for
对比表格:
| 特性 | while 循环 | for 循环 |
|---|---|---|
| 语法 | while(条件){} | for(初始化; 条件; 更新){} |
| 初始化 | 需要在循环外单独定义 | 在循环头部定义 |
| 更新 | 需要在循环体中手动更新 | 自动执行更新语句 |
| 循环次数 | 不确定,由条件决定 | 通常确定,由计数器决定 |
| 使用场景 | 条件复杂、次数不确定 | 次数确定、简单计数 |
| 可读性 | 适合复杂条件 | 适合固定次数循环 |
| 灵活性 | 更灵活 | 结构更紧凑 |
使用建议:
// 使用 for:次数确定
for (let i = 0; i < 10; i++) {
console.log(i)
}
// 使用 while:条件复杂或次数不确定
let i = 0
while (shouldContinue(i)) {
console.log(i)
i++
}2.3.2.7 面试题
// 面试题1:以下代码输出什么?
let i = 0
while (i < 3) {
i++
console.log(i)
}
// 输出:
// 1
// 2
// 3
// 解释:i 先自增,再输出,所以输出 1, 2, 3
// 面试题2:以下代码输出什么?
let n = 10
while (n > 0) {
if (n % 2 === 0) {
console.log(n)
}
n--
}
// 输出:
// 10
// 8
// 6
// 4
// 2
// 解释:只输出偶数
// 面试题3:以下代码执行多少次循环?
let i = 1
let count = 0
while (i < 100) {
i *= 2
count++
}
console.log('循环次数:', count)
// 输出:循环次数: 7
// 解释:1→2→4→8→16→32→64→128,共7次
// 面试题4:以下代码输出什么?
let x = 1
let y = 0
while (x <= 5) {
y += x
x++
}
console.log(y)
// 输出:15
// 解释:1+2+3+4+5 = 15
// 面试题5:找出问题
let i = 0
while (i < 5) {
console.log(i)
// 忘记更新 i,死循环
}
// 问题:没有更新循环变量,造成死循环
// 面试题6:输出斐波那契数列前5项
let a = 0, b = 1
let count = 0
while (count < 5) {
console.log(a)
let temp = a + b
a = b
b = temp
count++
}
// 输出:0, 1, 1, 2, 3
// 面试题7:判断数字是否为回文数
function isPalindrome(num) {
let original = num
let reversed = 0
while (num > 0) {
let digit = num % 10
reversed = reversed * 10 + digit
num = Math.floor(num / 10)
}
return original === reversed
}
console.log(isPalindrome(121)) // true
console.log(isPalindrome(123)) // false
// 面试题8:计算数字的位数
function countDigits(num) {
if (num === 0) return 1
num = Math.abs(num)
let count = 0
while (num > 0) {
num = Math.floor(num / 10)
count++
}
return count
}
console.log(countDigits(12345)) // 5
console.log(countDigits(-100)) // 32.3.2.8 最佳实践
推荐做法:
- 初始化循环变量:
// 推荐
let i = 0
while (i < 10) {
// 代码
i++
}- 确保条件能变为 false:
// 推荐
let count = 10
while (count > 0) {
// 代码
count-- // 条件会变化
}- 使用有意义的变量名:
// 推荐
let attempt = 1
while (attempt <= maxAttempts) {
// 代码
attempt++
}
// 不推荐
let i = 1
while (i <= n) {
// 代码
i++
}- 循环前检查边界条件:
// 推荐
function findElement(arr, target) {
if (!arr || arr.length === 0) {
return -1
}
let i = 0
while (i < arr.length) {
if (arr[i] === target) {
return i
}
i++
}
return -1
}- 限制循环次数(防止死循环):
// 推荐
let maxIterations = 1000
let iterations = 0
while (condition && iterations < maxIterations) {
// 代码
iterations++
}
if (iterations >= maxIterations) {
console.log('警告:可能存在死循环')
}避免做法:
- 避免死循环:
// 不推荐:死循环
while (true) {
// 永远执行
}
// 推荐:使用 break
let count = 0
while (true) {
if (count >= 10) break
count++
}- 避免忘记更新变量:
// 不推荐
let i = 0
while (i < 10) {
console.log(i)
// 忘记更新 i
}
// 推荐
let i = 0
while (i < 10) {
console.log(i)
i++
}- 避免复杂的循环条件:
// 不推荐:条件太复杂
while (i < 10 && j > 0 && flag && count < 100 && !done) {
// 代码
}
// 推荐:拆分为变量
let shouldContinue = i < 10 && j > 0 && flag && count < 100 && !done
while (shouldContinue) {
// 代码
shouldContinue = i < 10 && j > 0 && flag && count < 100 && !done
}2.3.2.9 总结
while 循环要点:
- 语法:
while (条件) { 循环体 } - 执行:先判断条件,满足则执行循环体
- 特点:先判断后执行,可能一次都不执行
- 注意:必须更新循环变量,否则死循环
- 使用场景:循环次数不确定、条件复杂
- 对比:比 for 更灵活,但需要手动管理循环变量
- 最佳实践:初始化变量、更新变量、防止死循环
核心要点:
- while 循环在条件为 true 时重复执行
- 必须在循环体中更新条件变量
- 循环次数不固定,由条件决定
- 适合不确定次数的循环
- 记得处理边界条件
2.3.3 for 循环
for 循环是 JavaScript 中最常用的循环结构,它将初始化、条件判断和循环变量更新集中在循环头部,代码结构清晰简洁。
2.3.3.1 基本语法
for (初始化; 条件; 更新) {
// 循环体:当条件为 true 时重复执行的代码
}语法说明:
| 部分 | 说明 | 示例 |
|---|---|---|
| 初始化 | 循环开始前执行一次,通常用于声明和初始化循环变量 | let i = 0 |
| 条件 | 每次循环开始前判断,如果为 true 则执行循环体,否则退出循环 | i < 5 |
| 更新 | 每次循环结束后执行,通常用于更新循环变量 | i++ |
| 循环体 | 当条件为 true 时重复执行的代码块 | { console.log(i) } |
执行流程:
- 执行初始化(只执行一次)
- 判断条件,如果为
true则执行循环体,如果为false则退出循环 - 执行循环体
- 执行更新
- 返回步骤 2
2.3.3.2 执行流程
流程图:
┌─────────┐
│ 开始 │
└────┬────┘
│
▼
┌─────────┐
│ 初始化 │ (只执行一次)
└────┬────┘
│
▼
┌─────────┐
│ 判断条件 │
└────┬────┘
│
├─── true ───┐
│ ▼
│ ┌──────────┐
│ │ 执行循环体 │
│ └────┬─────┘
│ │
│ ▼
│ ┌──────────┐
│ │ 更新变量 │
│ └────┬─────┘
│ │
│ │
└───────────┤
│
└── false ──┤
│
▼
┌──────────┐
│ 退出循环 │
└──────────┘2.3.3.3 代码示例
示例1:基础用法
// 打印 0 到 4
for (let i = 0; i < 5; i++) {
console.log(i)
}
// 输出:
// 0
// 1
// 2
// 3
// 4
// 打印 1 到 5
for (let i = 1; i <= 5; i++) {
console.log(i)
}
// 输出:
// 1
// 2
// 3
// 4
// 5
// 倒序打印 5 到 1
for (let i = 5; i >= 1; i--) {
console.log(i)
}
// 输出:
// 5
// 4
// 3
// 2
// 1示例2:累加和累乘
// 计算 1 到 100 的和
let sum = 0
for (let i = 1; i <= 100; i++) {
sum += i
}
console.log('1到100的和:', sum) // 1到100的和: 5050
// 计算 1 到 10 的乘积(阶乘)
let factorial = 1
for (let i = 1; i <= 10; i++) {
factorial *= i
}
console.log('10的阶乘:', factorial) // 10的阶乘: 3628800
// 计算偶数和
let evenSum = 0
for (let i = 2; i <= 100; i += 2) {
evenSum += i
}
console.log('1到100的偶数和:', evenSum) // 1到100的偶数和: 2550
// 计算奇数和
let oddSum = 0
for (let i = 1; i <= 100; i += 2) {
oddSum += i
}
console.log('1到100的奇数和:', oddSum) // 1到100的奇数和: 2500示例3:数组遍历
// 遍历数组元素
let fruits = ['苹果', '香蕉', '橙子', '葡萄']
for (let i = 0; i < fruits.length; i++) {
console.log('第' + i + '个水果:', fruits[i])
}
// 输出:
// 第0个水果: 苹果
// 第1个水果: 香蕉
// 第2个水果: 橙子
// 第3个水果: 葡萄
// 计算数组元素的平均值
let numbers = [85, 90, 78, 92, 88]
let total = 0
for (let i = 0; i < numbers.length; i++) {
total += numbers[i]
}
let avg = total / numbers.length
console.log('平均分:', avg) // 平均分: 86.6
// 查找数组中的最大值
let scores = [23, 45, 67, 12, 89, 34]
let max = scores[0]
for (let i = 1; i < scores.length; i++) {
if (scores[i] > max) {
max = scores[i]
}
}
console.log('最大值:', max) // 最大值: 89
// 筛选偶数
let arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
let evens = []
for (let i = 0; i < arr.length; i++) {
if (arr[i] % 2 === 0) {
evens.push(arr[i])
}
}
console.log('偶数数组:', evens) // 偶数数组: [2, 4, 6, 8, 10]示例4:字符串操作
// 统计字符串中的字符数
let str = 'Hello World'
let count = 0
for (let i = 0; i < str.length; i++) {
count++
}
console.log('字符个数:', count) // 字符个数: 11
// 统计大写字母个数
let text = 'Hello World'
let upperCount = 0
for (let i = 0; i < text.length; i++) {
if (text[i] >= 'A' && text[i] <= 'Z') {
upperCount++
}
}
console.log('大写字母个数:', upperCount) // 大写字母个数: 2
// 查找字符位置
let message = 'JavaScript'
let target = 'a'
let positions = []
for (let i = 0; i < message.length; i++) {
if (message[i] === target) {
positions.push(i)
}
}
console.log('字符 a 的位置:', positions) // 字符 a 的位置: [1, 3]
// 反转字符串
let original = 'hello'
let reversed = ''
for (let i = original.length - 1; i >= 0; i--) {
reversed += original[i]
}
console.log('反转后:', reversed) // 反转后: olleh示例5:乘法表
// 九九乘法表
for (let i = 1; i <= 9; i++) {
let line = ''
for (let j = 1; j <= i; j++) {
line += `${i} × ${j} = ${i * j} `
}
console.log(line)
}
// 输出:
// 1 × 1 = 1
// 2 × 1 = 2 2 × 2 = 4
// 3 × 1 = 3 3 × 2 = 6 3 × 3 = 9
// ...(以此类推)示例6:数学计算
// 判断素数
function isPrime(n) {
if (n <= 1) return false
for (let i = 2; i <= Math.sqrt(n); i++) {
if (n % i === 0) {
return false
}
}
return true
}
console.log(isPrime(7)) // true
console.log(isPrime(10)) // false
console.log(isPrime(17)) // true
// 打印斐波那契数列前 10 项
let n = 10
let a = 0, b = 1
console.log('斐波那契数列前' + n + '项:')
for (let i = 0; i < n; i++) {
console.log(a)
let temp = a + b
a = b
b = temp
}
// 输出:
// 0
// 1
// 1
// 2
// 3
// 5
// 8
// 13
// 21
// 34
// 打印水仙花数(三位数,各位数字的立方和等于本身)
console.log('水仙花数:')
for (let i = 100; i <= 999; i++) {
let hundreds = Math.floor(i / 100)
let tens = Math.floor((i % 100) / 10)
let ones = i % 10
if (hundreds ** 3 + tens ** 3 + ones ** 3 === i) {
console.log(i)
}
}
// 输出:
// 153
// 370
// 371
// 4072.3.3.4 for 循环变体
省略各个部分
for 循环的三个部分都是可选的,可以省略。
// 省略初始化
let i = 0
for (; i < 5; i++) {
console.log(i)
}
// 省略条件(会变成无限循环,需要手动 break)
for (let i = 0; ; i++) {
if (i >= 5) break
console.log(i)
}
// 省略更新
for (let i = 0; i < 5; ) {
console.log(i)
i++
}
// 省略所有(无限循环)
for (;;) {
console.log('无限循环')
break
}注意:虽然可以省略部分内容,但一般情况下不建议这样做,除非有特殊需求。
2.3.3.5 实际应用场景
场景1:批量操作
// 批量创建元素
let container = document.getElementById('container')
for (let i = 0; i < 5; i++) {
let div = document.createElement('div')
div.textContent = '元素 ' + i
container.appendChild(div)
}
// 批量生成数据
let users = []
for (let i = 1; i <= 10; i++) {
users.push({
id: i,
name: '用户' + i,
age: Math.floor(Math.random() * 50) + 18
})
}
console.log(users)场景2:数据统计
// 统计考试成绩
let scores = [85, 92, 78, 65, 88, 95, 72, 80]
let excellent = 0 // 优秀
let good = 0 // 良好
let pass = 0 // 及格
let fail = 0 // 不及格
for (let i = 0; i < scores.length; i++) {
if (scores[i] >= 90) {
excellent++
} else if (scores[i] >= 80) {
good++
} else if (scores[i] >= 60) {
pass++
} else {
fail++
}
}
console.log('优秀:', excellent)
console.log('良好:', good)
console.log('及格:', pass)
console.log('不及格:', fail)场景3:数组去重
// 数组去重
let arr = [1, 2, 3, 2, 4, 5, 3, 6, 1]
let uniqueArr = []
for (let i = 0; i < arr.length; i++) {
let isDuplicate = false
for (let j = 0; j < uniqueArr.length; j++) {
if (arr[i] === uniqueArr[j]) {
isDuplicate = true
break
}
}
if (!isDuplicate) {
uniqueArr.push(arr[i])
}
}
console.log('去重后的数组:', uniqueArr) // [1, 2, 3, 4, 5, 6]场景4:冒泡排序
// 冒泡排序
function bubbleSort(arr) {
for (let i = 0; i < arr.length - 1; i++) {
for (let j = 0; j < arr.length - 1 - i; j++) {
if (arr[j] > arr[j + 1]) {
// 交换位置
let temp = arr[j]
arr[j] = arr[j + 1]
arr[j + 1] = temp
}
}
}
return arr
}
let numbers = [64, 34, 25, 12, 22, 11, 90]
console.log('排序前:', numbers)
console.log('排序后:', bubbleSort([...numbers]))2.3.3.6 嵌套 for 循环
嵌套循环是指在一个循环内部再包含另一个循环。
// 打印矩形
for (let i = 0; i < 3; i++) {
let line = ''
for (let j = 0; j < 5; j++) {
line += '* '
}
console.log(line)
}
// 输出:
// * * * * *
// * * * * *
// * * * * *
// 打印直角三角形
for (let i = 1; i <= 5; i++) {
let line = ''
for (let j = 1; j <= i; j++) {
line += '* '
}
console.log(line)
}
// 输出:
// *
// * *
// * * *
// * * * *
// * * * * *
// 打印等腰三角形
for (let i = 1; i <= 5; i++) {
let line = ''
// 打印空格
for (let j = 1; j <= 5 - i; j++) {
line += ' '
}
// 打印星号
for (let j = 1; j <= 2 * i - 1; j++) {
line += '*'
}
console.log(line)
}
// 输出:
// *
// ***
// *****
// *******
// *********
// 二维数组遍历
let matrix = [
[1, 2, 3],
[4, 5, 6],
[7, 8, 9]
]
for (let i = 0; i < matrix.length; i++) {
let row = ''
for (let j = 0; j < matrix[i].length; j++) {
row += matrix[i][j] + ' '
}
console.log(row)
}
// 输出:
// 1 2 3
// 4 5 6
// 7 8 92.3.3.7 for vs while
对比表格:
| 特性 | for 循环 | while 循环 |
|---|---|---|
| 语法 | for(初始化; 条件; 更新){} | while(条件){} |
| 初始化 | 在循环头部集中定义 | 需要在循环外单独定义 |
| 更新 | 自动执行更新语句 | 需要在循环体中手动更新 |
| 循环次数 | 通常确定,由计数器决定 | 不确定,由条件决定 |
| 使用场景 | 次数确定的循环 | 条件复杂的循环 |
| 可读性 | 结构紧凑,一目了然 | 更灵活,但需要手动管理 |
| 推荐程度 | 固定次数循环优先使用 | 不确定次数循环优先使用 |
使用建议:
// 使用 for:次数确定
for (let i = 0; i < 10; i++) {
console.log(i)
}
// 使用 while:条件复杂或次数不确定
let condition = true
let attempts = 0
while (condition && attempts < 5) {
// 复杂的逻辑
attempts++
}2.3.3.8 注意事项
重要注意事项
- 循环变量的作用域:
// 推荐:使用 let 声明循环变量
for (let i = 0; i < 5; i++) {
console.log(i)
}
// 循环结束后,i 不可访问
// 不推荐:使用 var 声明循环变量
for (var j = 0; j < 5; j++) {
console.log(j)
}
// 循环结束后,j 仍然可以访问(值为5)- 避免在循环体中修改数组长度:
// 危险:在循环中删除元素
let arr = [1, 2, 3, 4, 5]
for (let i = 0; i < arr.length; i++) {
if (arr[i] === 3) {
arr.splice(i, 1) // 删除元素后索引会变化
i-- // 需要手动调整索引
}
}
// 推荐:倒序遍历删除
let arr2 = [1, 2, 3, 4, 5]
for (let i = arr2.length - 1; i >= 0; i--) {
if (arr2[i] === 3) {
arr2.splice(i, 1)
}
}- 避免无限循环:
// 错误示例:条件永远为 true
for (let i = 0; i < 5; ) {
console.log(i)
// 忘记更新 i,死循环
}
// 错误示例:更新方向错误
for (let i = 0; i < 5; i--) {
console.log(i)
// i 减小,永远满足 i < 5
}
// 正确示例
for (let i = 0; i < 5; i++) {
console.log(i)
}- 注意边界条件:
// 数组遍历
let arr = [1, 2, 3, 4, 5]
// 正确
for (let i = 0; i < arr.length; i++) {
console.log(arr[i]) // 1, 2, 3, 4, 5
}
// 错误:越界
for (let i = 0; i <= arr.length; i++) {
console.log(arr[i]) // 最后一次 arr[5] 是 undefined
}2.3.3.9 面试题
// 面试题1:以下代码输出什么?
for (let i = 0; i < 3; i++) {
console.log(i)
}
// 输出:
// 0
// 1
// 2
// 面试题2:以下代码循环多少次?
for (let i = 1; i <= 100; i += 2) {
// 只处理奇数
}
// 循环次数:50次(1, 3, 5, ..., 99)
// 面试题3:以下代码输出什么?
for (let i = 0; i < 5; i++) {
console.log('A')
if (i === 2) break
}
// 输出:
// A
// A
// A
// 解释:i=2 时 break 退出循环
// 面试题4:以下代码输出什么?
for (let i = 0; i < 5; i++) {
if (i % 2 === 0) continue
console.log(i)
}
// 输出:
// 1
// 3
// 解释:偶数跳过,只输出奇数
// 面试题5:以下代码输出什么?
for (let i = 0; i < 3; i++) {
setTimeout(() => {
console.log(i)
}, 0)
}
// 输出:
// 3
// 3
// 3
// 解释:setTimeout 是异步,循环结束时 i=3
// 使用闭包解决
for (let i = 0; i < 3; i++) {
(function(i) {
setTimeout(() => {
console.log(i)
}, 0)
})(i)
}
// 输出:
// 0
// 1
// 2
// 面试题6:var 和 let 的区别
for (var i = 0; i < 3; i++) {
setTimeout(() => {
console.log('var:', i)
}, 0)
}
// 输出:3, 3, 3
for (let j = 0; j < 3; j++) {
setTimeout(() => {
console.log('let:', j)
}, 0)
}
// 输出:0, 1, 2
// 面试题7:嵌套循环输出什么?
for (let i = 1; i <= 3; i++) {
let str = ''
for (let j = 1; j <= i; j++) {
str += j
}
console.log(str)
}
// 输出:
// 1
// 12
// 123
// 面试题8:计算结果
let sum = 0
for (let i = 1; i <= 10; i++) {
if (i % 2 === 0) continue
sum += i
}
console.log(sum)
// 输出:25(1 + 3 + 5 + 7 + 9 = 25)2.3.3.10 最佳实践
推荐做法:
- 使用 let 声明循环变量:
// 推荐
for (let i = 0; i < 10; i++) {
// 代码
}- 循环变量使用有意义的名称:
// 推荐
for (let index = 0; index < users.length; index++) {
console.log(users[index].name)
}
// 不推荐
for (let i = 0; i < users.length; i++) {
console.log(users[i].name)
}- 固定次数循环使用 for,不确定次数使用 while:
// 推荐:次数固定
for (let i = 0; i < 10; i++) {
// 代码
}
// 推荐:次数不确定
while (condition) {
// 代码
}- 使用 break 和 continue 控制循环:
// 使用 break 退出循环
for (let i = 0; i < 100; i++) {
if (foundTarget) break
}
// 使用 continue 跳过本次循环
for (let i = 0; i < 100; i++) {
if (shouldSkip(i)) continue
// 处理 i
}- 倒序遍历删除数组元素:
// 推荐:倒序删除
for (let i = arr.length - 1; i >= 0; i--) {
if (shouldRemove(arr[i])) {
arr.splice(i, 1)
}
}避免做法:
- 避免在循环中修改数组长度:
// 不推荐:正序删除元素
for (let i = 0; i < arr.length; i++) {
arr.splice(i, 1) // 索引会混乱
}- 避免过深的嵌套:
// 不推荐:嵌套过深
for (let i = 0; i < 10; i++) {
for (let j = 0; j < 10; j++) {
for (let k = 0; k < 10; k++) {
// 代码
}
}
}
// 推荐:拆分为函数
function processItem(i, j, k) {
// 代码
}
for (let i = 0; i < 10; i++) {
for (let j = 0; j < 10; j++) {
for (let k = 0; k < 10; k++) {
processItem(i, j, k)
}
}
}- 避免在循环体中进行复杂计算:
// 不推荐:每次循环都计算
for (let i = 0; i < array.length; i++) {
let result = expensiveCalculation(i)
// 使用 result
}
// 推荐:缓存计算结果
const cachedResults = {}
for (let i = 0; i < array.length; i++) {
if (!cachedResults[i]) {
cachedResults[i] = expensiveCalculation(i)
}
// 使用 cachedResults[i]
}2.3.3.11 总结
for 循环要点:
- 语法:
for (初始化; 条件; 更新) { 循环体 } - 执行:初始化 → 判断条件 → 执行循环体 → 更新 → 返回判断
- 特点:结构紧凑,初始化、条件、更新集中在一行
- 优势:适合固定次数的循环,代码清晰易读
- 场景:数组遍历、计数循环、嵌套循环
- 对比:比 while 更结构化,适合确定次数的循环
- 最佳实践:使用 let 声明变量、注意边界条件、避免死循环
核心要点:
- for 循环是 JavaScript 中最常用的循环结构
- 初始化、条件、更新三个部分集中在循环头部
- 适合循环次数确定的情况
- 可以嵌套使用实现多重循环
- 使用 let 避免变量提升问题
