为什么 `false && true || true` 的值为true?

6
根据MDN逻辑运算符页面的介绍:

false && anything会被短路计算为false。

鉴于这一信息,我期望false && true || true会被计算为false。然而,事实并非如此。只有当语句写成以下形式时,才会得到预期的结果(false):
false && (true || true)

我和同事尝试解决这个问题,最接近的解释是该语句按照优先级进行评估。根据MDN运算符优先级逻辑与逻辑或具有更高的优先级,这表明条件被评估为false && true是一个单一语句,然后继续确定false || true的布尔条件,其结果为true。写出来就是:

(false && true) || true

这里有些问题。可能是文档、JavaScript解析逻辑或者我的理解出了问题。

编辑:

我添加了赏金,因为没有一个答案真正理解这个问题。如上所述:MDN逻辑运算符页面明确说明:"false && anything" 短路求值结果为 false。

"anything" 意味着 "任何东西"!


1
这会被评估为“false”。你确定它不是更复杂的操作的一部分吗? - JaredPar
3
没问题,你展示的第二种形式是它的运作方式,(false && true) 评估为 false,所以最终变成了:false || true,因为其中至少有一个是 true,所以结果为 true。 - Patrick Evans
1
虽然你强调“任何东西”意味着“任何东西!”是正确的 - 任何右手边表达式都会被短路并且不会被评估 - “任何东西”并不意味着“所有东西”。 - Brian North
1
任何东西都意味着任何东西。它意味着下一件事,无论它是什么。正如@BrianNorth所说,它并不意味着所有的东西。如果它确实意味着所有的东西,那么当程序遇到false && 时,它会立即停止,因为它只会短路程序的其余部分。 - ArtOfWarfare
1
(false && true) || truefalse && (true || true) 不同。你认为你的代码代表哪一个?false && anything 是 false。但是你又有一个额外的 || true - Octopus
显示剩余2条评论
12个回答

14

你的困惑归根结底是对优先级的误解。

一个数学类比是"零乘以任何数等于零"。考虑以下表达式:

0 x 100 + 5
在任何编程语言中或是一个好的计算器上,这个表达式的结果为5。"零乘以任何数都等于零"的原则是正确的——但在这种情况下,"任何数"指的是100而不是100+5!为了看清楚这个问题,可以与以下内容进行比较:
5 + 0 x 100

无论您是在开头还是结尾加 5,操作符优先级规则都会消除语句的歧义。

在JavaScript的布尔逻辑中,&&的优先级高于||。由于每个运算符都是交换律,因此编写以下代码:

false && true || true

就是写作的同义词

true || false && true

我认为这最能说明正在发生的原理。然而,对我来说,这仍然令人失望,因为我之前对短路的理解。 - Kyle Falconer
@netinept - 我相信我的答案解释了短路行为的方式,你会认同的:https://dev59.com/33vaa4cB1Zd3GeqPG7lU#21394123(看一下它的结尾部分)。 - ArtOfWarfare
@ArtOfWarfare 是的,我同意你的评估。我正在等待奖励并试图保持开放的心态,因为有很多好答案,选择最佳答案很困难。 - Kyle Falconer
精彩的解释,然而我一开始并不相信,不得不在 https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Operator_Precedence 上检查优先级。 - BogdanBiv

11

该语言通过将表达式解析成抽象语法树来工作。您的表达式 false && true || true 被解析为:

          ||
         /  \
       &&    true
      /  \
 false    true

只有在构建AST之后,短路求值才能发生。

当且仅当false && anything都是&&节点的子树时,才会对其进行短路求值,返回false。

这个引用仅适用于有效的子树。

       &&
      /  \
 false    true
这意味着只有 false && true 会被短路求值为 false,而由此得到的 false || true 则会被求值为 true

9

你对优先级的理解有误。

&& 运算符会先执行,然后是 || 运算符,所以看起来就像你写的那样:

(false && true) || true

所以 MDN 的链接是正确的。

那么短路呢?它说“false && anything”会被短路评估为false。 - Kyle Falconer
@netinept - 假或真始终为真。假和任何东西都必须是假的,因为它永远不会成为真的。优先级是计算的顺序,因此您可以根据优先级在等式周围加上括号,然后查看它的外观。 - James Black
@netinept 运算符优先级比短路运算更为重要。 - Erbureth

4

JavaScript会按照以下方式解析这个表达式:

1) false && true //evaluates to false
2) false || true //equals true. The first false is the result above

很容易追踪JavaScript如何解析这整个表达式:
(function(){alert(1); return false; })() && 
(function(){alert(2); return true; })() || 
(function(){alert(3); return true; })()

1
我非常喜欢你的追踪方式。我从来没有想过以那种方式编写任何东西。如果它有效(我认为它有效),那不仅漂亮,而且是我需要更仔细研究的一个非常有趣的结构。 - James Black
感谢您的反馈。=) - Alcides Queiroz
这是一个很棒的技巧,但并没有真正回答问题。 - Kyle Falconer

4
让我们这样说吧。某些撰写此 MDN 文章的人表述得不够清晰/犯了小错误。
正如您所指出的那样,它说:
false && anything 会被短路求值为 false。
任何明智的人都会认为 anything 是任意复杂度的逻辑表达式。然而,这种理解是错误的(与布尔逻辑规则相矛盾)。
false && true || true == true

另外两位回答者已经解释了为什么会这样。这只是逻辑运算符 && 和 || 的顺序。首先处理 &&,然后处理 ||。

现在,回到不好的措辞。他们应该说的是以下内容。请注意我添加的额外括号。

false && (anything) is short-circuit evaluated to false.

我在这里使用括号来表示逻辑表达式的其余部分应该独立于“false”部分进行评估。如果它被独立评估,那么短路它就可以正常工作,因为 false && == false。但是,一旦我们有了一些不能独立评估的表达式,原始短语就无法工作。例如,“false && true || true”由于逻辑运算的顺序而无法被独立评估。然而,“false && doSomething()”可以。

我完全同意文档应该有(anything)而不是anything - Kyle Falconer
实际上,这是开源文档。我相信如果你在那里创建一个账户,你应该能够编辑它。 - Victor Ronin

2

首先考虑这个基本的算术表达式:

A * B + C

乘法具有优先级,因此结果为:
(A * B) + C

同样地,&& 占据优先级,所以

A && B || C

等同于:

(A && B) || C

就短路而言,如果Afalse,那么可能被短路的AnythingB。因此,如果您有这样的东西:

(false && takesALongTimeToDetermine()) || true

它会跳过评估takesALongTimeToDetermine()

我应该注意到这一点:短路不会改变答案。这是一种优化方法。由于&&只有在两个操作数都为true时才能计算出true,如果第一个操作数为false,它就不会再检查第二个操作数了,因为它已经知道答案是false。对于||也进行了相反的优化:如果第一个操作数为true,它将返回true,而不考虑第二个操作数的值。这种优化存在是为了使您的代码运行更快-它永远不会改变答案。MDN之所以提到它,仅仅是因为如果您的&&有一个操作数的计算时间比另一个长得多,那么您应该将其列为第二个操作数。(这也很重要,因为如果takesALongTimeToDetermine()除了返回一个值之外还做了其他事情,例如打印一个日志语句,那么如果函数被短路,您将看不到它。但这仍然与&&返回的答案无关。)


1
短路计算可以改变答案,如果短路部分具有副作用。考虑以下情况:function changeX(){ x=10; return false;}; x=5false && changeX() || x 的值为 5(因为 changeX 被短路了),而 true && changeX() || x 的值为 10,因为 changeX 没有被短路。 - Tibos
@Tibos - && 的短路并不会改变 && 返回的答案。不过你的例子很有趣 - 我之前并没有意识到 &&|| 在返回值上并不是布尔类型,直到我刚才查看了 MDN 文档。 - ArtOfWarfare

1

你说得对,MDN逻辑运算符 的措辞有点模糊。

你不应该把任何东西看作是紧跟在&&之后的任何东西,而是看作是&&右侧操作数中的任何一个

&&需要两个表达式:一个在&&左侧,一个在右侧。

在你的例子中:false && true || true,false是左侧表达式,第一个true是右侧表达式。接下来的|| true不是&&运算符将要计算的一部分。

短路是为了避免浪费时间计算右侧表达式,如果它不能再影响逻辑运算符的结果。

在这个例子中,意思是右侧表达式(true)的false && true将被忽略(因为|| true不是该表达式的一部分)。
因此,在执行时,将会发生以下情况:
  • 逻辑运算符&&将首先被评估
  • 它有两个表达式,在&&的两侧,需要进行评估
  • 第一个表达式(false)评估为false
  • 第二个表达式(true)被短路,因为无论结果如何,逻辑运算符都只能产生false
  • false && true已被评估为false
  • 逻辑运算符||将接下来被评估(由于false && true的结果是false,所以这样做就是false || true)
  • 第一个表达式(false)评估为false
  • 第二个表达式(true)评估为true
  • false || true已被评估为true。
  • 最终结果:true

0

你的解释是错误的,而且你是正确的,运算符优先级是导致它评估为true的原因。你所忽略的是在抽象语法树的上下文中,anything的含义。

由于操作顺序的关系,你最终得到了一棵树,像这样:

          ||
      /        \
     &&        true
   /    \
false   anything

anything的范围不包括树的右侧的真实值。

您可以在此处查看俄亥俄州立大学关于AST的一些笔记。它们适用于每种语言。


你说得没错,然而在发出这个问题后,我已经编辑了MDN文档,加上了右侧表达式周围的括号。了解这一点可能会让你更清楚。 - Kyle Falconer

0
这个表达式为什么会被计算为真是因为 OR 的优先级高于 AND,所以等价的语句应该是:
(false && true) || true

这是一种重言式,因为false || true将始终计算为true。


0

假布尔值 = 0;

真布尔值 = 1;

"&&" 运算符 = 乘法运算;

"||" 运算符 = 加法运算;

明白了吗?所以,如果你有这个:

if(true && false || true)

等于这个:

((1 x 0) + 1)

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