第 6 章 运算符与表达式(补充)

第 6 章 运算符与表达式(补充)

6.1 自增自减详解

在第四章我们简单介绍了 ++--,但这一节我们要把它彻底讲透。这两个运算符虽然只有两个字符,但里面藏着的细节足以让无数面试者翻车。

++a 与 a++ 的区别与执行顺序

前置 ++++a:先给变量加 1,然后返回新值。 后置 ++a++:先返回当前值(副本),然后给变量加 1。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
// 前置++:先加后用
let a = 5;
console.log(++a); // 6(先加到6,再输出)
console.log(a);   // 6

// 后置++:先用后加
let b = 5;
console.log(b++); // 5(先输出5,b再变成6)
console.log(b);   // 6

// 内存中的变化
// ++a: a = a + 1; return a;
// a++: temp = a; a = a + 1; return temp;
1
2
3
4
5
6
7
8
// 另一个角度理解
let a = 1;

console.log(++a); // a先+1变成2,返回2
console.log(a);   // 2

console.log(a++); // 返回a的值2,然后a变成3
console.log(a);   // 3
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
// 三者对比
let x = 5;

// 表达式本身的值
let val1 = ++x; // 前置++:x变成6,表达式返回6
console.log("val1 =", val1, "x =", x); // val1 = 6, x = 6

let y = 5;
let val2 = y++; // 后置++:表达式返回5,然后y变成6
console.log("val2 =", val2, "y =", y); // val2 = 5, y = 6

let z = 5;
let val3 = z;   // 仅仅是赋值
console.log("val3 =", val3, "z =", z); // val3 = 5, z = 5
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
// 常见陷阱:连续使用
let i = 1;
console.log(i++ + ++i); // 4
// 分析:
// 1. i++:返回1,i变成2
// 2. ++i:i变成3,返回3
// 3. 1 + 3 = 4
// 4. 最终 i = 3

console.log(i++ + i++); // 7
// 分析:
// 1. i++:返回3,i变成4
// 2. i++:返回4,i变成5
// 3. 3 + 4 = 7
// 4. 最终 i = 5

// 这种代码太混乱了!不要在同一个表达式中多次修改同一个变量!

循环中的 i++ 和 ++i

在循环中使用 i++++i,哪个更好?

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
// 答案:在大多数情况下,没有区别!
// 因为我们通常只关心循环变量本身,不关心表达式的返回值

for (let i = 0; i < 3; i++) {
    console.log(i); // 0, 1, 2
}

for (let i = 0; i < 3; ++i) {
    console.log(i); // 0, 1, 2(结果完全一样)
}
1
2
3
4
5
6
7
// 唯一有区别的场景:表达式的返回值被使用
let arr = [1, 2, 3];
let i = 0;

// 使用后置++
console.log(arr[i++]); // arr[0] = 1,然后 i 变成 1
console.log(arr[i++]); // arr[1] = 2,然后 i 变成 2
1
2
3
4
5
6
// 用 ++i 的话
let arr2 = [1, 2, 3];
let j = 0;

console.log(arr2[++j]); // j 先变成1,然后 arr[1] = 2
console.log(arr2[++j]); // j 先变成2,然后 arr[2] = 3
1
2
3
4
5
// 性能方面:现代 JavaScript 引擎会优化,没有区别
// 但在某些极致的性能敏感场景下,V8 引擎对 ++i 的优化更好一点点

// 推荐:使用 ++i 而不是 i++(风格更一致,表达更清晰)
// 因为前置++先改变值再使用,逻辑更直观
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
// 实用建议

// 1. 在 for 循环中,两者都可以,看个人习惯
for (let i = 0; i < 5; i++) {}  // 常见写法
for (let i = 0; i < 5; ++i) {}  // 也可以

// 2. 如果要使用表达式的值,小心选择
let k = 0;
while (k < 3) {
    // 用后置++:先使用当前值,再递增
    console.log(arr[k++]); // arr[0], arr[1], arr[2]

    // 用前置++:先递增,再使用
    // console.log(arr[++k]); // arr[1], arr[2], arr[3](越界!)
}

// 3. 不确定的时候,用 i += 1 代替 ++i
for (let i = 0; i < 5; i += 1) {} // 最清晰,但有点啰嗦
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// 完整示例:遍历数组的两种方式
const fruits = ["苹果", "香蕉", "橙子", "葡萄"];

// 方式1:后置++(常用)
for (let i = 0; i < fruits.length; i++) {
    console.log(fruits[i]);
}

// 方式2:前置++(也可以)
for (let i = 0; i < fruits.length; ++i) {
    console.log(fruits[i]);
}

// 方式3:用 while + 后置++
let idx = 0;
while (idx < fruits.length) {
    console.log(fruits[idx++]); // 使用后置++,idx在访问后才递增
}

// 方式4:用 while + 前置++
idx = 0;
while (idx < fruits.length) {
    console.log(fruits[idx]); // 先打印当前
    ++idx;                     // 后置++写成单独一行
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
// 特殊场景:对象属性的自增
const counter = { value: 0 };

// 前置++
console.log(++counter.value); // 1
console.log(counter.value);   // 1

// 后置++
console.log(counter.value++); // 1
console.log(counter.value);   // 2
1
2
3
4
5
// 自减同理
let num = 5;
console.log(--num); // 4(先减后用)
console.log(num--); // 4(先用后减)
console.log(num);   // 3

本章小结

本章我们深入理解了自增自减运算符:

  1. ++a(前置):先加 1,再返回新值。
  2. a++(后置):先返回当前值的副本,再加 1。
  3. 循环中的选择:在 for 循环中两者效果相同;在需要使用表达式值时,根据需求选择。
  4. 实用建议:大多数情况下用 i++++i 都可以,但在表达式中使用时要格外小心。遇到复杂场景,拆成多行代码更清晰。

下一章,我们将学习 JavaScript 中最重要的数据结构——数组。准备好了吗?继续冲!