运算符优先级
======

**运算符的优先级**决定了表达式中运算执行的先后顺序。优先级高的运算符会作为优先级低的运算符的操作数。

尝试一下
----

优先级和结合性
-------

考虑由下面的表示法描述的表达式。其中，OP1 和 OP2 都是操作符的占位符。


```js
a OP1 b OP2 c
```


如果 `OP1` 和 `OP2` 具有不同的优先级（见下表），则优先级最高的运算符先执行，不用考虑结合性。观察乘法如何具有比加法更高的优先级并首先执行，即使加法是首先写入代码的。


```js
console.log(3 + 10 \* 2); // 输出 23
console.log(3 + (10 \* 2)); // 输出 23 因为这里的括号是多余的
console.log((3 + 10) \* 2); // 输出 26 因为括号改变了优先级
```


左结合（左到右）相当于把左边的子表达式加上小括号 `(a OP b) OP c`，右结合（右到左）相当于 `a OP (b OP c)`。赋值运算符是右结合的，所以你可以这么写：


```js
a = b = 5; // 相当于 a = (b = 5);
```


预期结果是 `a` 和 `b` 的值都会成为 5。这是因为赋值运算符的返回结果就是赋值运算符右边的那个值，具体过程是：首先 `b` 被赋值为 5，然后 `a` 也被赋值为 `b = 5` 的返回值，也就是 5。

另一个例子是，只有幂运算符是右结合的，而其他算术运算符都是左结合的。有趣的是，无论结合性和优先级如何，求值顺序总是从左到右。

|  |  |
| --- | --- |
| 代码 | 输出 |
| 
```js
function echo(name, num) {
    console.log("Evaluating the " + name + " side");
    return num;
}
// 注意这里的除法运算符 (/)
console.log(echo("left", 6) / echo("right", 2));
```

 | 
```
Evaluating the left side
Evaluating the right side
3
```

 |
| 
```js
function echo(name, num) {
    console.log("Evaluating the " + name + " side");
    return num;
}
// 注意这里的幂运算符 (\*\*)
console.log(echo("left", 2) \*\* echo("right", 3));
```
 | 
```
Evaluating the left side
Evaluating the right side
8
```
 |

当有多个具有相同优先级的运算符时，结合性的差异就会发挥作用。仅使用一个或多个不同优先级的运算符时，结合性不会影响输出，如上面的例子所示。在下面的示例中，观察使用多个相同运算符时结合性会如何影响输出。

|  |  |
| --- | --- |
| 代码 | 输出 |
| 
```js
function echo(name, num) {
    console.log("Evaluating the " + name + " side");
    return num;
}
// 注意这里的除法运算符 (/)
console.log(echo("left", 6) / echo("middle", 2) / echo("right", 3));
```

 | 
```
Evaluating the left side
Evaluating the middle side
Evaluating the right side
1
```

 |
| 
```js
function echo(name, num) {
    console.log("Evaluating the " + name + " side");
    return num;
}
// 注意这里的幂运算符 (\*\*)
console.log(echo("left", 2) \*\* echo("middle", 3) \*\* echo("right", 2));
```

 | 
```
Evaluating the left side
Evaluating the middle side
Evaluating the right side
512
```

 |
| 
```js
function echo(name, num) {
    console.log("Evaluating the " + name + " side");
    return num;
}
// 注意这里左边和中间的被圆括号包围的求幂表达式
console.log((echo("left", 2) \*\* echo("middle", 3)) \*\* echo("right", 2));
```
 | 
```
Evaluating the left side
Evaluating the middle side
Evaluating the right side
64
```
 |

观察上面的代码片段，`6 / 3 / 2` 与 `(6 / 3) / 2` 是相同的，因为除法是左结合的。而幂运算符是右结合的，所以 `2 ** 3 ** 2` 与 `2 ** (3 ** 2)` 是相同的。因此，`(2 ** 3) ** 2` 会更改执行顺序，并导致输出上表中的 64。

请记住，判断执行顺序的时候，优先级在结合性之前。所以，混合求除法和幂时，求幂会先于除法。例如，`2 ** 3 / 3 ** 2` 的结果是 0.8888888888888888，因为它相当于 `(2 ** 3) / (3 ** 2)`。

### 分组和短路的注意事项

在下表中，**分组（Grouping）** 具有最高优先级。然而，这并不意味着总是优先对分组符号 `( … )` 内的表达式进行求值，尤其是涉及短路时。

短路是条件求值的术语。例如，在表达式 `a && (b + c)` 中，如果 `a` 为虚值（falsy），那么即使 `(b + c)` 在圆括号中，也不会被求值。我们可以说逻辑或运算符（“OR”）是“短路的”。除了逻辑或运算符外，其他短路运算符还包括逻辑与（“AND”）、空值合并、可选链和条件（三元）运算符。下面有更多例子：


```js
a || b \* c; // 首先对 `a` 求值，如果 `a` 为真值则直接返回 `a`
a && b < c; // 首先对 `a` 求值，如果 `a` 为虚值则直接返回 `a`
a ?? (b || c); // 首先对 `a` 求值，如果 `a` 不是 `null` 或 `undefined` 则直接返回 `a`
a?.b.c; // 首先对 `a` 求值，如果 `a` 是 `null` 或 `undefined` 则直接返回 `undefined`
```

示例
--


```js
3 > 2 && 2 > 1;
// 返回 true

3 > 2 > 1;
// 返回 false，因为 3 > 2 是 true，然后 true 会在比较运算符中
// 被隐式转换为 1，因此 true > 1 会变为 1 > 1，结果是 false
// 加括号可以更清楚：(3 > 2) > 1
```

汇总表
---

下面的表格将所有运算符按照优先级的不同从高（19）到低（1）排列。

请注意，下表中故意不包含展开语法——原因可以引用 Stack Overflow 上的一个回答，“展开语法不是一个运算符，因此没有优先级。它是数组字面量和函数调用（和对象字面量）语法的一部分。”

| 优先级 | 运算符类型 | 结合性 | 运算符 |
| --- | --- | --- | --- |
| 19 | 分组 | n/a（不相关） | `( … )` |
| 18 | 成员访问 | 从左到右 | `… . …` |
| 需计算的成员访问 | 从左到右 | `… [ … ]` |
| `new`（带参数列表） | n/a | `new … ( … )` |
| 函数调用 | 从左到右 | `… ( … )` |
| 可选链（Optional chaining） | 从左到右 | `?.` |
| 17 | `new`（无参数列表） | 从右到左 | `new …` |
| 16 | 后置递增 | n/a | `… ++` |
| 后置递减 | `… --` |
| 15 | 逻辑非 (!) | 从右到左 | `! …` |
| 按位非 (~) | `~ …` |
| 一元加法 (+) | `+ …` |
| 一元减法 (-) | `- …` |
| 前置递增 | `++ …` |
| 前置递减 | `-- …` |
| `typeof` | `typeof …` |
| `void` | `void …` |
| `delete` | `delete …` |
| `await` | `await …` |
| 14 | 幂 (\*\*) | 从右到左 | `… ** …` |
| 13 | 乘法 (\*) | 从左到右 | `… * …` |
| 除法 (/) | `… / …` |
| 取余 (%) | `… % …` |
| 12 | 加法 (+) | 从左到右 | `… + …` |
| 减法 (-) | `… - …` |
| 11 | 按位左移 (<<) | 从左到右 | `… << …` |
| 按位右移 (>>) | `… >> …` |
| 无符号右移 (>>>) | `… >>> …` |
| 10 | 小于 (<) | 从左到右 | `… < …` |
| 小于等于 (<=) | `… <= …` |
| 大于 (>) | `… > …` |
| 大于等于 (>=) | `… >= …` |
| `in` | `… in …` |
| `instanceof` | `… instanceof …` |
| 9 | 相等 (==) | 从左到右 | `… == …` |
| 不相等 (!=) | `… != …` |
| 一致/严格相等 (===) | `… === …` |
| 不一致/严格不相等 (!==) | `… !== …` |
| 8 | 按位与 (&) | 从左到右 | `… & …` |
| 7 | 按位异或 (^) | 从左到右 | `… ^ …` |
| 6 | 按位或 (|) | 从左到右 | `… | …` |
| 5 | 逻辑与 (&&) | 从左到右 | `… && …` |
| 4 | 逻辑或 (||) | 从左到右 | `… || …` |
| 空值合并 (??) | 从左到右 | `… ?? …` |
| 3 | 条件（三元）运算符 | 从右到左 | `… ? … : …` |
| 2 | 赋值 | 从右到左 | `… = …` |
| `… += …` |
| `… -= …` |
| `… **= …` |
| `… *= …` |
| `… /= …` |
| `… %= …` |
| `… <<= …` |
| `… >>= …` |
| `… >>>= …` |
| `… &= …` |
| `… ^= …` |
| `… |= …` |
| `… &&= …` |
| `… ||= …` |
| `… ??= …` |
| 1 | 逗号 / 序列 | 从左到右 | `… , …` |