为啥 JavaScript 中的逻辑运算符是左关联的?
Posted
技术标签:
【中文标题】为啥 JavaScript 中的逻辑运算符是左关联的?【英文标题】:Why are logical operators in JavaScript left associative?为什么 JavaScript 中的逻辑运算符是左关联的? 【发布时间】:2014-01-02 17:16:15 【问题描述】:logical AND and OR operators 和三元 conditional operator 是 javascript 中唯一的惰性运算符。使用以下规则对它们进行short-circuit evaluation 测试:
false && anything === false
true || anything === true
这与在 Haskell 中实现的方式相同:
(&&) :: Bool -> Bool -> Bool
False && _ = False
True && x = x
(||) :: Bool -> Bool -> Bool
True || _ = True
False || x = x
但是according to MDN logical operators in JavaScript are left associative。这是反直觉的。在我看来,他们应该是正确的联想。 Haskell 做了正确的事。 Haskell 中的逻辑运算符是右结合的:
infixr 3 &&
infixr 2 ||
考虑 Haskell 中的以下表达式:
False && True && True && True
因为&&
在 Haskell 中是右结合的,所以上面的表达式等价于:
False && (True && (True && True))
因此,(True && (True && True))
表达式的计算结果无关紧要。由于第一个False
,整个表达式在一个步骤中被简化为False
。
现在考虑如果&&
保持关联会发生什么。该表达式相当于:
((False && True) && True) && True
现在需要 3 次归约来计算整个表达式:
((False && True) && True) && True
(False && True) && True
False && True
False
如您所见,逻辑运算符正确关联更有意义。这让我想到了我的实际问题:
为什么 JavaScript 中的逻辑运算符是左关联的? ECMAScript 规范对此有什么看法? JavaScript 中的逻辑运算符实际上是右关联的吗? MDN 文档是否有关于逻辑运算符关联性的错误信息?
编辑:根据specification逻辑运算符是左结合的:
LogicalANDExpression = BitwiseORExpression
| LogicalANDExpression && BitwiseORExpression
LogicalORExpression = LogicalANDExpression
| LogicalORExpression || LogicalANDExpression
【问题讨论】:
结果,包括短路,无论哪种方式都是一样的,不是吗? 如果关联性对结果没有任何影响,编译器可以采用任何一种方式。规范的作者可能并不担心这一点,因为这无关紧要。 从您链接到的 MDN 表中,看起来他们只是将所有二元运算符设为左关联,但赋值除外。在大多数情况下,选择是任意的,因为操作是可交换的。 我想我的意思是它们是关联的和可交换的。忽略短路,a && b
等价于b && a
。大多数交换运算符也是关联的,但我发现了这个:unspecified.wordpress.com/2008/12/28/…
@Ankur 不,不会。由于逻辑 AND 和 OR 的定义方式,第一个操作数总是被计算,而第二个操作数只有在第一个计算没有短路时才会被计算。在评估第一个操作数之前,您不能评估第二个操作数。因此,即使最后一个操作数是 False
,您仍然需要评估第一个操作数,而后者又需要评估其第一个操作数,依此类推。
【参考方案1】:
对于任何体面的编译器,这些运算符的选择关联性几乎是无关紧要的,无论如何输出的代码都是相同的。是的,解析树不同,但发出的代码不需要。
在我所知道的所有 C 系列语言(Javascript 也属于其中)中,逻辑运算符都是关联的。所以真正的问题变成了,为什么类 C 语言将逻辑运算符定义为左结合?由于选择的关联性是无关紧要的(就语义和效率而言),我怀疑是选择了最“自然”(如“大多数其他运算符使用的”)的关联性,尽管我没有没有任何来源支持我的主张。其他可能的解释是左关联运算符使用 LALR 解析器解析时占用的堆栈空间更少(现在这不是一个大问题,但可能在 C 出现时又回来了)。
【讨论】:
类 C 语言中布尔表达式的求值行为有点像 foldl',这很好,因为它们总是严格的。【参考方案2】:考虑一下这段代码:
console.log( true || (false && true) ); // right associative (implicit)
console.log( (true || false) && true ); // left associative (Javascript)
这两个示例实际上都返回了相同的结果,但这不是担心运算符关联性的原因。它在这里相关的原因是因为逻辑运算符确定其结果的独特方式。即使所有排列最终得出相同的最终结论,计算的顺序也会发生变化,这可能会对您的代码产生重大影响。
那么,现在考虑一下:
var name = "";
// ----------------------------------------
// Right associative
// ----------------------------------------
name = "albert einstein";
console.log("RIGHT : %s : %s", true || (false && updateName()), name);
// ----------------------------------------
// Left Associative
// ----------------------------------------
name = "albert einstein";
console.log("LEFT : %s : %s", (true || false) && updateName(), name);
function updateName()
name = "charles manson";
return true;
输出为:
RIGHT : true : albert einstein
LEFT : true : charles manson
两个表达式都返回 true,但只有左侧关联的版本必须调用 updateName() 才能返回答案。正确的关联版本不同。它只计算(false && updateName())
中的第一个参数,因为第二个参数不能将false
更改为true
。
记住这两点:
运算符优先级描述了不同运算符类型的复合表达式的嵌套顺序。 运算符关联性描述具有相同运算符优先级的复合表达式的嵌套顺序。请注意,以上两点都不会改变解释单个表达式的方式,只会改变解释 compound 表达式的方式。关联性发生在更高的层次上。
运算符关联性以及运算符优先级对语言的行为方式产生巨大影响。了解这些差异对于能够使用和快速掌握不同编程语言的功能差异至关重要。你的问题肯定会得到我的赞许,我希望这能澄清一些事情。保重。
【讨论】:
“...JavaScript 中的逻辑运算符绝对是右结合”是什么意思?规范中的语法将逻辑运算符定义为左结合。解释器示例代码显示短路评估,它根本不显示关联性。 你是对的。我是倒着记的。我的主要观点是解决每个人对表达结果的关注,而不是它们的影响。谢谢你。帖子已更新。 这可能就是 JavaScript 定义 || 的原因和 && 作为不同的运算符“类型”,以便优先解决它们,而不是关联性。 谢谢,这个答案帮助我努力掌握关联性的本质。 这个答案与原始问题无关,这是关于每个单独的逻辑运算符的左结合性,而不是关于混合两者的表达式(正如这个答案所解释的那样,已解决按优先级)。【参考方案3】:简单的答案:想象一下 var i = null; if (i == null || i.getSomeValue()) ... 当没有关联时,将首先评估第二个测试,给你一个例外。
【讨论】:
这不是问题所在。关联性仅在链中具有多个运算符的表达式中很重要,例如if (i==null || i.isBad() || i.getSomeValue())
,它可以被解析为(i==null || i.isBad()) || i.getSomeValue()
(左关联)或i==null || (i.isBad() || i.getSomeValue())
(右关联)。
关联性不决定首先评估哪个操作数。他们定义运算符的方式决定了首先评估哪个操作数。仔细阅读问题。以上是关于为啥 JavaScript 中的逻辑运算符是左关联的?的主要内容,如果未能解决你的问题,请参考以下文章
“从右到左的运算符关联性”是不是与 javaScript 中赋值运算符中的求值顺序相同
SQL查询中左连接之后的所有连接是不是也必须是左连接?为啥或者为啥不?