JavaScript运算符优先级完整列表与结合性规则
系统梳理 JavaScript/ECMAScript 最新规范中的 18 级运算符完整优先级列表与结合性规则。深入剖析语法树绑定机制、从左至右操作数求值原理、右结合运算符行为模式以及常见语法冲突防御指南。
一、核心概念解析:优先级、结合性与求值顺序的三维区分
在日常开发中,许多代码歧义源于混淆了“优先级(Precedence)”、“结合性(Associativity)”以及“求值顺序(Evaluation Order)”。在 JavaScript 引擎解析并执行一段表达式时,这三个概念分别发生在编译期与运行期的不同阶段:
1. 优先级(Precedence):语法树节点的深度层级
当一个表达式中出现不同类型的运算符时,优先级高的运算符具有更强的“粘性”,它会先将紧邻的操作数打包成一个子表达式节点。例如在 1 + 2 * 3 中,乘法运算符 * 的优先级(13 级)高于加法运算符 +(12 级),因此在词法语法分析阶段,2 * 3 被首先打包为乘法子节点,形成 1 + (2 * 3) 的语法树结构。
2. 结合性(Associativity):同级并列时的决斗方向
当相邻运算符的优先级完全相同时,结合性决定了语法树向哪个方向倾斜:
- 左结合(Left-to-Right):由左向右依次结合。例如
10 - 4 - 2,减法属于左结合,等价于(10 - 4) - 2,计算结果为4;若按右结合解析则会变成10 - (4 - 2) = 8。 - 右结合(Right-to-Left):由右向左反向折叠。例如幂运算符
2 ** 3 ** 2等价于2 ** (3 ** 2) = 512,赋值运算符a = b = 5等价于a = (b = 5)。
3. 核心陷阱:运算符优先级不等于操作数求值顺序
这是许多资深开发者也容易产生误解的机制:无论运算符的优先级与结合性如何安排,JavaScript 中的各个操作数(表达式中的独立标识符或函数调用)永远严格遵循自左向右的物理顺序进行求值计算。
function log(msg, val) {
console.log(msg);
return val;
}
// 乘法优先级高于加法,但求值顺序依旧是:A -> B -> C
const result = log("A", 1) + log("B", 2) * log("C", 3);
// 控制台真实输出顺序:
// "A"
// "B"
// "C"
// 最终计算: 1 + (2 * 3) = 7
引擎首先从左向右执行 log("A", 1) 获取数值 1;接着执行 log("B", 2) 获取数值 2;再执行 log("C", 3) 获取数值 3。求值完毕后,引擎根据此前 AST 确定的优先级结构,先进行乘法 2 * 3 = 6,最后完成加法 1 + 6 = 7。
二、JavaScript 运算符优先级与结合性完整列表(1~18 级)
依据 ECMAScript 规范定义,JavaScript 中的运算符被严格划分为从 18 级(最高绑定优先级)到 1 级(最低绑定优先级)的层级。下表按优先级由高到低完整呈现所有类别、符号、结合性及标准解析示例:
| 级别 | 运算符类别 | 代表性符号 | 结合性 | 代码示例与等价形式 |
|---|---|---|---|---|
| 18 | 分组与成员访问 | ( )、.、[ ]、?. |
从左到右 | obj.a[0]?.b():链式调用逐层向下解构 |
| 18 | 带参数的实例化 | new ... ( ... ) |
不适用 | new Person("Alice"):优先级高于成员属性访问 |
| 18 | 函数调用与导入 | foo( ... )、import( ... ) |
从左到右 | fn(a)(b) 等价于 (fn(a))(b) |
| 17 | 无参数的实例化 | new ... |
从右到左 | new Date.toString():等价于 new (Date.toString)() |
| 16 | 后置递增/递减 | a++、a-- |
不适用 | i++:返回原值,随后在运行时变量自增 |
| 15 | 一元逻辑非与按位非 | !、~ |
从右到左 | !!val 等价于 !(!val);~-1 === 0 |
| 15 | 一元正负号 | +a、-a |
从右到左 | +"42" 强制转换为数值 42 |
| 15 | 前置递增/递减 | ++a、--a |
从右到左 | ++i:变量先自增,随后返回新值 |
| 15 | 类型与运算控制 | typeof、void、delete |
从右到左 | typeof typeof 1 等价于 typeof "number" |
| 15 | 异步等待 | await |
从右到左 | await fetch().then(...):紧邻 Promise 表达式 |
| 14 | 指数幂运算 | ** |
从右到左 | 2 ** 3 ** 2 等价于 2 ** (3 ** 2) = 512 |
| 13 | 乘法、除法、取模 | *、/、% |
从左到右 | 10 / 2 * 5 等价于 (10 / 2) * 5 = 25 |
| 12 | 加法与减法 | +、- |
从左到右 | 10 - 5 + 2 等价于 (10 - 5) + 2 = 7 |
| 11 | 二进制按位位移 | <<、>>、>>> |
从左到右 | 1 << 2 + 1 等价于 1 << (2 + 1) = 8 |
| 10 | 关系比较与检测 | <、<=、>、>=、in、instanceof |
从左到右 | a < b < c 等价于 (a < b) < c(严重陷阱) |
| 9 | 相等性比较 | ==、!=、===、!== |
从左到右 | a === b == true 等价于 (a === b) == true |
| 8 | 按位与 | & |
从左到右 | flag & 1 === 1:等价于 flag & (1 === 1)(常见BUG) |
| 7 | 按位异或 | ^ |
从左到右 | a ^ b & c 等价于 a ^ (b & c) |
| 6 | 按位或 | | |
从左到右 | a | b ^ c 等价于 a | (b ^ c) |
| 5 | 逻辑与 | && |
从左到右 | a || b && c 等价于 a || (b && c)(与高于或) |
| 4 | 逻辑或与空值合并 | ||、?? |
从左到右 | a ?? b;注意:?? 与 || 不允许无括号混用 |
| 3 | 条件(三元)运算符 | ? : |
从右到左 | a ? b : c ? d : e 等价于 a ? b : (c ? d : e) |
| 2 | 赋值与复合逻辑赋值 | =、+=、-=、*=、&&=、||=、??= |
从右到左 | x = y = z = 10 等价于 x = (y = (z = 10)) |
| 2 | 生成器暂停 | yield、yield* |
从右到左 | yield a = 1 等价于 yield (a = 1) |
| 1 | 展开运算符 | ... |
不适用 | 用于数组字面量或函数实参传递 |
| 1 | 逗号运算符 | , |
从左到右 | x = (1, 2, 3):从左到右求值,最终返回末尾项 3 |
三、右结合运算符深度剖析与运行时行为
在 JavaScript 的 18 个优先级层级中,绝大多数二元运算符均采用“左结合(从左到右)”规则。只有少数几类属于“右结合(从右到左)”。掌握这些右结合运算符的内部折叠机制,对于写出安全无歧义的代码至关重要:
1. 指数幂运算(**):向右深度归约
在大部分编程语言(如 Python、Ruby、ES2016+)中,幂运算都是右结合的。当多个 ** 运算符并列出现时,右侧的指数优先计算:
// 表达式原型:
const val = 2 ** 3 ** 2;
// 依据右结合规则解析为: 2 ** (3 ** 2)
// 1. 先计算右侧子表达式: 3 ** 2 = 9
// 2. 再计算外层表达式: 2 ** 9 = 512
console.log(val); // 512
// 注意: 若按左结合误算会得出: (2 ** 3) ** 2 = 8 ** 2 = 64 (错误!)
2. 连续赋值与复合赋值:数据向左传递
赋值运算符(包括 =、+=、??= 等)是典型的右结合运算符。它的求值结果是“等号右侧赋入的值”,因此级联赋值会从最右端依次向左赋值:
let a, b, c;
a = b = c = 42;
// 实际语法树等价于:
// a = (b = (c = 42))
// 1. 将 42 赋值给 c,表达式 (c = 42) 返回 42
// 2. 将 42 赋值给 b,表达式 (b = 42) 返回 42
// 3. 将 42 赋值给 a,最终全部变量被统一设为 42
若编写 let x = y = 10;,由于解析为 let x = (y = 10);,在非严格模式下未声明的 y 将被隐式泄漏为全局对象的属性(window.y = 10),从而破坏模块封装性并引发内存泄漏。
3. 条件(三元)运算符(? :):分支自右向左嵌套
当多个三元条件运算符连写时,右结合特性使右侧的条件表达式自动作为 else 分支的嵌套条件展开:
function getGrade(score) {
return score >= 90 ? "优秀" : score >= 60 ? "及格" : "不及格";
}
// 依据右结合规则,等价于:
// return score >= 90 ? "优秀" : (score >= 60 ? "及格" : "不及格");
四、高频易错的五大优先级冲突场景与避坑实战
1. 空值合并(??)与逻辑或/与(||、&&)的直接混用限制
ECMAScript 规范为了杜绝开发者因直觉分歧导致的严重逻辑隐患,在语法层面上明确规定:?? 不允许与 && 或 || 在不加括号的情况下直接混用。如果直接书写,JavaScript 引擎将在词法/语法解析期直接抛出 Uncaught SyntaxError。
// ❌ 错误示范:编译期直接抛出 SyntaxError
const value1 = a || b ?? c; // SyntaxError: Unexpected token '??'
const value2 = x && y ?? z; // SyntaxError: Unexpected token '??'
// ✅ 规范写法 1:先使用 || 做假值降级,再用 ?? 判空
const safe1 = (a || b) ?? c;
// ✅ 规范写法 2:将 ?? 表达式作为备用方案
const safe2 = a || (b ?? c);
2. 字符串拼接加号 + 与三元运算符的优先级混淆
加法运算符 + 处于第 12 级,而条件运算符 ? : 处于第 3 级。加法运算符优先级远高于三元运算符,若漏写括号会导致整个前半段字符串被当作布尔条件判定:
const isOnline = false;
// ❌ 错误逻辑:期望输出 "Status: 离线"
console.log("Status: " + isOnline ? "在线" : "离线");
// 实际输出: "在线" !
// 原因解析: ("Status: " + false) 先执行,得到非空字符串 "Status: false"
// 非空字符串在三元条件中判定为真值(truthy),因此始终返回 "在线"!
// ✅ 正确修复:为三元表达式强制包裹圆括号
console.log("Status: " + (isOnline ? "在线" : "离线"));
// 输出: "Status: 离线"
3. 比较运算符链式书写:1 < x < 5 的逻辑假象
在数学表达中 1 < x < 5 表示 x 介于 1 和 5 之间。但在 JavaScript 中,比较运算符处于第 10 级且属于左结合:
const x = 10;
// ❌ 数学思维陷阱:
console.log(1 < x < 5); // 输出: true!
// 解析过程:
// 1. (1 < 10) 优先计算,得出布尔值 true
// 2. 接着进行 (true < 5)
// 3. 布尔值 true 在数值比较中隐式转换为数值 1
// 4. (1 < 5) 恒成立,因此返回 true!
// ✅ 正确写法:使用逻辑与明确拆分范围
console.log(x > 1 && x < 5); // 输出: false
4. 逻辑与 &&(第5级)高于逻辑或 ||(第4级)
在许多业务授权判断中,开发者常常将 A || B && C 误当作 (A || B) && C,而实际上逻辑与会优先结合:
const isAdmin = false;
const isEditor = true;
const hasPermission = false;
// 业务需求:只要是管理员,或者既是编辑又有写权限的人可以编辑
// 表达式:
if (isAdmin || isEditor && hasPermission) {
// 等价于: isAdmin || (isEditor && hasPermission)
// 当 isAdmin 为 false 时,由于 hasPermission 为 false,整体返回 false(符合当前业务)
}
// 但是!如果业务需求是:“当前用户必须具有权限,且角色必须是管理员或编辑之一”:
// ❌ 错误示范:
if (isAdmin || isEditor && hasPermission) { ... } // 逻辑与先结合,当 isAdmin 为 true 时会直接放行,即使没有权限!
// ✅ 正确修复:使用括号改变优先级
if ((isAdmin || isEditor) && hasPermission) { ... }
5. 带参与无参 new 运算符与成员访问的优先级差异
带参数的 new Foo(...) 处于第 18 级,与成员访问属性 . 优先级相同并左结合;而无参数的 new Foo 处于第 17 级,优先级低于成员访问 .:
function Engine() {
this.start = function() { return "V8"; };
}
Engine.prototype.type = "Combustion";
// 场景 1: new 带参数
new Engine().start();
// 等价于: (new Engine()).start()
// 先实例化 Engine,再调用实例上的 start 方法
// 场景 2: 成员属性与无参 new
new Engine.prototype.constructor();
// 等价于: new (Engine.prototype.constructor)()
// 成员访问 . (18级) 高于无参 new (17级)
五、工程化最佳实践与代码质量防御规范
在现代化前端大型工程与团队协作中,完全依靠开发者的个人记忆来默背 18 级运算符表极易出现漏洞。为了确保代码的可读性、稳定性与可维护性,建议在工程实践中遵循以下规范:
1. 防御性圆括号原则(Defensive Grouping)
当表达式跨越两个以上不同优先级类别,或者包含位运算、条件三元、短路逻辑混合时,必须无条件使用圆括号显式指明计算顺序。括号不仅能确保编译器百分之百按照预期构建 AST,更能直接向团队协作伙伴传达清晰的代码语义,大幅降低代码评审(Code Review)心智负担。
2. 引入 ESLint 静态代码安全防线
在工程的 .eslintrc.js 或 eslint.config.js 中开启官方推荐的 no-mixed-operators 规则。该规则能够在代码编写与提交阶段自动拦截可能引起混淆的运算符混合表达式,并在编辑器内直接给出警告提醒:
// eslint.config.js
export default [
{
rules: {
"no-mixed-operators": ["error", {
groups: [
["+", "-", "*", "/", "%", "**"],
["&", "|", "^", "~", "<<", ">>", ">>>"],
["==", "!=", "===", "!==", ">", ">=", "<", "<="],
["&&", "||"],
["in", "instanceof"]
],
allowSamePrecedence: true
}]
}
}
];
3. 结合性速记总结口诀
在日常开发时,可以通过以下三句话快速掌握整个 JavaScript 运算符体系:
- 绝大多数皆左向:除极少数特殊运算符外,所有算术、比较、逻辑与访问运算符均为左结合。
- 唯四门派右结合:单目一元前缀(
!、~、typeof)、指数幂(**)、三元条件(? :)与所有赋值运算符(=、+=、&&=)。 - 求值时序永向右:运算符优先级决定节点树结构,但所有操作数在运行时刻永远严格从左向右顺序求值。
ES6模块化中js文件怎么用import和export交互
2026-11-05 17:00:48
JS读取本地文件时报错或无响应的常见原因
2026-11-05 16:32:31
let、const与var的区别:JavaScript变量声明最佳实践
2026-11-05 14:16:38
JS中直接定义静态二维数组的字面量写法
2026-11-05 14:16:08
JS 怎么查找字符串中所有匹配项的位置
2026-11-05 14:15:53








