运算符优先级
运算符优先级
运算符优先级决定了表达式中运算符的执行顺序。本文将介绍JavaScript中运算符的优先级和结合性规则,帮助您编写可预测的表达式。
优先级基础
在JavaScript中,表达式中的运算符会按照预定义的优先级顺序执行。优先级高的运算符会先于优先级低的运算符执行。
例如,在表达式 3 + 4 * 5
中,乘法运算符 *
的优先级高于加法运算符 +
,因此先计算 4 * 5
,然后再加上 3
,最终结果为 23
,而不是 35
。
console.log(3 + 4 * 5); // 23(先计算4 * 5 = 20,然后3 + 20 = 23)
console.log((3 + 4) * 5); // 35(括号改变了运算顺序)
运算符优先级表
下表按照优先级从高到低列出了JavaScript中的运算符。同一行的运算符具有相同的优先级。
优先级 | 运算符类型 | 运算符 | 结合性 |
---|---|---|---|
20 | 分组 | ( ... ) | n/a |
19 | 成员访问 | obj.prop | 从左到右 |
需计算的成员访问 | obj[expr] | 从左到右 | |
new (带参数列表) | new Func(args) | n/a | |
函数调用 | func(args) | 从左到右 | |
可选链 | ?. | 从左到右 | |
18 | new (无参数列表) | new Func | 从右到左 |
17 | 后置递增 | expr++ | n/a |
后置递减 | expr-- | n/a | |
16 | 逻辑非 | !expr | 从右到左 |
按位非 | ~expr | 从右到左 | |
一元加法 | +expr | 从右到左 | |
一元减法 | -expr | 从右到左 | |
前置递增 | ++expr | 从右到左 | |
前置递减 | --expr | 从右到左 | |
typeof | typeof expr | 从右到左 | |
void | void expr | 从右到左 | |
delete | delete expr | 从右到左 | |
await | await expr | 从右到左 | |
15 | 幂 | expr ** expr | 从右到左 |
14 | 乘法 | expr * expr | 从左到右 |
除法 | expr / expr | 从左到右 | |
取模 | expr % expr | 从左到右 | |
13 | 加法 | expr + expr | 从左到右 |
减法 | expr - expr | 从左到右 | |
12 | 按位左移 | expr << expr | 从左到右 |
按位右移 | expr >> expr | 从左到右 | |
无符号右移 | expr >>> expr | 从左到右 | |
11 | 小于 | expr < expr | 从左到右 |
小于等于 | expr <= expr | 从左到右 | |
大于 | expr > expr | 从左到右 | |
大于等于 | expr >= expr | 从左到右 | |
in | expr in expr | 从左到右 | |
instanceof | expr instanceof expr | 从左到右 | |
10 | 等于 | expr == expr | 从左到右 |
不等于 | expr != expr | 从左到右 | |
严格等于 | expr === expr | 从左到右 | |
严格不等于 | expr !== expr | 从左到右 | |
9 | 按位与 | expr & expr | 从左到右 |
8 | 按位异或 | expr ^ expr | 从左到右 |
7 | 按位或 | expr | expr | 从左到右 |
6 | 逻辑与 | expr && expr | 从左到右 |
5 | 逻辑或 | expr || expr | 从左到右 |
空值合并 | expr ?? expr | 从左到右 | |
4 | 条件(三元)运算符 | expr ? expr : expr | 从右到左 |
3 | 赋值 | expr = expr | 从右到左 |
赋值运算 | expr += expr | 从右到左 | |
expr -= expr | 从右到左 | ||
expr *= expr | 从右到左 | ||
expr /= expr | 从右到左 | ||
expr %= expr | 从右到左 | ||
expr **= expr | 从右到左 | ||
expr <<= expr | 从右到左 | ||
expr >>= expr | 从右到左 | ||
expr >>>= expr | 从右到左 | ||
expr &= expr | 从右到左 | ||
expr ^= expr | 从右到左 | ||
expr |= expr | 从右到左 | ||
expr &&= expr | 从右到左 | ||
expr ||= expr | 从右到左 | ||
expr ??= expr | 从右到左 | ||
2 | yield | yield expr | 从右到左 |
yield* | yield* expr | 从右到左 | |
1 | 逗号 | expr, expr | 从左到右 |
结合性
结合性决定了相同优先级的运算符如何分组。结合性有两种:
- 从左到右(左结合性):运算符从左到右分组
- 从右到左(右结合性):运算符从右到左分组
例如,减法运算符是左结合的:
console.log(5 - 2 - 1); // 2(先计算5 - 2 = 3,然后3 - 1 = 2)
而赋值运算符是右结合的:
let a, b, c;
a = b = c = 5; // 先计算c = 5,然后b = 5,最后a = 5
console.log(a, b, c); // 5 5 5
常见优先级规则示例
1. 算术运算符
console.log(2 + 3 * 4); // 14(先乘法,后加法)
console.log(2 * 3 + 4); // 10(先乘法,后加法)
console.log(2 * (3 + 4)); // 14(括号内先计算)
console.log(2 ** 3 * 4); // 32(先幂运算,后乘法)
console.log(2 * 3 ** 2); // 18(先幂运算,后乘法)
2. 比较运算符和逻辑运算符
console.log(5 > 3 && 2 < 4); // true(先比较,后逻辑与)
console.log(5 > 3 || 1 > 2); // true(先比较,后逻辑或)
console.log(!(5 > 3)); // false(先比较,后逻辑非)
console.log(true && false || true); // true(先逻辑与,后逻辑或)
console.log(true || false && false); // true(先逻辑与,后逻辑或)
3. 赋值和条件运算符
let a = 5;
let b = 10;
let c = a > b ? a : b; // 先比较,后条件运算
console.log(c); // 10
let d = 1, e = 2, f = 3;
d += e *= f; // 先计算e *= f(e变为6),然后d += 6(d变为7)
console.log(d, e); // 7 6
4. 一元运算符和成员访问
let arr = [1, 2, 3];
console.log(arr[0] + arr[1]); // 3(成员访问优先级高于加法)
let num = 5;
console.log(-num + 2); // -3(一元减法优先级高于加法)
console.log(++num + 2); // 8(前置递增优先级高于加法,num变为6,然后6 + 2 = 8)
console.log(num++ + 2); // 8(后置递增在表达式计算后执行,6 + 2 = 8,然后num变为7)
console.log(num); // 7
5. 函数调用和对象属性访问
function getValue() {
return { data: 42 };
}
console.log(getValue().data); // 42(函数调用优先级高于属性访问)
let obj = {
value: 10,
getValue: function() { return this.value; }
};
console.log(obj.getValue()); // 10(属性访问优先级高于函数调用)
使用括号明确优先级
虽然了解运算符优先级很重要,但在复杂表达式中,使用括号可以使代码更加清晰,并确保按照您期望的顺序执行操作:
// 不使用括号,依赖默认优先级
let result = 2 + 3 * 4 - 1; // 13
// 使用括号明确优先级
let result1 = 2 + (3 * 4) - 1; // 13(与默认优先级相同,但更清晰)
let result2 = (2 + 3) * (4 - 1); // 15(改变了计算顺序)
在逻辑表达式中,括号尤为重要:
// 不使用括号
if (x > 0 && y > 0 || z > 0) {
// 当x和y都大于0,或者z大于0时执行
}
// 使用括号更清晰
if ((x > 0 && y > 0) || z > 0) {
// 同上,但更明确
}
// 改变逻辑顺序
if (x > 0 && (y > 0 || z > 0)) {
// 当x大于0,并且y或z大于0时执行
}
常见陷阱和注意事项
1. 自增和自减运算符
前置和后置自增/自减运算符的行为不同:
let a = 5;
let b = 5;
let resultA = ++a; // 先增加a,然后返回新值
let resultB = b++; // 先返回原值,然后增加b
console.log(resultA, a); // 6 6
console.log(resultB, b); // 5 6
2. 逻辑运算符的短路行为
逻辑运算符具有短路行为,这可能影响表达式的计算:
// 逻辑与短路
console.log(false && someUndefinedVariable); // false,不会评估右侧表达式
// 逻辑或短路
console.log(true || someUndefinedVariable); // true,不会评估右侧表达式
// 空值合并运算符短路
console.log(0 ?? someUndefinedVariable); // 0,左侧不是null或undefined,不会评估右侧表达式
3. 加法运算符的类型转换
加法运算符对字符串和数字的处理不同:
console.log(1 + 2 + "3"); // "33"(先计算1 + 2 = 3,然后与"3"连接得到"33")
console.log("1" + 2 + 3); // "123"(先将"1"与2连接得到"12",然后与3连接得到"123")
4. 比较运算符的优先级
比较运算符的优先级高于赋值运算符,但低于算术运算符:
let x = 5;
let result = x < 10 + 5; // 等价于 x < (10 + 5),结果为true
console.log(result); // true
5. 逗号运算符
逗号运算符的优先级最低,它会计算所有操作数,但只返回最后一个操作数的值:
let x = (1, 2, 3, 4, 5); // x的值为5
console.log(x); // 5
// 不使用括号时要小心
let y = 1, 2, 3; // 语法错误!这被解释为声明多个变量,但缺少变量名
// 正确使用逗号运算符需要括号
let z = (1, 2, 3); // z的值为3
优先级与短路求值
JavaScript的逻辑运算符(&&
、||
和??
)具有短路求值特性,这与它们的优先级相结合,可能导致一些意外行为:
// 逻辑与的优先级高于逻辑或
console.log(true || false && false); // true(先计算false && false得到false,然后true || false得到true)
console.log(false || true && true); // true(先计算true && true得到true,然后false || true得到true)
// 使用括号改变优先级
console.log((true || false) && false); // false
空值合并运算符(??
)不能直接与&&
和||
一起使用,必须使用括号明确优先级:
// 错误:不允许直接与&&或||组合使用
// console.log(null || undefined ?? "default"); // SyntaxError
// 正确:使用括号明确优先级
console.log((null || undefined) ?? "default"); // "default"
console.log(null || (undefined ?? "default")); // "default"
运算符优先级与函数参数
在函数调用中,逗号用于分隔参数,而不是作为逗号运算符:
// 逗号作为参数分隔符
console.log(1, 2, 3); // 输出三个值:1 2 3
// 逗号作为运算符(需要括号)
console.log((1, 2, 3)); // 输出一个值:3
优先级与解构赋值
在解构赋值中,括号的使用受到限制,因为它们可能被误解为表达式:
// 正确的解构赋值
let [a, b] = [1, 2];
let {c, d} = {c: 3, d: 4};
// 错误:不能将解构赋值目标包装在括号中
// let [(a)] = [1]; // SyntaxError
优先级与箭头函数
箭头函数的参数和函数体可能需要括号来明确优先级:
// 单参数箭头函数不需要括号
const square = x => x * x;
// 多参数箭头函数需要括号
const add = (x, y) => x + y;
// 函数体是表达式时不需要括号
const double = x => x * 2;
// 函数体是对象字面量时需要括号
const createPerson = name => ({ name });
// 函数体是多条语句时需要大括号
const process = x => {
const y = x * 2;
return y + 1;
};
实际应用示例
1. 条件渲染
在React等前端框架中,运算符优先级对条件渲染很重要:
// 使用&&进行条件渲染
function UserProfile({ user }) {
return (
<div>
<h1>用户资料</h1>
{user && user.name && <p>姓名:{user.name}</p>}
{user && user.email && <p>邮箱:{user.email}</p>}
{user && user.isAdmin && <AdminPanel />}
</div>
);
}
// 使用三元运算符进行条件渲染
function LoginStatus({ isLoggedIn, username }) {
return (
<div>
{isLoggedIn ? (
<p>欢迎,{username}!</p>
) : (
<button>登录</button>
)}
</div>
);
}
2. 数据处理
在数据处理中,运算符优先级影响计算结果:
// 计算平均分,但跳过缺失值
function calculateAverage(scores) {
const validScores = scores.filter(score => score !== null && score !== undefined);
const sum = validScores.reduce((total, score) => total + score, 0);
return validScores.length > 0 ? sum / validScores.length : 0;
}
const scores = [85, 90, null, 75, undefined, 95];
console.log(calculateAverage(scores)); // 86.25
3. 配置合并
在合并配置对象时,运算符优先级很重要:
function mergeConfig(userConfig, defaultConfig) {
// 使用空值合并运算符处理嵌套属性
return {
theme: userConfig.theme ?? defaultConfig.theme,
language: userConfig.language ?? defaultConfig.language,
features: {
comments: userConfig.features?.comments ?? defaultConfig.features.comments,
sharing: userConfig.features?.sharing ?? defaultConfig.features.sharing,
notifications: userConfig.features?.notifications ?? defaultConfig.features.notifications
}
};
}
const defaultConfig = {
theme: "light",
language: "en",
features: {
comments: true,
sharing: true,
notifications: true
}
};
const userConfig = {
theme: "dark",
features: {
comments: false
}
};
console.log(mergeConfig(userConfig, defaultConfig));
// {
// theme: "dark",
// language: "en",
// features: {
// comments: false,
// sharing: true,
// notifications: true
// }
// }
最佳实践
1. 使用括号明确意图
即使您了解运算符优先级,也应该在复杂表达式中使用括号,使代码更易读:
// 不推荐
if (a && b || c && d) {
// ...
}
// 推荐
if ((a && b) || (c && d)) {
// ...
}
2. 避免过于复杂的表达式
将复杂表达式拆分为多个简单表达式,提高可读性:
// 不推荐
const result = a && b ? c + d * e : f || g && h;
// 推荐
const condition = a && b;
const value1 = c + (d * e);
const value2 = f || (g && h);
const result = condition ? value1 : value2;
3. 使用命名变量代替复杂条件
将复杂条件提取为命名变量,使代码自文档化:
// 不推荐
if (user.age >= 18 && user.isVerified && (user.subscription === "premium" || user.credits > 100)) {
// ...
}
// 推荐
const isAdult = user.age >= 18;
const isVerified = user.isVerified;
const hasPremiumAccess = user.subscription === "premium" || user.credits > 100;
if (isAdult && isVerified && hasPremiumAccess) {
// ...
}
4. 避免依赖不常见的优先级规则
不要依赖不常见或容易混淆的优先级规则,使用括号明确意图:
// 不推荐(依赖位运算符优先级)
const flags = FLAG_A | FLAG_B & FLAG_C;
// 推荐
const flags = FLAG_A | (FLAG_B & FLAG_C);
5. 小心自增/自减运算符
在复杂表达式中避免使用自增/自减运算符,或者将它们单独放在一行:
// 不推荐
let result = arr[i++] + arr[i++];
// 推荐
let result = arr[i];
i++;
result += arr[i];
i++;
总结
JavaScript运算符优先级决定了表达式中运算符的执行顺序。了解这些规则对于编写正确的代码至关重要,但在复杂表达式中,使用括号明确优先级可以提高代码的可读性和可维护性。
关键要点:
优先级层次:JavaScript运算符有明确的优先级层次,从高到低排列。
结合性:结合性决定了相同优先级的运算符如何分组(从左到右或从右到左)。
括号优先:括号内的表达式总是先计算,可以用来覆盖默认的优先级规则。
短路求值:逻辑运算符(
&&
、||
、??
)具有短路求值特性,这会影响表达式的计算。最佳实践:使用括号明确意图,避免过于复杂的表达式,使用命名变量代替复杂条件。
通过理解运算符优先级并遵循最佳实践,您可以编写更加清晰、可靠的JavaScript代码,减少因优先级错误导致的bug。
练习
不使用括号,预测以下表达式的结果:
3 + 4 * 5 - 6 / 2
解释以下表达式的计算过程:
let a = 5; let b = 10; let c = a++ + --b;
重写以下表达式,使用括号明确优先级:
let result = a && b || c && d || e;
修复以下代码中的优先级问题:
if (isLoggedIn && role === "admin" || role === "moderator") { // 允许访问管理面板 }
编写一个函数,接受一个数组和一个条件函数,返回数组中第一个满足条件的元素,如果没有找到则返回默认值。使用短路求值和运算符优先级知识。