JavaScript 运算符优先级:掌握计算顺序的规则
还记得小学数学课上的"先乘除后加减"吗?在 JavaScript 中,运算符也有类似的优先级规则。就像交通规则决定了谁先通行,运算符优先级决定了复杂表达式中哪个运算先执行。理解这些规则,能让你编写出正确且易读的代码。
为什么需要优先级?
看一个简单的例子:
javascript
let result = 3 + 4 * 5;
console.log(result); // 23,不是 35!为什么是 23 而不是 35?因为乘法 (*) 的优先级高于加法 (+),所以先计算 4 * 5 = 20,然后再加 3,得到 23。
如果我们想先计算加法,就需要使用括号:
javascript
let result = (3 + 4) * 5;
console.log(result); // 35优先级表(从高到低)
以下是常用运算符的优先级排序,数字越大优先级越高:
javascript
// 优先级 20:分组(括号)
(expression)
// 优先级 19:成员访问、函数调用
obj.property
obj[property]
func(args)
new Constructor()
// 优先级 17:后置递增/递减
x++
x--
// 优先级 16:逻辑非、一元运算符、前置递增/递减
!x
+x
-x
typeof x
++x
--x
// 优先级 15:幂运算
**
// 优先级 14:乘法、除法、取余
*
/
%
// 优先级 13:加法、减法
+
-
// 优先级 12:位移运算
<<
>>
>>>
// 优先级 11:关系运算符
<
<=
>
>=
in
instanceof
// 优先级 10:相等性运算符
==
!=
===
!==
// 优先级 9-5:位运算符
&
^
|
// 优先级 6:逻辑与
&&
// 优先级 5:逻辑或
||
// 优先级 4:空值合并
??
// 优先级 4:条件运算符
? :
// 优先级 3:赋值运算符
=
+=
-=
*=
/=
等等
// 优先级 1:逗号运算符
,常见优先级示例
算术运算符优先级
javascript
// 乘除优先于加减
console.log(2 + 3 * 4); // 14(先 3*4=12,再 2+12=14)
console.log(10 - 6 / 2); // 7(先 6/2=3,再 10-3=7)
console.log(8 / 2 + 3); // 7(先 8/2=4,再 4+3=7)
// 同级运算从左到右
console.log(10 - 3 - 2); // 5(先 10-3=7,再 7-2=5)
console.log(20 / 4 / 2); // 2.5(先 20/4=5,再 5/2=2.5)
// 幂运算优先于乘除
console.log(2 * 3 ** 2); // 18(先 3**2=9,再 2*9=18)
console.log(2 ** 3 * 4); // 32(先 2**3=8,再 8*4=32)
// 使用括号改变优先级
console.log((2 + 3) * 4); // 20
console.log(2 * (3 + 4)); // 14比较和逻辑运算符
javascript
// 比较优先于逻辑运算
let age = 25;
let hasLicense = true;
// 先执行 age >= 18,得到 true,再执行 true && hasLicense
console.log(age >= 18 && hasLicense); // true
// && 优先于 ||
console.log(true || (false && false)); // true(先 false && false = false,再 true || false = true)
console.log((true || false) && false); // false(改变优先级)
// ! 优先于 &&
let isWeekend = false;
let isHoliday = false;
console.log(!isWeekend && !isHoliday); // true(先执行两个 !)
// 实际应用
let score = 85;
let attendance = 90;
let passed = score >= 80 && attendance >= 85;
console.log(passed); // true赋值运算符优先级最低
赋值运算符的优先级很低,几乎总是最后执行:
javascript
let a, b, c;
// 先计算右边的表达式,最后赋值
a = 5 + 3; // 先 5+3=8,再赋值给 a
console.log(a); // 8
b = 10 > 5; // 先比较,得 true,再赋值给 b
console.log(b); // true
c = 2 + 3 * 4 > 10; // 先 3*4=12,再 2+12=14,再 14>10=true,最后赋值
console.log(c); // true一元运算符优先级
一元运算符优先级较高:
javascript
// typeof 优先级高于算术运算
console.log(typeof 5 + 3); // "number3"(先 typeof 5 = "number",再拼接)
console.log(typeof (5 + 3)); // "number"(改变优先级)
// ! 优先级很高
console.log(!true || false); // false(先 !true=false,再 false || false)
// 递增/递减
let x = 5;
let y = ++x * 2; // 先 ++x 得 6,再 6*2=12
console.log(y); // 12
console.log(x); // 6
let a = 5;
let b = a++ * 2; // 先使用 a=5 进行运算 5*2=10,再递增 a
console.log(b); // 10
console.log(a); // 6结合性(Associativity)
当运算符优先级相同时,结合性决定了计算顺序。大部分运算符是左结合(从左到右),少数是右结合(从右到左)。
左结合性
javascript
// 算术运算符:从左到右
console.log(10 - 5 - 2); // 3((10 - 5) - 2 = 5 - 2 = 3)
console.log(20 / 4 / 2); // 2.5((20 / 4) / 2 = 5 / 2 = 2.5)
// 加法:从左到右
console.log(1 + 2 + 3 + 4); // 10(((1 + 2) + 3) + 4)
// 字符串拼接
console.log("a" + "b" + "c"); // "abc"(("a" + "b") + "c")
console.log(1 + 2 + "3"); // "33"((1 + 2) + "3" = 3 + "3")
console.log("1" + 2 + 3); // "123"(("1" + 2) + 3 = "12" + 3)右结合性
javascript
// 赋值运算符:从右到左
let a, b, c;
a = b = c = 10;
// 执行顺序:c = 10,然后 b = 10,最后 a = 10
console.log(a, b, c); // 10 10 10
// 条件运算符:从右到左
let result = true ? 1 : false ? 2 : 3;
// 执行顺序:false ? 2 : 3 = 3,然后 true ? 1 : 3 = 1
console.log(result); // 1
// 幂运算:从右到左(这是特殊的!)
console.log(2 ** (3 ** 2)); // 512(2 ** (3 ** 2) = 2 ** 9 = 512)
console.log((2 ** 3) ** 2); // 64(改变结合性)复杂表达式分析
让我们分析一些复杂的表达式:
示例 1:混合运算
javascript
let result = 5 + 3 * 2 - 4 / 2;
// 分步执行:
// 1. 3 * 2 = 6(乘法优先)
// 2. 4 / 2 = 2(除法优先)
// 3. 5 + 6 = 11(加法)
// 4. 11 - 2 = 9(减法)
console.log(result); // 9示例 2:逻辑和比较
javascript
let x = 10;
let y = 20;
let z = 30;
let result = (x < y && y < z) || z < x;
// 分步执行:
// 1. x < y => 10 < 20 = true
// 2. y < z => 20 < 30 = true
// 3. z < x => 30 < 10 = false
// 4. true && true = true
// 5. true || false = true
console.log(result); // true示例 3:赋值和递增
javascript
let a = 5;
let b = a++ + ++a;
// 分步执行:
// 1. a++ 使用当前值 5,然后 a 变成 6
// 2. ++a 先递增(a 变成 7),然后使用 7
// 3. 5 + 7 = 12
// 4. 赋值给 b
console.log(b); // 12
console.log(a); // 7示例 4:条件和逻辑
javascript
let score = 85;
let result = score >= 90 ? "A" : score >= 80 ? "B" : "C";
// 分步执行(从右到左):
// 1. 内层条件:score >= 80 ? "B" : "C"
// 85 >= 80 为 true,所以得到 "B"
// 2. 外层条件:score >= 90 ? "A" : "B"
// 85 >= 90 为 false,所以得到 "B"
console.log(result); // "B"实际编程建议
1. 使用括号增强可读性
即使你知道优先级规则,为了让代码更易读,建议使用括号:
javascript
// 不够清晰
let result1 = a + b * c - d / e;
// 更清晰
let result2 = a + b * c - d / e;
// 复杂逻辑一定要用括号
let canAccess = (isAdmin || isModerator) && !isBanned;2. 避免过于复杂的表达式
javascript
// ❌ 不好:太复杂
let result = (a++ + (++b * c--) / --d && e > f) || g <= h ? i : j;
// ✅ 好:拆分成多个步骤
let temp1 = a++ + (++b * c--) / --d;
let temp2 = temp1 && e > f;
let result = temp2 || g <= h ? i : j;
// 或者更好:完全拆分
let multiplied = ++b * c--;
let divided = multiplied / --d;
let sum = a++ + divided;
let condition1 = sum && e > f;
let condition2 = condition1 || g <= h;
let result = condition2 ? i : j;3. 注意类型转换的陷阱
javascript
// 字符串拼接优先于数字相加
console.log(1 + 2 + "3"); // "33"
console.log("1" + 2 + 3); // "123"
console.log("1" + (2 + 3)); // "15"
// 使用括号明确意图
let total = price + tax; // 数字相加
let display = "Total: $" + (price + tax); // 先计算,再拼接4. 小心短路求值
javascript
// && 的短路特性
let user = null;
let name = user && user.name; // 安全:user 为 null,不会访问 name
// 但不要过度依赖
let value = obj && obj.prop && obj.prop.value; // 可读性差
// 使用可选链更清晰
let value = obj?.prop?.value;5. 赋值的返回值
赋值运算符会返回所赋的值,可以用于连续赋值,但要小心:
javascript
// 连续赋值
let a = (b = c = 0); // 都是 0
// 在条件中赋值(容易混淆)
if ((x = 10)) {
// 这是赋值!不是比较!
console.log("Always true");
}
// 应该是:
if (x === 10) {
console.log("Compare");
}
// 有意的条件赋值要用括号表明
if ((match = text.match(/pattern/))) {
console.log(match);
}优先级记忆技巧
基本规则
- 括号最高:
()总是先计算 - 成员访问和函数调用:
.[]() - 一元运算符:
!typeof++--+- - 幂运算:
**(右结合) - 乘除取余:
*/% - 加减:
+- - 比较:
<><=>= - 相等:
==!====!== - 逻辑与:
&& - 逻辑或:
|| - 条件:
? : - 赋值:
=+=等(右结合)
口诀(中文)
"括号成员一元幂,乘除加减比较等,逻辑与或条件赋"
实用原则
不需要记住所有优先级,记住常用的就够了:
- 乘除优于加减
- 比较优于逻辑
- && 优于 ||
- 赋值最后
- 不确定的地方用括号
常见错误
错误 1:忘记运算符优先级
javascript
// 错误
if (type === "user" || "admin") {
// 总是 true!
// ...
}
// 正确
if (type === "user" || type === "admin") {
// ...
}错误 2:前置/后置递增混淆
javascript
let i = 5;
// 容易混淆
let a = i++ * 2; // a = 10, i = 6
let b = ++i * 2; // b = 14, i = 7
// 建议单独写
i++;
let a = i * 2; // 更清晰错误 3:链式比较
javascript
// JavaScript 不支持链式比较
let x = 5;
console.log(1 < x < 10); // true,但不是你想的那样!
// 实际执行:
// 1 < x => 1 < 5 => true
// true < 10 => 1 < 10 => true
// 正确做法
console.log(1 < x && x < 10); // true总结
运算符优先级是 JavaScript 表达式求值的基础,理解它能避免很多隐蔽的 bug。
本节要点回顾:
- 优先级决定了不同运算符的执行顺序
- 结合性决定了相同优先级运算符的执行顺序
- 大部分运算符是左结合,赋值、条件、幂运算是右结合
- 括号
()的优先级最高,可以改变执行顺序 - 常用优先级:乘除 > 加减 > 比较 > 逻辑与 > 逻辑或 > 赋值
- 为了代码可读性,建议使用括号明确意图
- 避免编写过于复杂的表达式
- 不确定时,查文档或使用括号