为什么 JavaScript 中三目运算符内的逗号会导致语法错误?

24

当我尝试在条件(三元)运算符中使用逗号运算符进行日志记录时,我注意到了一些奇怪的事情。以下是一个人为制造的例子:

const a = 2;
const b = 1;
a > b ? console.log(a), a : b; //I expect this to log and evaluate to a

但是我遇到了这个:

Uncaught SyntaxError: Unexpected token ,

根据MDN文档,条件运算符接受两个表达式作为三元运算符的'if'和'else'情况,而逗号运算符在理论上是一个表达式,如下所示:

逗号运算符从左到右依次评估其每个操作数并返回最后一个操作数的值。

那么为什么会出现语法错误呢? 逗号运算符是一个表达式,应该允许它在条件运算符中。虽然,在逗号的操作数周围加括号可以正常工作:
a > b ? (console.log(a), a) : b; //Logs and gives a

为什么这样可以正常工作?括号(或分组运算符)使解释器能够知道它正在处理一个表达式,但是console.log(a),a已经是一个表达式,不需要括号,那么为什么没有它们会导致语法错误?


我进行了几次编辑,但是我不会删除ecmascript-6标签,尽管我认为它适用的唯一原因是您不必要地使用const关键字而不是var - Dexygen
@GeorgeJempty 我本来只想用 ECMAScript(没有版本号)因为我在答案中使用了规范,但是它并不存在,不过这是一个好观点。我认为你的编辑很好,谢谢。 - Andrew Li
1
很高兴你同意在标题中加入“三元运算符”,我认为这会使它更容易被谷歌搜索到。总的来说,这是一个非常好的自问自答的问答,我自己也做过几个,但从未得到如此热烈的反响。 - Dexygen
2个回答

30

这是语言中有意的一部分,并在ECMAScript Language Specification中进行了概述。逗号运算符的语法在Section 12.16中定义,其中规定如下:

12.16 Comma Operator ( , )

语法

表达式:
  赋值表达式
  表达式,赋值表达式

在这里,规范概述了逗号运算符的使用方式。一个Expression是任何AssignmentExpression或者是它本身后跟一个逗号(该运算符)和另一个AssignmentExpression。需要注意的重要事项是,AssignmentExpression是一个Expression,但是Expression不是一个AssignmentExpression

至于实际的条件运算符,操作符和条件表达式的语法在Section 12.14中指定:

12.14 条件运算符 ( ? : )

语法

ConditionalExpression:  
  LogicalORExpression  
  LogicalORExpression ? AssignmentExpression : AssignmentExpression

根据规范,条件表达式只能包含AssignmentExpression,而不仅仅是Expression。因此,条件运算符不能在其操作数中的一个操作数内部有逗号运算符。这似乎是语言的一个奇怪的怪癖,但考虑到非常特定的语法,规范有一个具体的原因:

注意:在ECMAScript中,ConditionalExpression的语法与C和Java略有不同,它们允许第二个子表达式是Expression1,但限制第三个表达式为ConditionalExpression。ECMAScript之所以存在这种差异,是为了允许赋值表达式由条件语句的任一部分控制,并消除逗号表达式作为中心表达式的令人困惑且相当无用的情况。
由于Java和C的限制性语法,它们不允许像这样的东西(Java):
int a = 2;
int b = 1;
System.out.println(a > b ? b = a : a = b); //Can't use assignment in 'else' part
//                                 ^^^^^

ECMAScript的作者决定允许三元操作符中的两个分支都进行赋值,因此出现了这个带有AssignmentExpression的定义。因此,这个定义也不允许逗号运算符实际上出现在条件运算符的“if”部分,但由于它的稀缺性和无用性,这并不是一个问题。他们实质上一箭双雕; 允许更宽松的语法并摆脱了不良实践的无用语法。
添加分组运算符使其起作用的原因是因为分组运算符产生的( Expression )同样也是AssignmentExpression,允许它在三元运算符中使用,请参见str's answer以获取更多详细信息。

1 这里指的是Java的Expression,而不是ECMAScript的Expression。Java没有逗号运算符,因此它的Expression不包括它。


1

这个答案是Li357的答案的扩展。具体来说,它展示了条件运算符在语法中允许PrimaryExpression(不包括逗号运算符),但不允许Expression(包括逗号运算符)。


请查看本回答底部每种提到的表达式或运算符的规范链接。
条件运算符的规范定义如下:
ConditionalExpression:
  LogicalORExpression
  LogicalORExpression ? AssignmentExpression : AssignmentExpression
因此,它可以是仅为LogicalORExpression,或者是LogicalORExpression和两个AssignmentExpression的组合。 AssignmentExpression本身也可以由LogicalORExpression指定,等等。
但是,与其听起来简单的名称不同,LogicalORExpression不仅是基本条件,而且可以包含许多不同的嵌套表达式。一直到PrimaryExpression,其中还包括分组表达式(Expression)
正如在逗号运算符的规范中所示,它仅在Expression中指定,而不在PrimaryExpression本身中指定。
Expression:
  AssignmentExpression
  Expression , AssignmentExpression
简单概括一下:JavaScript的语法只允许在一个分组操作符()内使用逗号运算符,且该分组操作符必须位于AssignmentExpression中。
另请参阅JavaScript中的运算符优先级

资源


我本来想把这个包含在我的答案里,但是我感觉这会让答案变得冗长而没有太多价值。但作为补充,这很好用。+1 - Andrew Li
“有点相关的是运算符优先级” - 不,这个问题完全是关于运算符优先级的!:-D - Bergi
@Bergi 在规范中没有明确记录优先级(至少我没有找到任何东西),但是由语法隐含给出。这个事实和MDN不是JavaScript的权威来源,这就是为什么我用那种措辞的原因。但你当然是正确的,我改变了那句话。 - str

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接