25

逻辑 AND 和 OR 运算符是 JavaScript 中唯一的惰性运算符以及三元条件运算符。使用以下规则对它们进行短路评估测试:

false && anything === false
true || anything === true

这与在 Haskell 中实现的方式相同:

(&&) :: Bool -> Bool -> Bool
False && _ = False
True  && x = x

(||) :: Bool -> Bool -> Bool
True  || _ = True
False || x = x

然而,根据 MDN,JavaScript 中的逻辑运算符是左关联的。这是反直觉的。在我看来,他们应该是正确的联想。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 文档是否有关于逻辑运算符关联性的错误信息?


编辑:根据规范,逻辑运算符是左关联的:

LogicalANDExpression = BitwiseORExpression
                     | LogicalANDExpression && BitwiseORExpression

LogicalORExpression = LogicalANDExpression
                    | LogicalORExpression || LogicalANDExpression
4

3 回答 3

15

对于任何体面的编译器,这些运算符的选择关联性几乎无关紧要,并且输出的代码无论如何都将是相同的。是的,解析树不同,但发出的代码不需要。

在我所知道的所有 C 系列语言(Javascript 也属于其中)中,逻辑运算符都是关联的。所以真正的问题变成了,为什么类 C 语言将逻辑运算符定义为左结合?由于选择的关联性是无关紧要的(就语义和效率而言),我怀疑是选择了最“自然”(如“大多数其他运算符使用的”)的关联性,尽管我没有没有任何来源支持我的主张。其他可能的解释是,左关联运算符使用 LALR 解析器解析时占用的堆栈空间更少(现在这不是一个大问题,但可能在 C 出现时又回来了)。

于 2013-12-17T18:54:21.060 回答
5

考虑这段代码:

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() 才能返回答案。正确的关联版本不同。它只计算第一个参数 in(false && updateName())因为第二个参数不能将 更改falsetrue.

记住这两点:

  • 运算符优先级描述了不同运算符类型 的复合表达式的嵌套顺序。
  • 运算符关联性描述了具有相同运算符优先级的复合表达式的嵌套顺序。

请注意,上述两点都不会改变解释单个表达式的方式,只会改变解释复合表达式的方式。关联性发生在更高的层次上。

运算符关联性以及运算符优先级对语言的行为方式产生巨大影响。了解这些差异对于能够使用和快速掌握不同编程语言的功能差异至关重要。你的问题肯定会得到我的赞许,我希望这能澄清一些事情。小心。

于 2013-12-24T04:04:04.600 回答
-3

简单的答案:想象一下 var i = null; if (i == null || i.getSomeValue()) ... 当不保持关联时,将首先评估第二个测试,给你一个例外。

于 2013-12-23T22:54:00.863 回答