Skip to content

运算符(Day2 4/4) ​

本页对应 Day02_JavaScript,共 5 段视频,约 46 分钟。

学习目标 ​

  • 能说明赋值运算符的操作数个数、左操作数必须是变量、右操作数类型不限,并判断赋值表达式的值及其副作用。
  • 能正确使用 +=、-=、*=、/=、%= 等复合赋值运算符,将其改写为等价展开形式并预测变量与表达式的值。
  • 能说出位运算符按二进制位运算的基本规则,使用 & 和 | 对简单整数进行按位与、按位或计算并解释结果。
  • 能使用 typeof 判断数据的基本类型,说明其操作数个数、返回值类型和无副作用,并正确书写以避免语法歧义。
  • 能使用逗号运算符在同一语句中声明或修改多个变量,区分逗号运算符与函数参数分隔符,并判断逗号表达式的结果。
  • 能根据操作数中是否含字符串,区分 + 的相加、正号、字符串连接作用,正确预测字符串连接及 += 字符串连接赋值的结果。

知识点 ​

赋值运算符:等号不是“判等”,而是“赋值” ​

转写里的“负值运算符”是口误,准确叫赋值运算符 =。为什么需要它?因为程序要能把一个值保存到变量里,后续才能修改、计算。怎么想?把 = 看成一个二元运算符:左边是变量,右边是数据、变量或表达式;运算结果是“赋值后左边变量的值”,并且它会修改左边变量,所以有副作用。怎么做?

js
let number = 150;
number = 600;
console.log(number = "hello"); // "hello"
console.log(number);           // "hello"

这里 number = "hello" 不只是“把 hello 给 number”,它整体也是一个表达式,表达式的值就是赋值后的 "hello"。实际开发中我们 99% 只用它的副作用,不取表达式值。易错点:左边必须是变量,100 = 200 不合法;另外 let number = 600 是声明语句,不要把它当成可随意嵌套输出的表达式。转写中“带把的”应理解为带声明关键字的语句形式,语句和表达式要区分。

复合赋值运算符:+= 只是简写 ​

为什么需要?n = n + 1 写起来啰嗦,于是有 +=、-=、*=、/=、%=。怎么想?n += 1 等价于 n = n + 1,n -= 5 * 10 等价于 n = n - (5 * 10),右边整体先算。怎么做?

js
let n = 100;
n += 1;        // 101
n += 2;        // 103
n -= 5 * 10;   // 53
n %= 8;        // 5
console.log(n); // 5

它同样有副作用,修改的是左边变量;表达式值也是赋值后左边变量的值:

js
let m = 5;
let r = (m *= 2);
console.log(r, m); // 10 10

易错点:左边仍必须是变量,右边无所谓;不熟时可在注释里写展开式,例如 n %= 8; // n = n % 8。

位运算符:把数转成二进制后逐位算 ​

为什么需要?转写里说得很清楚:JS 应用基本不用,但要知道它存在,因为很多语言有这类底层运算。怎么想?& 是按位与,| 是按位或,操作数先转二进制,再逐位对齐计算。怎么做?

js
let n1 = 11, n2 = 7;
console.log(n1 & n2); // 3
console.log(n1 | n2); // 15

推导:11 是 1011,7 是 0111。按位与得 0011,即 3;按位或得 1111,即 15。转写里的“暗慰语”应改为“按位与”,“备货”应改为“按位或”。易错点:& 和逻辑与 && 不是一回事,| 和 || 也不是一回事;不要用算术直觉猜结果。

typeof:一元类型判断 ​

为什么需要?有时要判断一个数据是什么类型。怎么想?typeof 是一元运算符,只接一个操作数,返回表示类型的字符串,且没有副作用。怎么做?

js
let res = typeof 100;
console.log(res);        // "number"
console.log(typeof "abc"); // "string"

转写里的“开播”就是 typeof。易错点:typeof 100 中间要有空格,或写 typeof(100);写成 typeof100 会被当成一个变量名而报错。括号的主要作用是隔开运算符和操作数,并不是必须“函数式调用”。

逗号运算符:把多个表达式串起来 ​

为什么需要?想在一个表达式位置依次执行多个赋值。怎么想?逗号运算符有两个操作数,无类型要求,无副作用;整个表达式的值取最后一个操作数的值。怎么做?

js
let a = 100, b = 200, c = 300; // 声明里的逗号是分隔符
a = 1000, c = 2000;
console.log(a, b, c); // 1000 200 2000

let result = (a = 1000, c = 2000);
console.log(result); // 2000

易错点:console.log(1000, 2000) 里的逗号是参数分隔符,不是逗号运算符;要让它成为整体表达式,可加小括号。声明 let a = 100, b = 200 里的逗号也是声明分隔符,不要和逗号运算符混为一谈。

加号的三副面孔:正号、加法、字符串连接 ​

为什么需要?+ 既能做加法,又能连接字符串,还能作一元正号。怎么想?先看操作数个数:一个操作数是正号;两个操作数时,只要有一个是字符串,就是字符串连接,否则做加法。怎么做?

js
console.log(45 + "8");       // "458"
console.log(1000 + "hello"); // "1000hello"
console.log("hello" + 1000); // "hello1000"
console.log(true + false);   // 1

let num = 200;
num += "hello";
console.log(num); // "200hello"

转写里的“死针”应改为“字符串(string)”。易错点:45 + "8" 不是 53,而是字符串 "458";+= 也遵循同样规则,只要右边出现字符串,就会变成字符串连接赋值。判断 + 的作用时,先数操作数,再看有没有字符串。

题目回顾:先别急着算,先看运算符 ​

本节作业是写出下面代码的输出结果:

js
var n = 100;

console.log(n ++ && n --);   
console.log(n);              

console.log(-- n || n ++);   
console.log(n);

这道题表面上看只是 ++、--、&&、|| 的组合,实际考的是三个基础点:第一,自增自减会修改变量,也就是转写稿里反复强调的“副作用”;第二,自增自减组成的表达式本身有值;第三,&& 和 || 会发生短路,后面的代码不一定执行。转写稿里讲赋值运算符时说得很清楚:一个表达式运算完之后,要看两个操作数有没有被修改,被修改了就叫有副作用;并且表达式整体也有值。自增自减同样要按这个思路分析。

先建立判断顺序:先取值,再改值,还是先改值,再取值 ​

n++ 和 n-- 是“先用后改”:表达式的值取变量原来的值,然后变量再加一或减一。
--n 是“先改后用”:变量先减一,表达式的值取减完后的新值。

题目开始时:

js
var n = 100;

所以此时 n 是 100。后面每一步都要同时记录两个东西:表达式当前的值,以及 n 被改成了多少。不能只看 n 最后是多少,因为 console.log 输出的是整个表达式的值。

第一条:n++ && n-- ​

js
console.log(n ++ && n --);

先看左边 n++:它取 n 原来的值 100 作为表达式的值,然后 n 变成 101。所以左边得到 100。在 JavaScript 中,100 是真值,&& 左边为真时会继续看右边,并且整个 && 表达式的值取右边操作数的值。

再看右边 n--:此时 n 是 101,n-- 先取 101 作为表达式的值,然后 n 变成 100。因此右边表达式的值是 101,整个 n++ && n-- 的值就是 101。

所以第一条输出:

js
101

这里最容易错的地方是:有人算出 n 最后变成 100,就以为输出 100;也有人把 && 当成只输出 true。但 && 返回的是操作数的值,不是简单的布尔值。本题右边是 101,所以输出 101。

第二条:输出 n ​

js
console.log(n);

经过第一条后,n 先由 100 变成 101,再由 101 变成 100。所以第二条输出:

js
100

这一步是在检查你有没有跟踪副作用。转写稿里说“副作用指的是运算完成之后,操作数有没有被修改”,这里被修改的操作数就是变量 n。

第三条:--n || n++ ​

js
console.log(-- n || n ++);

此时 n 是 100。先算左边 --n:这是前置自减,先减再取值,所以 n 先变成 99,整个 --n 的值是 99。99 是真值,|| 左边为真时会发生短路,右边的 n++ 不再执行。

因此整个 --n || n++ 的值取左边的值 99,输出:

js
99

注意:因为短路,n++ 没有执行,所以 n 仍然是 99,不会因为右边写了个 n++ 就变成 100。这就是 || 的短路特性,也是本题第二个易错点。

第四条:再次输出 n ​

js
console.log(n);

第三条中 --n 已经把 n 从 100 改成 99,而右边的 n++ 被短路没有执行,所以最后 n 还是 99。输出:

js
99

最终答案与检查表 ​

完整输出结果是:

js
101
100
99
99

可以把本题的判断规则压缩成四句话:

  1. n++、n--:先取原值,再修改 n。
  2. --n:先修改 n,再取新值。
  3. &&:左边为真看右边,整个表达式的值取右边;左边为假直接返回左边。
  4. ||:左边为真直接返回左边并短路;左边为假才看右边。

易错点提醒 ​

第一,不要把 &&、|| 的结果一律写成 true 或 false。本题中它们返回的是操作数的值,所以会出现 101、99。第二,不要忽略自增自减对 n 的修改,每执行一次都要更新 n。第三,看到 || 左边已经为真,就要立刻停止,右边不执行。第四,转写稿里提到自增自减这类有副作用的运算符,操作数必须是变量形式,不能写成 100++ 这种形式;本题操作数都是变量 n,语法没有问题。把“表达式值”和“变量最终值”分开记录,这道作业就不会绕晕。

课堂画面 ​

课堂画面 1

课堂画面 2

课堂画面 3

课堂画面 4

课堂画面 5

代码示例 ​

1. 作业题:自增自减与逻辑运算 ​

html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <title>作业:自增自减与逻辑运算</title>
</head>
<body>
  <pre id="output"></pre>

  <script>
    const output = document.getElementById('output');

    function log(value) {
      output.textContent += value + '\n';
      console.log(value);
    }

    var n = 100;

    log(n ++ && n --);
    log(n);

    log(-- n || n ++);
    log(n);
  </script>
</body>
</html>

输出结果:

text
101
100
99
99

要点解释:

  • n ++ && n --:n++ 先返回 100,n 变成 101;100 是真值,继续执行 n--,n-- 返回 101,n 变成 100,整个 && 表达式的值是 101。
  • console.log(n):此时 n 是 100。
  • -- n || n ++:--n 先把 n 减为 99,并返回 99;99 是真值,|| 发生短路,右边的 n++ 不执行,整个表达式值是 99。
  • console.log(n):因为 n++ 没有执行,所以 n 仍是 99。
  • 易错点:&& 和 || 返回的不一定是布尔值,而是参与运算的操作数本身;后缀 n-- 返回的是减之前的值,前缀 --n 返回的是减之后的值。

2. 赋值运算符、复合赋值、字符串连接与逗号运算符 ​

html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <title>赋值运算符与字符串连接</title>
</head>
<body>
  <pre id="output"></pre>

  <script>
    const output = document.getElementById('output');

    function log(value) {
      output.textContent += value + '\n';
      console.log(value);
    }

    var number = 150;
    number = 600;
    number = 'hello';
    log(number);

    var n = 100;
    n += 1;
    log(n);

    n += 2;
    log(n);

    n -= 5 * 10;
    log(n);

    n %= 8;
    log(n);

    var a = 5;
    log(a *= 2);
    log(a);

    var A = 1000;
    log(A + 'hello');
    log(45 + '8');
    log(1 + 2);

    var B = 200;
    B += 'hello';
    log(B);

    var x = 100, y = 200, z = 300;
    x = 1000, z = 2000;
    log(x);
    log(z);

    var res = (x = 1000, z = 2000);
    log(res);

    console.log(1000, 200);
  </script>
</body>
</html>

输出结果:

text
hello
101
103
53
5
10
10
1000hello
458
3
200hello
1000
2000
2000

要点解释:

  • number = 600、number = 'hello':赋值运算符左侧必须是变量,右侧可以是直接量、变量或表达式。
  • n += 1 等价于 n = n + 1;n += 2 等价于 n = n + 2。
  • n -= 5 * 10 等价于 n = n - (5 * 10),右侧先计算为 50,所以 103 - 50 = 53。
  • n %= 8 等价于 n = n % 8,53 % 8 = 5。
  • a *= 2:执行前 a 是 5,执行后 a 是 10,整个赋值表达式的值也是 10。
  • A + 'hello'、45 + '8':只要 + 两侧有一个操作数是字符串,就执行字符串连接,数字会被转成字符串,所以 45 + '8' 是 '458',不是 53。
  • 1 + 2:两个操作数都不是字符串,执行数字相加,结果是 3。
  • B += 'hello':+= 同样遵循字符串连接规则,结果是 '200hello'。
  • var res = (x = 1000, z = 2000):逗号运算符返回最后一个表达式的值,所以 res 是 2000。
  • console.log(1000, 200):函数小括号里的逗号是参数分隔符,不是逗号运算符。
  • 易错点:复合赋值左侧必须是变量,不能写成 100 += 1;字符串连接时不要把 '8' 自动当成数字 8。

3. 位运算符与 typeof ​

html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <title>位运算符与 typeof</title>
</head>
<body>
  <pre id="output"></pre>

  <script>
    const output = document.getElementById('output');

    function log(value) {
      output.textContent += value + '\n';
      console.log(value);
    }

    var n1 = 11;
    var n2 = 7;

    log(n1 & n2);
    log(n1 | n2);

    var res = typeof 100;
    log(res);

    log(typeof 'hello');

    // 易错写法,会报错,不要取消注释:
    // console.log(typeof100);
  </script>
</body>
</html>

输出结果:

text
3
15
number
string

要点解释:

  • n1 & n2:按位与。11 的二进制是 1011,7 的二进制补成 0111,逐位与得到 0011,即 3。
  • n1 | n2:按位或。1011 | 0111 得到 1111,即 15。
  • typeof 100:typeof 是一元运算符,用来判断数据类型,表达式值是字符串 'number'。
  • typeof 'hello':结果是字符串 'string'。
  • typeof 标准写法是 typeof 100 或 typeof(100),运算符和操作数之间要留空格或用小括号隔开。
  • 易错点:写成 typeof100 时,浏览器会把 typeof100 当成一个变量名,导致语法错误。
  • 位运算符在实际 JS 业务开发中很少使用,这里重点理解“先转二进制,再逐位运算”的规则。

常见错误 ​

  1. 现象:console.log(var n = 100) 报语法错误。
    原因:var n = 100 是声明语句,不能整体当表达式输出。
    修法:声明与输出分开写,如 var n = 100; console.log(n);。

  2. 现象:n++ && n-- 的 n 最终值算错。
    原因:忽略逻辑与短路,且未记录 ++/-- 返回旧值与副作用后的新值。
    修法:先标当前值,再判断左操作数真假;若短路,右侧不执行。

  3. 现象:写 100 += 1 或 5++ 报错。
    原因:复合赋值和自增自减会对左操作数产生副作用,左操作数必须是变量。
    修法:左侧改为变量,如 var n = 100; n += 1;。

  4. 现象:typeof100 被当成变量名或报错。
    原因:typeof 与操作数连写后解析异常。
    修法:写成 typeof 100 或 typeof(100)。

  5. 现象:console.log(a = 1, b = 2) 以为输出逗号表达式的值。
    原因:函数小括号内的逗号是参数分隔符,不是逗号运算符。
    修法:若要整体作为逗号表达式,加括号:console.log((a = 1, b = 2));。

  6. 现象:把 45 + "8" 算成 53。
    原因:二元 + 中只要有一个操作数是字符串,就执行字符串连接。
    修法:需要数值相加时显式转换,如 45 + Number("8")。

练习 ​

  1. 写出输出结果:
js
var a = 5;
console.log(a += 2 * 3);
console.log(a);

答案:11、11。

  1. 写出输出结果:
js
var b = 10;
console.log(b++ || b--);
console.log(b);

答案:10、11。

  1. 写出输出结果:
js
console.log(typeof 100);
console.log(typeof(100));
console.log(typeof "100");

答案:number、number、string。

  1. 写出输出结果:
js
var x = 1, y = 2;
console.log((x = 10, y = 20));
console.log(x, y);

答案:20、10 20。

  1. 写出输出结果:
js
console.log(45 + "8");
console.log(45 + 8);
console.log(11 & 7);
console.log(11 | 7);

答案:458、53、3、15。

原视频对照 ​

#视频时长
1赋值运算符23:53
2位运算符06:35
3其他运算符-typeof02:44
4其他运算符-逗号运算符07:00
5其他运算符-字符串连接符05:45

小结 ​

  1. 分析赋值类表达式时,要同时看“表达式值”和“变量被修改后的值”。
  2. 遇到 ++/-- 与 &&/|| 混合,先判断短路,再决定右侧是否执行。
  3. 复合赋值左侧必须是变量,右侧可以是直接量、变量或表达式。
  4. typeof 建议加空格或括号,避免连写造成解析错误。
  5. 逗号是否为运算符要看位置,函数参数括号内优先视为参数分隔符。
  6. 二元 + 先检查是否有字符串操作数,有则连接,无则按数值相加。
  7. 位运算了解规则即可,不要与逻辑运算混淆。
  8. 做题时养成“拆表达式、标当前值、按优先级和短路逐步计算”的习惯。