【问题标题】:Does JavaScript have "Short-circuit" evaluation?JavaScript 是否有“短路”评估?
【发布时间】:2021-09-23 18:50:48
【问题描述】:

我想知道 JavaScript 是否具有像 C# 中的 && 运算符这样的“短路”评估。如果没有,我想知道是否有可以采用的解决方法。

【问题讨论】:

标签: javascript short-circuiting


【解决方案1】:

是的,JavaScript 有“短路”评估。

if (true == true || foo.foo){
    // Passes, no errors because foo isn't defined.
}

Live DEMO

if (false && foo.foo){
    // Passes, no errors because foo isn't defined.
}

Live DEMO

【讨论】:

  • 那么短路是JS中的标准?
  • 感谢 gdoron,请帮助我理解...在 C# 中我也有像 & 这样的二元运算符,所以两个操作数都必须为真才能通过,而不是在 C 中使用 &&
  • @GibboK。那么,显然,该逻辑运算符不能有Short-circuit。自己试试吧。使用我的演示。
  • @GibboK:看看这个operator reference。是的,在 JS 中也有一个二元 AND 运算符。
  • @GibboK。是的,在标准中!但是好的评论,就像在 javascript 实现中的 JIT-compile-magic 时代一样,人们真的很想知道某些东西是否是“标准”,或者可能受到实现的影响。使用二元逻辑运算符的条件语句的评估方式和(短期)是标准行为ecma-international.org/ecma-262/5.1/#sec-11.11
【解决方案2】:

这个答案非常详细地介绍了 在 JavaScript 中的工作原理,以及所有的问题和相关的主题,例如运算符优先级,如果您正在寻找一个快速定义并且已经了解短路的方式有效,我建议检查其他答案。


到目前为止我们(认为我们)知道的:

首先让我们检查一下我们都熟悉的行为,在if() 块内,我们使用&& 来检查这两个东西是否是true

if (true && true) {
   console.log('bar');
} 

现在,你的第一反应可能是:'是的,很简单,如果expr1expr2 都被评估为true' p>

嗯,是的,也不是。您在技术上是正确的,这就是您描述的行为,但这并不是代码的确切评估方式,我们需要更深入地研究才能完全理解。


&&|| 究竟是如何解释的?:

是时候看看“ 引擎的底层”了。让我们考虑这个实际的例子:

function sanitise(x) {
  if (isNaN(x)) {
    return NaN;
  }
  return x;
}

let userinput = 0xFF; // as an example
const res = sanitise(userinput) && userinput + 5

console.log(res);

结果是260.. 但是为什么呢?为了得到答案,我们需要了解短路评估是如何工作的。

MDN Definition expr1 && expr2 中的 && 运算符执行如下:

如果expr1可以转换为true,则返回expr2;否则,返回expr1

这意味着,在我们的实际示例中,const res 的评估方式如下:

  1. 调用expr1 - sanitise(0xFF)
  2. 0xFF 是 250 的有效十六进制数,否则我会返回 NaN
  3. expr1 返回一个“真实”值,是时候执行expr2 (否则我会停下来,因为NaN 是假的)
  4. 由于userinput 是真实的(一个数字),我可以添加+5 到它
  • “Truthy”表示表达式可以被评估为真。这是一个列表 truthyfalsy 表达式。

因此,在这里,我们能够通过简单地使用 && 运算符来避免额外的 if 块和进一步的 isNaN 检查。


它是如何工作的:

到目前为止,我们至少应该了解 运算符的工作原理。通用规则是:

  • (some falsy expression) && expr 将评估为假表达式
  • (some truthy expression) || expr 将评估为真实表达式

这里有一些进一步的例子可以更好地理解:

function a() { console.log('a'); return false; }
function b() { console.log('b'); return true; }

if ( a() && b() ){
     console.log('foobar'); 
}

//Evaluates a() as false, stops execution.

function a() { console.log('a'); return false; }
function b() { console.log('b'); return true; }

if ( a() || b() ){
     console.log('foobar'); 
}

/* 1. Evaluates a() as false
   2. So it should execute expr2, which is `b()`
   3. b() returned as true, executing statement `console.log('foobar');`
*/

最后一个令人讨厌但非常重要的事情[运算符优先级]:

很好,希望你能掌握它!我们需要知道的最后一件事是关于运算符优先级的规则,即:

  • && 运算符始终在 || 运算符之前执行。

考虑以下示例:

function a() { console.log('a'); return true;}
function b() { console.log('b'); return false;}
function c() { console.log('c'); return false;}

console.log(a() || b() && c());

// returns a() and stops execution

这将返回为a(),可能会让某些人感到困惑。原因很简单,只是我们的视力有点欺骗我们,因为我们习惯于从左到右阅读。让我们把console.log() 和其他没有的东西拿出来,只关注评估

true || false && false

现在开始讨论这个问题:

  1. 我们说&& 运算符具有优先级,因此它首先被评估。为了帮助我们更好地想象评估,想想定义

    expr1 && expr2
    

    地点:

    • expr2false
    • expr1true || false
  2. 所以这是棘手的部分,现在 true || false 被评估(expr1 - && 的左侧)。

    • 假设 || 运算符停止执行,如果 expr1 中的 expr1 || expr2 评估为真,则执行 expr1 并停止代码执行。
  3. 返回值为true

嗯.. 这很棘手,都是因为很少有奇怪的规则和语义。但请记住,您始终可以使用 () 转义运算符优先级 - 就像在数学中一样

function a() { console.log('a'); return true;}
function b() { console.log('b'); return false;}
function c() { console.log('c'); return false;}

console.log((a() || b()) && c());

/* 1. The () escape && operator precedence
   2. a() is evaluated as false, so expr2 (c()) to be executed
   3. c()  
*/

【讨论】:

  • 我会 1) 不要使用“编译器”这个词。 “引擎”更准确。 2) 不要谈论expr1expr2condition1 或其他什么,这只是令人困惑。决定一个,你也可以引入局部变量,例如。 const expr1 = true; if(expr1 && ...)
  • @JonasWilms 感谢您的输入,并相应地修改了答案。
  • 这个仍然没有直接回答问题。
  • 这是我见过的最好的“没有明确回答问题的好答案”...
  • 这是正确的答案,并有深入的解释,应该被标记为接受并且比现在更多!
【解决方案3】:

思路是从左到右读取逻辑表达式,如果左边条件的值足够得到总值,右边的条件不会被处理和评估。一些非常简单的例子:

function test() {
    const caseNumber = document.querySelector('#sel').value;
    const userChoice = () => confirm('Press OK or Cancel');
    if (caseNumber === '1') {
        console.log (1 === 1 || userChoice());
    } else if (caseNumber === '2') {
        console.log (1 === 2 && userChoice());
    } else if (caseNumber === '3') {
        console.log (1 === 2 || userChoice());
    } else if (caseNumber === '4') {
        console.log (1 === 1 && userChoice());
    } else if (caseNumber === '5') {
        console.log (userChoice() || 1 === 1);
    } else if (caseNumber === '6') {
        console.log (userChoice() && 1 === 2);
    }
}
<label for="sel">Select a number of a test case and press "RUN!":</label>
<br><select id="sel">
  <option value="">Unselected</option>
  <option value="1">Case 1</option>
  <option value="2">Case 2</option>
  <option value="3">Case 3</option>
  <option value="4">Case 4</option>
  <option value="5">Case 5</option>
  <option value="6">Case 6</option>
</select>
<button onclick="test()">RUN!</button>

上面的前两种情况将分别打印到控制台结果truefalse,您甚至不会看到要求您按“确定”或“取消”的模式窗口,因为左侧条件足以定义总的结果。 相反,在案例 3-6 中,您将看到模态窗口询问您的选择,因为前两个取决于正确的部分(即您的选择),而后两个 – 不管聚合的事实如何这些表达式的值不取决于您的选择——因为首先读取左条件。因此,重要的是根据您要首先处理的条件从左到右放置条件。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2013-10-18
    • 2015-05-11
    • 1970-01-01
    • 2017-02-03
    相关资源
    最近更新 更多