admin管理员组

文章数量:1134577

I would like to know if JavaScript has "short-circuit" evaluation like &&-operator in C#. If not, I would like to know if there is a workaround that makes sense to adopt.

I would like to know if JavaScript has "short-circuit" evaluation like &&-operator in C#. If not, I would like to know if there is a workaround that makes sense to adopt.

Share Improve this question edited Mar 30, 2023 at 10:06 Cadoiz 1,6661 gold badge24 silver badges33 bronze badges asked Sep 23, 2012 at 17:34 GibboKGibboK 73.9k147 gold badges451 silver badges670 bronze badges 4
  • 2 You're welcome. I've added https://www.google.com/search?q=site:stackoverflow.com+%s as a search shortcut (Chrome/Firefox) to speed up searches. – Rob W Commented Sep 23, 2012 at 17:39
  • Also here an answer to my question developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/… – GibboK Commented Aug 1, 2014 at 8:40
  • Further useful resources: The || evaluation question The && evaluation question – Samuel Hulla Commented May 9, 2019 at 11:08
  • Closely related: Logical operators in JavaScript — how do you use them?. – Sebastian Simon Commented Nov 6, 2022 at 12:05
Add a comment  | 

3 Answers 3

Reset to default 163

Yes, JavaScript has "short-circuit" evaluation.

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

Live demo

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

Live demo

The answer is yes!

This answer goes into great detail on how short-circuiting works in JavaScript, with all the gotchas and also relevant themes such as operator precedence. If you’re looking for a quick definition and already understand how short-circuiting works, I’d recommend checking other answers.


What we (thought we) knew so far:

First let’s inspect the behavior we are all familiar with, inside the if condition, where we use && to check whether the two expressions are true:

const expr1 = true;
const expr2 = true;

if (expr1 && expr2) {
  console.log("bar");
}

Now, your first instinct is probably to say: “Ah yes, quite simple, the code executes the statement if both expr1 and expr2 are evaluated as true.

Well, yes and no. You are technically correct, that is the behavior you described, but that’s not exactly how the code is evaluated and we’ll need to delve deeper in order to fully understand.

How exactly is the && and || interpreted?

It’s time to look “under the hood” of the javascript engine. Let’s consider this practical example:

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

  return x;
}

let userInput = 0xFF; // As an example.
const res = sanitize(userInput) && userInput + 5

console.log(res);

Well, the result is 260, but why? In order to get the answer, we need to understand how the short-circuit evaluation works.

By the MDN definition, the && operator in expr1 && expr2 performs as follows:

Logical AND (&&) evaluates operands from left to right, returning immediately with the value of the first falsy operand it encounters; if all values are truthy, the value of the last operand is returned.

If a value can be converted to true, the value is so-called truthy. If a value can be converted to false, the value is so-called falsy.

[…]

As each operand is converted to a boolean, if the result of one conversion is found to be false, the AND operator stops and returns the original value of that falsy operand; it does not evaluate any of the remaining operands.

Or, more simply, in an older revision of the documentation:

Operator Syntax Description
Logical AND (&&) expr1 && expr2 If expr1 can be converted to true, returns expr2; else, returns expr1.

So this means, in our practical example, the const res is evaluated the following way:

  1. Invoke expr1, which is sanitize(userInput), or sanitize(0xFF).
  2. Run sanitize(0xFF): check isNaN(x), or isNaN(0xFF), (which results in false because 0xFF is a valid hexadecimal number literal for 255), return x which is 0xFF, or 255. If isNaN(x) was true, sanitize would’ve returned NaN.
  3. The expr1 resulted in 255, a “truthy” value, so it’s time to evaluate expr2. If NaN was returned, it’d stop as NaN is falsy.
  4. Since sanitize(userInput) is truthy (a non-zero, finite number), keep going and add 5 to userInput.

“Truthy” means that expression can be evaluated as true.

So here, we were able to avoid additional if blocks and further isNaN checks with a simple usage of the && operator.

How it really works:

By now, we should at least have a picture how the short-circuit operators work.

The operators that exhibit short-circuiting behavior are:

  • Logical AND (&&)
  • Logical OR (||)
  • Nullish coalescing operator (??)
  • Optional chaining (?.)

The universal rule goes:

  • a && b &&&& z evaluates to the first falsy operand, or to the last operand if none is found to be falsy.
  • a || b |||| z evaluates to the first truthy operand, or to the last operand if none is found to be truthy.
  • a ?? b ???? z evaluates to the first operand which is neither null nor undefined, or to the last operand, otherwise.
  • a?.b?.?.z accesses each property in the chain and evaluates to the value of the nested z property if all previous links of the chain evaluate to something that can be converted to an object (i.e. neither null nor undefined); otherwise it returns undefined, regardless of which nested property fails.

Here are some further examples for better comprehension:

function a() {
  console.log("a");

  return false;
}

function b() {
  console.log("b");

  return true;
}

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

// `a()` is evaluated as `false`; execution is stopped.

function a() {
  console.log("a");

  return false;
}

function b() {
  console.log("b");

  return true;
}

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

/*
** 1. `a()` is evaluated as `false`.
** 2. So it should evaluate `expr2`, which is `b()`.
** 3. `b()` is evaluated `true`.
** 4. The statement `console.log("foobar");` is executed.
*/

function a() {
  console.log("a");

  return null;
}

function b() {
  console.log("b");

  return false;
}

function c() {
  console.log("c");

  return true;
}

if (a() ?? b() ?? c()){
   console.log("foobar"); 
}

/*
** 1. `a()` is evaluated as `null`.
** 2. So it should evaluate `expr2`, which is `b()`.
** 3. `b()` is evaluated as `false`; execution is stopped.
*/

const deeply = {
    get nested(){
      console.log("nested");
      
      return {
        get object(){
          console.log("object");
          
          return null;
        }
      };
    }
  };

if (deeply?.nested?.object?.but?.not?.that?.deep){
   console.log("foobar"); 
}

/*
** 1. `deeply` is evaluated as an object.
** 2. `deeply?.nested` is evaluated as an object.
** 3. `deeply?.nested?.object` is evaluated as `null`.
** 4. `?.but?.not?.that?.deep` is essentially skipped over; the entire optional chain is evaluated as `undefined`; execution is stopped.
*/


One last pesky, but very important thing: Operator precedence

Nice, hopefully you’re getting the hang of it! Last thing we need to know is a rule about operator precedence, that is: The && operator is always evaluated prior to the || operator.

Consider the following example:

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());

// "a" is logged; execution is stopped.

The expression a() || b() && c() will, perhaps confusingly to some, result in a(). The reason is quite simple, it’s just our eyesight that’s kind of deceiving us, because we’re used to reading left-to-right. Let’s take the console.log() and what not out and focus purely on the evaluation:

true || false && false

&& having a higher precedence than || means that the operands closest to && are evaluated first — but after all operations with an even higher precedence already being evaluated. Since there’re only && and || here, these are just false and false.

|| works the same way, including the rule that all operations with an even higher precedence should already be evaluated.

  1. So first, we need to evaluate false && false, which is just false.
  2. Then we evaluate true || false (with the resulting false), which is true.
  3. The whole expression is equivalent to (true || (false && false)), which is (true || (false)), which is (true).

You can also try an alternative perspective: in the ECMAScript specification (which is what the JavaScript language is based on), the expression expr1 || expr2 follows a pattern called the LogicalORExpression. Following its definition, in simple terms, both expr1 and expr2 are their own LogicalANDExpression.

  1. If you wanted to evaluate something like true || false && false, you’d have to evaluate the LogicalORExpression: (true) || (false && false). You know that (true) is just true, but you don’t immediately know what (false && false) is.
  2. So then, you’d have to evaluate the LogicalANDExpression: (false) && (false). And now you’re done, because (false) is just false.

Only once you know the result of evaluating each LogicalANDExpression, you can move on to evaluate the constituting LogicalORExpression. This is exactly what is meant by && being evaluated before ||, or && having a higher precedence than ||.

(NB: These grammar rules kill two birds with one stone: they define the evaluation order (via recursive definitions) and the operator precedence (in this case: left to right, and && before ||).)

Well, that might seem pretty tricky, all because of a few weird rules and semantics. But remember, you can always escape operator precedence with parentheses, also known as the grouping operator () — just like in math.

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 parenthesized part is evaluated first.
** 2. `a()` is evaluated as `true`, so `b()` is skipped
** 3. `c()` is evaluated as `false`, stops execution.
*/

And we have yet to talk about where to place the ?? operator in terms of precedence! But don’t worry: since operator precedence rules between && and || and ?? would be too confusing and too complicated, it is actually not allowed to put them next to each other! They can only appear together in the same expression if it’s very clear which one is evaluated first.

(a ?? b) && c  // Clearly, `(a ?? b)` is evaluated first.
a ?? (b && c)  // Clearly, `(b && c)` is evaluated first.
a ?? b && c    // Unclear! Throws a SyntaxError.

The idea is that logical expressions are read left-to-right, and if the value of the left condition is enough to get the total value, the right condition will not be processed and evaluated. Some very simple examples:

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>

The first two cases above will print to console results true and false respectively and you will not even see the modal window asking you to press "OK" or "Cancel", because the left condition is sufficient to define the total result. On the contrary, with the cases 3–6, you will see the modal window asking for your choice, because the former two depend on the right part (that is your choice), and the latter two — regardless of the fact that the aggregate values of these expressions do not depend on your choice — because left conditions are read first. So, it is important to place conditions left-to-right based on which ones you want to be processed first.

本文标签: Does JavaScript have quotShortcircuitquot evaluationStack Overflow