运算符
# 03.运算符
# 目录介绍
# 3.1 算术运算符
算术运算符用于执行基本的数学运算。JavaScript 提供了以下算术运算符:
| 运算符 | 描述 | 示例 |
|---|---|---|
+ | 加法 | 3 + 2 → 5 |
- | 减法 | 5 - 2 → 3 |
* | 乘法 | 3 * 2 → 6 |
/ | 除法 | 6 / 2 → 3 |
% | 取余 | 5 % 2 → 1 |
** | 指数(幂) | 2 ** 3 → 8 |
++ | 自增 | let a = 1; a++ → 2 |
-- | 自减 | let a = 1; a-- → 0 |
# 3.1.1 加法运算符
加法运算符(+)是最常见的运算符,用来求两个数值的和。
1 + 1 // 2
true + true // 2
1 + true // 2
'a' + 'bc' // "abc"
2
3
4
加法运算符是在运行时决定,到底是执行相加,还是执行连接。也就是说,运算子的不同,导致了不同的语法行为,这种现象称为"重载"(overload)。由于加法运算符存在重载,可能执行两种运算,使用的时候必须很小心。
加法运算符的底层原理:V8 引擎在执行 + 运算时,会先对两个操作数调用内部的 ToPrimitive() 抽象操作,将对象转为原始值。如果转换结果中有一个是字符串,则另一个也被转为字符串,执行字符串拼接(String Concatenation);否则两个操作数都被转为数字执行数值加法。这就解释了为什么 '3' + 4 + 5 结果是 "345"(从左到右,'3' + 4 先变成 "34",再 "34" + 5 变成 "345"),而 3 + 4 + '5' 结果是 "75"(3 + 4 先算出 7,再 7 + '5' 变成 "75")。
'3' + 4 + 5 // "345"
3 + 4 + '5' // "75"
2
ToPrimitive 转换过程详解:
// 对象转原始值的步骤:
// 1. 如果有 [Symbol.toPrimitive] 方法,优先调用
// 2. 否则,hint 为 'number' 时:先 valueOf(),再 toString()
// 3. hint 为 'string' 时:先 toString(),再 valueOf()
// 4. hint 为 'default'(+ 运算符)时:同 'number'
// [] 的转换过程
[].valueOf() // [] (返回自身,不是原始值)
[].toString() // "" (空字符串,是原始值,使用它)
// {} 的转换过程
({}).valueOf() // {} (返回自身)
({}).toString() // "[object Object]"
// 因此:
[] + [] // "" + "" = ""
[] + {} // "" + "[object Object]" = "[object Object]"
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 3.1.2 余数运算符
余数运算符(%)返回前一个运算子被后一个运算子除,所得的余数。
12 % 5 // 2
// 需要注意的是,运算结果的正负号由第一个运算子的正负号决定。
-1 % 2 // -1
1 % -2 // 1
// 余数运算符还可以用于浮点数的运算。但是,由于浮点数不是精确的值,无法得到完全准确的结果。
6.5 % 2.1 // 0.19999999999999973
2
3
4
5
6
7
8
实际应用场景:
// 1. 判断奇偶
function isEven(n) {
return n % 2 === 0;
}
// 2. 循环索引(轮播图、环形缓冲区)
function getCircularIndex(index, length) {
return ((index % length) + length) % length; // 处理负数
}
console.log(getCircularIndex(-1, 5)); // 4
console.log(getCircularIndex(7, 5)); // 2
// 3. 时间格式化
function formatTime(totalSeconds) {
const hours = Math.floor(totalSeconds / 3600);
const minutes = Math.floor((totalSeconds % 3600) / 60);
const seconds = totalSeconds % 60;
return `${hours}:${String(minutes).padStart(2, '0')}:${String(seconds).padStart(2, '0')}`;
}
console.log(formatTime(3725)); // "1:02:05"
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 3.1.3 自增和自减
自增和自减运算符,是一元运算符,只需要一个运算子。它们的作用是将运算子首先转为数值,然后加上1或者减去1。它们会修改原始变量。
var x = 1;
++x // 2
x // 2
--x // 1
x // 1
2
3
4
5
6
前置和后置的区别——这是面试高频考点:
let a = 5;
let b = a++; // 后置:先返回 a 的当前值(5),再 a 加 1
console.log(a, b); // 6, 5
let c = 5;
let d = ++c; // 前置:先 c 加 1,再返回新值(6)
console.log(c, d); // 6, 6
// 复杂表达式中的自增(面试题)
let i = 1;
let result = i++ + ++i;
// i++ 返回 1(i变为2),++i 返回 3(i先变为3)
// result = 1 + 3 = 4
console.log(result); // 4
console.log(i); // 3
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 3.1.4 指数运算符
ES2016 引入了指数运算符 **,替代 Math.pow():
2 ** 3 // 8
2 ** 0.5 // 1.4142135623730951(等同于 Math.sqrt(2))
10 ** -2 // 0.01
// 右结合性(从右向左计算)
2 ** 3 ** 2 // 等于 2 ** (3 ** 2) = 2 ** 9 = 512
// 而不是 (2 ** 3) ** 2 = 8 ** 2 = 64
// 指数赋值
let base = 2;
base **= 10;
console.log(base); // 1024
2
3
4
5
6
7
8
9
10
11
12
# 3.2 赋值运算符
赋值运算符用于为变量赋值。
# 3.2.1 基本赋值
赋值运算符(Assignment Operators)用于给变量赋值。最常见的赋值运算符,当然就是等号(=)。
// 将 1 赋值给变量 x
var x = 1;
// 将变量 y 的值赋值给变量 x
var x = y;
2
3
4
5
# 3.2.2 复合赋值
| 运算符 | 描述 | 示例 |
|---|---|---|
= | 赋值 | let a = 5 |
+= | 加后赋值 | a += 2 → a = a + 2 |
-= | 减后赋值 | a -= 2 → a = a - 2 |
*= | 乘后赋值 | a *= 2 → a = a * 2 |
/= | 除后赋值 | a /= 2 → a = a / 2 |
%= | 取余后赋值 | a %= 2 → a = a % 2 |
**= | 指数后赋值 | a **= 2 → a = a ** 2 |
&&= | 逻辑与赋值(ES2021) | a &&= b → a && (a = b) |
||= | 逻辑或赋值(ES2021) | a ||= b → a || (a = b) |
??= | 空值合并赋值(ES2021) | a ??= b → a ?? (a = b) |
// ES2021 逻辑赋值运算符
let config = { debug: false, verbose: null };
// ||= 在左侧为 falsy 时赋值
config.debug ||= true; // debug 为 false(falsy),赋值为 true
console.log(config.debug); // true
// ??= 仅在左侧为 null/undefined 时赋值
config.verbose ??= 'normal'; // verbose 为 null,赋值为 'normal'
console.log(config.verbose); // 'normal'
// &&= 在左侧为 truthy 时赋值
let user = { name: 'Alice', token: 'abc123' };
user.token &&= refreshToken(); // token 存在时才刷新
2
3
4
5
6
7
8
9
10
11
12
13
14
# 3.2.3 解构赋值
ES6 引入的解构赋值是一种从数组或对象中提取值的简洁语法:
// 数组解构
const [a, b, c] = [1, 2, 3];
console.log(a, b, c); // 1 2 3
// 跳过元素
const [first, , third] = [1, 2, 3];
console.log(first, third); // 1 3
// 默认值
const [x = 10, y = 20] = [1];
console.log(x, y); // 1 20
// 交换变量(不需要临时变量!)
let m = 1, n = 2;
[m, n] = [n, m];
console.log(m, n); // 2 1
// 剩余元素
const [head, ...tail] = [1, 2, 3, 4, 5];
console.log(head); // 1
console.log(tail); // [2, 3, 4, 5]
// 对象解构
const { name, age, job = 'Developer' } = { name: 'Alice', age: 25 };
console.log(name, age, job); // Alice 25 Developer
// 重命名
const { name: userName, age: userAge } = { name: 'Alice', age: 25 };
console.log(userName, userAge); // Alice 25
// 嵌套解构
const { address: { city, zip } } = {
address: { city: 'Beijing', zip: '100000' }
};
console.log(city, zip); // Beijing 100000
// 函数参数解构
function greet({ name, greeting = 'Hello' }) {
console.log(`${greeting}, ${name}!`);
}
greet({ name: 'Alice' }); // Hello, Alice!
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
底层原理:解构赋值在编译时被转换为普通的属性访问语句。例如 const { a, b } = obj 会被转换为类似 const a = obj.a; const b = obj.b; 的代码。数组解构使用迭代器协议——任何可迭代对象(实现了 [Symbol.iterator])都可以被数组解构。
# 3.3 比较运算符
比较运算符用于比较两个值,返回布尔值(true 或 false)。
| 运算符 | 描述 | 示例 |
|---|---|---|
== | 等于(值相等) | 5 == '5' → true |
=== | 严格等于(值和类型都相等) | 5 === '5' → false |
!= | 不等于 | 5 != '5' → false |
!== | 严格不等于 | 5 !== '5' → true |
> | 大于 | 5 > 3 → true |
< | 小于 | 5 < 3 → false |
>= | 大于等于 | 5 >= 5 → true |
<= | 小于等于 | 5 <= 3 → false |
# 3.3.1 相等运算符
描述:比较两个值是否相等(会进行类型转换)。
1 == 1.0
// 等同于
1 === 1.0
2
3
相等运算符隐藏的类型转换,会带来一些违反直觉的结果。
0 == '' // true
0 == '0' // true
2 == true // false
2 == false // false
false == 'false' // false
false == '0' // true
false == undefined // false
false == null // false
null == undefined // true
' \t\r\n ' == 0 // true
2
3
4
5
6
7
8
9
10
11
12
13
14
# 3.3.2 严格等于
描述:比较两个值是否相等(不会进行类型转换,值和类型都必须相同)。
如果两个值的类型不同,直接返回false。
1 === "1" // false
true === "true" // false
2
同一类型的原始类型的值(数值、字符串、布尔值)比较时,值相同就返回true,值不同就返回false。
1 === 0x1 // true
undefined和null与自身严格相等。
undefined === undefined // true
null === null // true
2
# 3.3.3 相等比较的底层算法
==(抽象相等比较算法 Abstract Equality Comparison)的完整规则:
1. 如果两个操作数类型相同,执行严格相等比较 ===
2. null == undefined → true
3. number == string → 将 string 转为 number 再比较
4. boolean == 其他 → 将 boolean 转为 number(true→1, false→0)再比较
5. object == string/number → 将 object 转为原始值(ToPrimitive)再比较
2
3
4
5
// 用规则推导:'0' == false
// 规则4: false 转为 0 → '0' == 0
// 规则3: '0' 转为 0 → 0 == 0
// 规则1: 同类型比较 → true
// 用规则推导:[] == false
// 规则4: false 转为 0 → [] == 0
// 规则5: [] 转为原始值 → '' == 0
// 规则3: '' 转为 0 → 0 == 0
// 规则1: 同类型比较 → true
// 用规则推导:null == 0
// 规则2只说 null == undefined 为 true
// null 和 0 不匹配任何规则 → false
2
3
4
5
6
7
8
9
10
11
12
13
14
===(严格相等比较)和 Object.is() 的区别:
// === 的两个特殊情况
NaN === NaN // false(这是 IEEE 754 的规定)
+0 === -0 // true
// Object.is() 修正了这两个特殊情况
Object.is(NaN, NaN) // true
Object.is(+0, -0) // false
// 实际开发中的选择
// 99% 的场景用 ===
// 需要区分 +0/-0 或判断 NaN 时用 Object.is()
2
3
4
5
6
7
8
9
10
11
# 3.4 逻辑运算符
逻辑运算符用于组合或操作布尔值。
# 3.4.1 逻辑与和逻辑或
| 运算符 | 描述 | 示例 |
|---|---|---|
&& | 逻辑与 | true && false → false |
|| | 逻辑或 | true || false → true |
! | 逻辑非 | !true → false |
# 3.4.2 短路求值原理
短路求值的底层原理:逻辑运算符 && 和 || 在 JavaScript 中实现了短路求值(Short-circuit Evaluation)。关键点是它们返回的是操作数的值本身,而不是布尔值。
// && 返回第一个 falsy 值,或最后一个值
console.log(1 && 2 && 3); // 3(全部truthy,返回最后一个)
console.log(1 && 0 && 3); // 0(遇到第一个falsy就返回)
console.log(null && 'hello'); // null
// || 返回第一个 truthy 值,或最后一个值
console.log(0 || '' || 'hello'); // 'hello'
console.log(0 || '' || null); // null(全部falsy,返回最后一个)
// 实际应用
// 1. 短路保护(安全访问属性)
const name = user && user.profile && user.profile.name;
// 2. 设置默认值(注意 0、''、false 也会触发)
const port = config.port || 3000;
// 3. 条件执行
isReady && doSomething(); // 等价于 if(isReady) doSomething()
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 3.4.3 空值合并运算符
ES2020 引入的 ?? 运算符专门解决 || 对 falsy 值误判的问题:
// || 的问题:0、''、false 也被当作"空"
const count1 = 0 || 10; // 10(不符合预期!0 是有效值)
const count2 = '' || 'N/A'; // 'N/A'(不符合预期!空字符串可能是有效值)
// ?? 只在 null/undefined 时才使用右侧值
const count3 = 0 ?? 10; // 0(正确!)
const count4 = '' ?? 'N/A'; // ''(正确!)
const count5 = null ?? 10; // 10
const count6 = undefined ?? 10; // 10
const count7 = false ?? true; // false(false不是null/undefined)
// 实际应用:配置合并
function getConfig(userConfig) {
return {
timeout: userConfig.timeout ?? 5000,
retries: userConfig.retries ?? 3,
debug: userConfig.debug ?? false, // 用 || 这里会出错
};
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 3.4.4 逻辑赋值运算符
ES2021 引入的逻辑赋值运算符结合了逻辑运算和赋值:
// ||= 逻辑或赋值
let a = null;
a ||= 'default'; // a = a || 'default' → 'default'
// &&= 逻辑与赋值
let b = 'hello';
b &&= b.toUpperCase(); // b 有值时才执行 → 'HELLO'
let c = null;
c &&= c.toUpperCase(); // c 为 null,不执行 → null
// ??= 空值合并赋值
let config = {};
config.port ??= 3000; // port 不存在(undefined) → 3000
config.debug ??= false; // debug 不存在 → false
config.port ??= 8080; // port 已经是 3000,不覆盖
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 3.5 位运算符
位运算符对操作数的二进制表示进行操作。JavaScript 中的数字在进行位运算前会被转换为32位有符号整数。
# 3.5.1 位运算基础
| 运算符 | 描述 | 示例 |
|---|---|---|
& | 按位与 | 5 & 1 → 1 |
\| | 按位或 | 5 \| 1 → 5 |
^ | 按位异或 | 5 ^ 1 → 4 |
~ | 按位非 | ~5 → -6 |
<< | 左移 | 5 << 1 → 10 |
>> | 右移 | 5 >> 1 → 2 |
>>> | 无符号右移 | -5 >>> 1 → 2147483645 |
// 5 的二进制: 00000101
// 1 的二进制: 00000001
// 按位与(都为1才是1)
5 & 1 // 00000101 & 00000001 = 00000001 = 1
// 按位或(有一个1就是1)
5 | 1 // 00000101 | 00000001 = 00000101 = 5
// 按位异或(不同为1,相同为0)
5 ^ 1 // 00000101 ^ 00000001 = 00000100 = 4
// 按位非(取反,然后结果是 -(n+1))
~5 // -(5+1) = -6
// 左移(相当于乘以2的n次方)
5 << 1 // 00000101 << 1 = 00001010 = 10
5 << 2 // 00000101 << 2 = 00010100 = 20
// 右移(相当于除以2的n次方并取整)
5 >> 1 // 00000101 >> 1 = 00000010 = 2
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 3.5.2 位运算的实际应用
// 1. 快速取整(比 Math.floor 快,但只适用于32位范围内的数字)
console.log(3.7 | 0); // 3
console.log(-3.7 | 0); // -3(注意:不同于Math.floor(-3.7)=-4)
console.log(~~3.7); // 3(双按位非也可以取整)
// 2. 判断奇偶(比 % 2 快)
console.log(5 & 1); // 1(奇数)
console.log(4 & 1); // 0(偶数)
// 3. 交换两个变量(不用临时变量,不用解构)
let a = 5, b = 3;
a ^= b; // a = 5 ^ 3 = 6
b ^= a; // b = 3 ^ 6 = 5
a ^= b; // a = 6 ^ 5 = 3
console.log(a, b); // 3 5
// 4. 权限系统(标志位)
const READ = 1; // 0001
const WRITE = 2; // 0010
const EXECUTE = 4; // 0100
const ADMIN = 8; // 1000
let permissions = READ | WRITE; // 0011 = 3
// 检查权限
console.log((permissions & READ) !== 0); // true
console.log((permissions & EXECUTE) !== 0); // false
// 添加权限
permissions |= EXECUTE; // 0111 = 7
// 移除权限
permissions &= ~WRITE; // 0101 = 5
// 5. RGB 颜色处理
function rgbToHex(r, g, b) {
return '#' + ((r << 16) | (g << 8) | b).toString(16).padStart(6, '0');
}
console.log(rgbToHex(255, 128, 0)); // "#ff8000"
function hexToRgb(hex) {
const num = parseInt(hex.slice(1), 16);
return {
r: (num >> 16) & 255,
g: (num >> 8) & 255,
b: num & 255,
};
}
console.log(hexToRgb('#ff8000')); // {r: 255, g: 128, b: 0}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
# 3.6 三元运算符
# 3.6.1 基本用法与嵌套
三元运算符(条件运算符)是 JavaScript 中唯一的三元运算符,格式为 条件 ? 表达式1 : 表达式2。
// 基本用法
const age = 20;
const status = age >= 18 ? '成年' : '未成年';
// 嵌套三元(不推荐超过2层)
const score = 85;
const grade = score >= 90 ? 'A'
: score >= 80 ? 'B'
: score >= 70 ? 'C'
: score >= 60 ? 'D'
: 'F';
// 三元运算符 vs if...else 的选择
// 三元:适合简单的值选择
const display = isVisible ? 'block' : 'none';
// if...else:适合有副作用的操作
if (isReady) {
startProcess();
logEvent('started');
} else {
showLoading();
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 3.7 类型运算符
# 3.7.1 typeof原理
typeof 运算符返回一个表示操作数类型的字符串。
console.log(typeof 42); // "number"
console.log(typeof 'hello'); // "string"
console.log(typeof true); // "boolean"
console.log(typeof undefined); // "undefined"
console.log(typeof null); // "object" ← 历史 bug!
console.log(typeof Symbol('id')); // "symbol"
console.log(typeof 123n); // "bigint"
console.log(typeof {}); // "object"
console.log(typeof []); // "object" ← 数组也返回 object
console.log(typeof function(){}); // "function"
2
3
4
5
6
7
8
9
10
typeof 的底层原理:在 JavaScript 的第一版实现中,值以32位为单位存储,前3位是类型标记(type tag):000 表示对象,001 表示整数,010 表示浮点数,100 表示字符串,110 表示布尔值。null 被表示为空指针(全0),所以它的类型标记也是 000(对象),因此 typeof null === "object"。这是一个25年前的 bug,但由于太多代码依赖了这个行为,TC39 委员会曾在 ES6 中提议修复但最终被否决。
# 3.7.2 instanceof原理
instanceof 运算符检测构造函数的 prototype 属性是否出现在对象的原型链上:
console.log([] instanceof Array); // true
console.log({} instanceof Object); // true
console.log(() => {} instanceof Function); // true
// instanceof 的本质:遍历原型链
// obj instanceof Constructor
// 等价于检查 Constructor.prototype 是否在 obj 的原型链上
// 手动实现 instanceof
function myInstanceof(obj, Constructor) {
let proto = Object.getPrototypeOf(obj);
while (proto !== null) {
if (proto === Constructor.prototype) return true;
proto = Object.getPrototypeOf(proto);
}
return false;
}
console.log(myInstanceof([], Array)); // true
console.log(myInstanceof([], Object)); // true(Array 继承自 Object)
// instanceof 的局限性:跨 iframe/realm 失效
// 因为不同 iframe 有各自独立的 Array 构造函数
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 3.8 其他运算符
# 3.8.1 可选链运算符
ES2020 引入的 ?. 运算符在访问深层嵌套属性时提供安全保障:
const user = {
profile: {
address: {
city: 'Beijing'
}
}
};
// 传统方式(冗长且容易出错)
const city1 = user && user.profile && user.profile.address && user.profile.address.city;
// 可选链(简洁安全)
const city2 = user?.profile?.address?.city; // 'Beijing'
// 如果中间有 null/undefined,直接返回 undefined 而不报错
const zip = user?.profile?.address?.zip; // undefined
const country = user?.location?.country; // undefined(不会报错)
// 可选链调用方法
const length = someArray?.length; // 如果数组存在则返回长度
const result = obj?.method?.(); // 如果方法存在则调用
// 可选链访问数组元素
const firstItem = arr?.[0];
// 配合空值合并运算符
const displayName = user?.profile?.name ?? 'Anonymous';
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
底层原理:?. 在引擎内部被转换为条件判断——a?.b 等价于 a == null ? undefined : a.b(注意是 == 不是 ===,所以 null 和 undefined 都会触发短路)。
# 3.8.2 展开运算符
... 展开运算符(Spread Operator)可以展开可迭代对象:
// 数组展开
const arr1 = [1, 2, 3];
const arr2 = [...arr1, 4, 5]; // [1, 2, 3, 4, 5]
// 对象展开(浅拷贝)
const obj1 = { a: 1, b: 2 };
const obj2 = { ...obj1, c: 3 }; // { a: 1, b: 2, c: 3 }
// 函数参数展开
function sum(a, b, c) { return a + b + c; }
console.log(sum(...[1, 2, 3])); // 6
// 合并数组(比 concat 更直观)
const merged = [...arr1, ...arr2];
// 字符串展开为字符数组
const chars = [..."hello"]; // ['h', 'e', 'l', 'l', 'o']
// 去重(配合 Set)
const unique = [...new Set([1, 2, 2, 3, 3])]; // [1, 2, 3]
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 3.8.3 逗号运算符
逗号运算符从左到右依次执行表达式,返回最后一个表达式的值:
let a = (1, 2, 3);
console.log(a); // 3
// 常见应用:for 循环中初始化多个变量
for (let i = 0, j = 10; i < j; i++, j--) {
console.log(i, j);
}
2
3
4
5
6
7
# 3.9 运算符优先级
# 3.9.1 优先级表
JavaScript 中的运算符优先级决定了表达式中运算的顺序。以下是完整的优先级(从高到低):
| 优先级 | 运算符 | 描述 |
|---|---|---|
| 1 | () | 分组 |
| 2 | . [] ?. () | 成员访问、可选链、函数调用 |
| 3 | new(带参数) | 构造函数调用 |
| 4 | ++ --(后缀) | 后缀自增/自减 |
| 5 | ! ~ + - typeof void delete await | 一元运算符 |
| 6 | ** | 指数(右结合) |
| 7 | * / % | 乘除余 |
| 8 | + - | 加减 |
| 9 | << >> >>> | 位移 |
| 10 | < <= > >= instanceof in | 比较 |
| 11 | == != === !== | 相等 |
| 12 | & | 按位与 |
| 13 | ^ | 按位异或 |
| 14 | \| | 按位或 |
| 15 | && | 逻辑与 |
| 16 | \|\| | 逻辑或 |
| 17 | ?? | 空值合并 |
| 18 | ?: | 条件(三元) |
| 19 | = += -= 等 | 赋值 |
| 20 | , | 逗号 |
// 常见的优先级陷阱
console.log(1 + 2 * 3); // 7(* 优先于 +)
console.log((1 + 2) * 3); // 9(括号最高优先级)
// && 优先于 ||
console.log(true || false && false); // true
// 等价于 true || (false && false) → true || false → true
// 赋值运算符优先级很低
let x = 1 + 2 * 3; // x = 7(先算右边)
2
3
4
5
6
7
8
9
10