运算符优先级
运算符优先级决定了如何解析运算符之间的关系。优先级较高的运算符成为优先级较低的运算符的操作数。
¥Operator precedence determines how operators are parsed concerning each other. Operators with higher precedence become the operands of operators with lower precedence.
Try it
优先级和关联性
¥Precedence and associativity
考虑一个可以用下面的表示来描述的表达式,其中 OP1
和 OP2
都是运算符的填空项。
¥Consider an expression describable by the representation below, where both OP1
and OP2
are fill-in-the-blanks for OPerators.
a OP1 b OP2 c
上面的组合有两种可能的解释:
¥The combination above has two possible interpretations:
(a OP1 b) OP2 c a OP1 (b OP2 c)
语言决定采用哪一种取决于 OP1
和 OP2
的身份。
¥Which one the language decides to adopt depends on the identity of OP1
and OP2
.
如果 OP1
和 OP2
具有不同的优先级(参见下表),则优先级较高的运算符先执行,并且结合性并不重要。观察乘法如何比加法具有更高的优先级并首先执行,即使加法首先写入代码中。
¥If OP1
and OP2
have different precedence levels (see the table below), the operator with the higher precedence goes first and associativity does not matter. Observe how multiplication has higher precedence than addition and executed first, even though addition is written first in the code.
console.log(3 + 10 * 2); // 23
console.log(3 + (10 * 2)); // 23, because parentheses here are superfluous
console.log((3 + 10) * 2); // 26, because the parentheses change the order
在相同优先级的运算符中,语言按结合性对它们进行分组。左关联性(从左到右)意味着它被解释为 (a OP1 b) OP2 c
,而右关联性(从右到左)意味着它被解释为 a OP1 (b OP2 c)
。赋值运算符是右结合的,所以你可以写:
¥Within operators of the same precedence, the language groups them by associativity. Left-associativity (left-to-right) means that it is interpreted as (a OP1 b) OP2 c
, while right-associativity (right-to-left) means it is interpreted as a OP1 (b OP2 c)
. Assignment operators are right-associative, so you can write:
a = b = 5; // same as writing a = (b = 5);
预期结果是 a
和 b
的值为 5。这是因为赋值运算符返回所分配的值。首先,b
设置为 5。然后 a
也被设置为 5 - b = 5
的返回值,也就是赋值的右操作数。
¥with the expected result that a
and b
get the value 5. This is because the assignment operator returns the value that is assigned. First, b
is set to 5. Then the a
is also set to 5 — the return value of b = 5
, a.k.a. right operand of the assignment.
作为另一个例子,唯一的幂运算符具有右结合性,而其他算术运算符具有左结合性。
¥As another example, the unique exponentiation operator has right-associativity, whereas other arithmetic operators have left-associativity.
const a = 4 ** 3 ** 2; // Same as 4 ** (3 ** 2); evaluates to 262144
const b = 4 / 3 / 2; // Same as (4 / 3) / 2; evaluates to 0.6666...
运算符首先按优先级分组,然后,对于具有相同优先级的相邻运算符,按关联性分组。因此,当混合除法和求幂时,求幂总是在除法之前。例如,2 ** 3 / 3 ** 2
结果为 0.8888888888888888,因为它与 (2 ** 3) / (3 ** 2)
相同。
¥Operators are first grouped by precedence, and then, for adjacent operators that have the same precedence, by associativity. So, when mixing division and exponentiation, the exponentiation always comes before the division. For example, 2 ** 3 / 3 ** 2
results in 0.8888888888888888 because it is the same as (2 ** 3) / (3 ** 2)
.
对于前缀一元运算符,假设我们有以下模式:
¥For prefix unary operators, suppose we have the following pattern:
OP1 a OP2 b
其中 OP1
是前缀一元运算符,OP2
是二元运算符。如果 OP1
的优先级高于 OP2
,则将其分组为 (OP1 a) OP2 b
;否则就是 OP1 (a OP2 b)
。
¥where OP1
is a prefix unary operator and OP2
is a binary operator. If OP1
has higher precedence than OP2
, then it would be grouped as (OP1 a) OP2 b
; otherwise, it would be OP1 (a OP2 b)
.
const a = 1;
const b = 2;
typeof a + b; // Equivalent to (typeof a) + b; result is "number2"
如果一元运算符位于第二个操作数上:
¥If the unary operator is on the second operand:
a OP2 OP1 b
那么二元运算符 OP2
的优先级必须低于一元运算符 OP1
,才能将其分组为 a OP2 (OP1 b)
。例如,以下内容无效:
¥Then the binary operator OP2
must have lower precedence than the unary operator OP1
for it to be grouped as a OP2 (OP1 b)
. For example, the following is invalid:
function* foo() {
a + yield 1;
}
因为 +
的优先级高于 yield
,所以这将变成 (a + yield) 1
,但因为 yield
在生成器函数中是 保留字,所以这将是一个语法错误。幸运的是,大多数一元运算符的优先级高于二元运算符,并且不会遇到此陷阱。
¥Because +
has higher precedence than yield
, this would become (a + yield) 1
— but because yield
is a reserved word in generator functions, this would be a syntax error. Luckily, most unary operators have higher precedence than binary operators and do not suffer from this pitfall.
如果我们有两个前缀一元运算符:
¥If we have two prefix unary operators:
OP1 OP2 a
然后,更接近操作数 OP2
的一元运算符的优先级必须高于 OP1
,才能将其分组为 OP1 (OP2 a)
。也可以用另一种方式得到它并最终得到 (OP1 OP2) a
:
¥Then the unary operator closer to the operand, OP2
, must have higher precedence than OP1
for it to be grouped as OP1 (OP2 a)
. It's possible to get it the other way and end up with (OP1 OP2) a
:
async function* foo() {
await yield 1;
}
因为 await
的优先级高于 yield
,所以这将变成 (await yield) 1
,它正在等待名为 yield
的标识符,并且出现语法错误。同样,如果你有 new !A;
,因为 !
的优先级低于 new
,所以这将变成 (new !) A
,这显然是无效的。(无论如何,这段代码看起来毫无意义,因为 !A
总是生成一个布尔值,而不是构造函数。)
¥Because await
has higher precedence than yield
, this would become (await yield) 1
, which is awaiting an identifier called yield
, and a syntax error. Similarly, if you have new !A;
, because !
has lower precedence than new
, this would become (new !) A
, which is obviously invalid. (This code looks nonsensical to write anyway, since !A
always produces a boolean, not a constructor function.)
对于后缀一元运算符(即 ++
和 --
),适用相同的规则。幸运的是,这两个运算符的优先级都高于任何二元运算符,因此分组始终符合你的预期。此外,由于 ++
求值为一个值,而不是一个引用,因此你也不能像在 C 中那样将多个增量链接在一起。
¥For postfix unary operators (namely, ++
and --
), the same rules apply. Luckily, both operators have higher precedence than any binary operator, so the grouping is always what you would expect. Moreover, because ++
evaluates to a value, not a reference, you can't chain multiple increments together either, as you may do in C.
let a = 1;
a++++; // SyntaxError: Invalid left-hand side in postfix operation.
运算符优先级将被递归处理。例如,考虑这个表达式:
¥Operator precedence will be handled recursively. For example, consider this expression:
1 + 2 ** 3 * 4 / 5 >> 6
首先,我们通过降低优先级来对具有不同优先级的运算符进行分组。
¥First, we group operators with different precedence by decreasing levels of precedence.
**
运算符具有最高优先级,因此它首先分组。- 环视
**
表达式,右侧有*
,左侧有+
。*
具有更高的优先级,因此它被排在最前面。*
和/
具有相同的优先级,因此我们现在将它们组合在一起。 - 环视分组为 2 的
*
//
表达式,由于+
的优先级高于>>
,所以将前者分组。
(1 + ( (2 ** 3) * 4 / 5) ) >> 6
// │ │ └─ 1. ─┘ │ │
// │ └────── 2. ───────┘ │
// └────────── 3. ──────────┘
在 *
//
组中,因为它们都是左结合的,所以左操作数将被分组。
¥Within the *
//
group, because they are both left-associative, the left operand would be grouped.
(1 + ( ( (2 ** 3) * 4 ) / 5) ) >> 6
// │ │ │ └─ 1. ─┘ │ │ │
// │ └─│─────── 2. ───│────┘ │
// └──────│───── 3. ─────│──────┘
// └───── 4. ─────┘
请注意,运算符优先级和结合性仅影响运算符的求值顺序(隐式分组),而不影响操作数的求值顺序。操作数始终从左到右计算。优先级较高的表达式总是首先被求值,然后根据运算符优先级的顺序组合它们的结果。
¥Note that operator precedence and associativity only affect the order of evaluation of operators (the implicit grouping), but not the order of evaluation of operands. The operands are always evaluated from left-to-right. The higher-precedence expressions are always evaluated first, and their results are then composed according to the order of operator precedence.
function echo(name, num) {
console.log(`Evaluating the ${name} side`);
return num;
}
// Exponentiation operator (**) is right-associative,
// but all call expressions (echo()), which have higher precedence,
// will be evaluated before ** does
console.log(echo("left", 4) ** echo("middle", 3) ** echo("right", 2));
// Evaluating the left side
// Evaluating the middle side
// Evaluating the right side
// 262144
// Exponentiation operator (**) has higher precedence than division (/),
// but evaluation always starts with the left operand
console.log(echo("left", 4) / echo("middle", 3) ** echo("right", 2));
// Evaluating the left side
// Evaluating the middle side
// Evaluating the right side
// 0.4444444444444444
如果你熟悉二叉树,请将其视为 后序遍历。
¥If you are familiar with binary trees, think about it as a post-order traversal.
/ ┌────────┴────────┐ echo("left", 4) ** ┌────────┴────────┐ echo("middle", 3) echo("right", 2)
当所有运算符被正确分组后,二元运算符将形成一棵二叉树。评估从最外面的组开始 - 这是优先级最低的运算符(在本例中为 /
)。首先计算该运算符的左操作数,该操作数可能由更高优先级的运算符组成(例如调用表达式 echo("left", 4)
)。计算完左操作数后,以相同的方式计算右操作数。因此,所有叶节点(echo()
调用)都将从左到右访问,而不管加入它们的运算符的优先级如何。
¥After all operators have been properly grouped, the binary operators would form a binary tree. Evaluation starts from the outermost group — which is the operator with the lowest precedence (/
in this case). The left operand of this operator is first evaluated, which may be composed of higher-precedence operators (such as a call expression echo("left", 4)
). After the left operand has been evaluated, the right operand is evaluated in the same fashion. Therefore, all leaf nodes — the echo()
calls — would be visited left-to-right, regardless of the precedence of operators joining them.
短路
¥Short-circuiting
在上一节中,我们说过 "优先级较高的表达式总是首先被求值" — 这通常是正确的,但必须通过短路确认进行修改,在这种情况下,操作数可能根本不会被评估。
¥In the previous section, we said "the higher-precedence expressions are always evaluated first" — this is generally true, but it has to be amended with the acknowledgement of short-circuiting, in which case an operand may not be evaluated at all.
短路是条件评估的行话。例如,在表达式 a && (b + c)
中,如果 a
是 falsy,则子表达式 (b + c)
甚至不会被计算,即使它被分组并因此具有比 &&
更高的优先级。我们可以说逻辑 AND 运算符 (&&
) 是 "short-circuited"。除了逻辑 AND 之外,其他短路运算符还包括逻辑 OR (||
)、无效合并 (??
) 和可选链接 (?.
)。
¥Short-circuiting is jargon for conditional evaluation. For example, in the expression a && (b + c)
, if a
is falsy, then the sub-expression (b + c)
will not even get evaluated, even if it is grouped and therefore has higher precedence than &&
. We could say that the logical AND operator (&&
) is "short-circuited". Along with logical AND, other short-circuited operators include logical OR (||
), nullish coalescing (??
), and optional chaining (?.
).
a || (b * c); // evaluate `a` first, then produce `a` if `a` is "truthy"
a && (b < c); // evaluate `a` first, then produce `a` if `a` is "falsy"
a ?? (b || c); // evaluate `a` first, then produce `a` if `a` is not `null` and not `undefined`
a?.b.c; // evaluate `a` first, then produce `undefined` if `a` is `null` or `undefined`
当评估短路运算符时,总是评估左操作数。仅当左操作数无法确定运算结果时,才会计算右操作数。
¥When evaluating a short-circuited operator, the left operand is always evaluated. The right operand will only be evaluated if the left operand cannot determine the result of the operation.
注意:短路行为是在这些运算符中进行的。其他运算符将始终评估两个操作数,无论这实际上是否有用 - 例如,
NaN * foo()
将始终调用foo
,即使结果永远不会是NaN
之外的其他值。¥Note: The behavior of short-circuiting is baked in these operators. Other operators would always evaluate both operands, regardless if that's actually useful — for example,
NaN * foo()
will always callfoo
, even when the result would never be something other thanNaN
.
之前的后序遍历模型仍然有效。然而,在访问短路运算符的左子树之后,语言将决定是否需要评估右操作数。如果不是(例如,因为 ||
的左操作数已经为真),则直接返回结果,而不访问右子树。
¥The previous model of a post-order traversal still stands. However, after the left subtree of a short-circuiting operator has been visited, the language will decide if the right operand needs to be evaluated. If not (for example, because the left operand of ||
is already truthy), the result is directly returned without visiting the right subtree.
考虑这种情况:
¥Consider this case:
function A() { console.log('called A'); return false; }
function B() { console.log('called B'); return false; }
function C() { console.log('called C'); return true; }
console.log(C() || B() && A());
// Logs:
// called C
// true
尽管 &&
具有更高的优先级,但仅评估 C()
。这并不意味着 ||
在这种情况下具有更高的优先级 - 正是因为 (B() && A())
具有更高的优先级,导致它作为一个整体被忽略。如果重新排列为:
¥Only C()
is evaluated, despite &&
having higher precedence. This does not mean that ||
has higher precedence in this case — it's exactly because (B() && A())
has higher precedence that causes it to be neglected as a whole. If it's re-arranged as:
console.log(A() && C() || B());
// Logs:
// called A
// called B
// false
那么 &&
的短路效应只会阻止 C()
被评估,但由于 A() && C()
作为一个整体是 false
,所以 B()
仍然会被评估。
¥Then the short-circuiting effect of &&
would only prevent C()
from being evaluated, but because A() && C()
as a whole is false
, B()
would still be evaluated.
但请注意,短路不会改变最终的评估结果。它只影响操作数的计算,而不影响运算符的分组方式 - 如果操作数的计算没有副作用(例如,记录到控制台、分配给变量、抛出错误),则在以下位置不会观察到短路: 全部。
¥However, note that short-circuiting does not change the final evaluation outcome. It only affects the evaluation of operands, not how operators are grouped — if evaluation of operands doesn't have side effects (for example, logging to the console, assigning to variables, throwing an error), short-circuiting would not be observable at all.
这些运算符(&&=
、||=
、??=
)的赋值对应项也被短路。它们以一种短路的方式导致分配根本不会发生。
¥The assignment counterparts of these operators (&&=
, ||=
, ??=
) are short-circuited as well. They are short-circuited in a way that the assignment does not happen at all.
表格
¥Table
下表按从最高优先级 (18) 到最低优先级 (1) 的顺序列出了运算符。
¥The following table lists operators in order from highest precedence (18) to lowest precedence (1).
关于该表的几个一般注意事项:
¥Several general notes about the table:
- 并非此处包含的所有语法都是严格意义上的 "operators"。例如,价差
...
和箭头=>
通常不被视为运算符。然而,我们仍然将它们包括在内,以显示它们与其他运算符/表达式相比的结合有多紧密。 - 某些运算符的某些操作数需要比高优先级运算符生成的表达式更窄的表达式。例如,成员访问
.
(优先级 17)的右侧必须是标识符而不是分组表达式。箭头=>
(优先级 2)的左侧必须是参数列表或单个标识符,而不是某个随机表达式。 - 某些运算符具有某些操作数,它们接受的表达式比高优先级运算符生成的表达式更宽。例如,括号符号
[ … ]
(优先级 17)的括号括起来的表达式可以是任何表达式,甚至是逗号(优先级 1)连接的表达式。这些运算符的作用就好像该操作数是 "自动分组"。在这种情况下,我们将省略关联性。
注意:
¥Notes:
- 操作数可以是任何表达式。
- "右侧" 必须是标识符。
- "右侧" 可以是任何表达式。
- "右侧" 是优先级 > 1 的任何表达式的逗号分隔列表(即不是逗号表达式)。
new
表达式的构造函数不能是可选链。 - 操作数必须是有效的赋值目标(标识符或属性访问)。它的优先级意味着
new Foo++
是(new Foo)++
(语法错误)而不是new (Foo++)
(类型错误:(Foo++) 不是构造函数)。 - 操作数必须是有效的赋值目标(标识符或属性访问)。
- 操作数不能是标识符或 私有属性 访问。
- 左侧的优先级不能为 14。
- 操作数不能是不分组的逻辑 OR
||
或逻辑 AND&&
运算符。 - "左手边" 必须是有效的赋值目标(标识符或属性访问)。
- 关联性意味着
?
之后的两个表达式隐式分组。 - "左手边" 是单个标识符或带括号的参数列表。
- 仅在对象文字、数组文字或参数列表内部有效。
第 17 组和第 16 组的优先级可能有点模糊。以下是一些示例以进行说明。
¥The precedence of groups 17 and 16 may be a bit ambiguous. Here are a few examples to clarify.
- 可选链接始终可以替代其各自的语法而无需可选性(除非在少数特殊情况下禁止使用可选链接)。例如,任何接受
a?.b
的地方也接受a.b
,反之亦然,a?.()
、a()
等也是如此。 - 成员表达式和计算成员表达式始终可以互相替代。
- 调用表达式和
import()
表达式始终可以互相替代。 - 这留下了四类表达式:成员访问、带参数的
new
、函数调用和不带参数的new
。- 成员访问的 "左手边" 可以是:成员访问 (
a.b.c
)、带参数的new
(new a().b
) 和函数调用 (a().b
)。 - 带有参数的
new
的 "左手边" 可以是:成员访问 (new a.b()
) 和new
,带有参数 (new new a()()
)。 - 函数调用的 "左手边" 可以是:成员访问 (
a.b()
)、带参数的new
(new a()()
) 和函数调用 (a()()
)。 - 不带参数的
new
的操作数可以是:成员访问 (new a.b
)、带参数的new
(new new a()
) 和不带参数的new
(new new a
)。
- 成员访问的 "左手边" 可以是:成员访问 (