Does JavaScript have "Short-circuit" evaluation?
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.
Yes, JavaScript has "short-circuit" evaluation.
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
This answer goes into great detail on how short-circuiting works in JavaScript, with all the gotcha's 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 recommending checking other answers.
What we (thought we) knew so far:
First let's inspect the behaviour we are all familiar with, inside the if()
block, where we use &&
to check whether the two things are true
:
if (true && true) {
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 behaviour 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 sanitise(x) {
if (isNaN(x)) {
return NaN;
}
return x;
}
let userinput = 0xFF; // as an example
const res = sanitise(userinput) && userinput + 5
console.log(res);
Well the result is 260
.. but why? In order to get the answer, we need to understand how does the short-circuit evaluation work.
By the MDN Definition the
&&
operator inexpr1 && expr2
is executed followingly:If
expr1
can be converted totrue
, returnsexpr2
; else, returnsexpr1
.
So this means, in our practical example, the const res
is evaluated the following way:
- Invoking
expr1
-sanitise(0xFF)
-
0xFF
is a valid hexadecimal number for 250, otherwise I'd returnNaN
- The
expr1
returned a "truthy" value, time to executeexpr2
(otherwise I'd stop asNaN
is falsy) - Since
userinput
is truthy (a number), I can add+5
to it
- "Truthy" means that expression can be evaluated as true. Here's a list of truthy and falsy expressions.
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 universal rule goes:
(some falsy expression) && expr
will evaluate to falsy expression(some truthy expression) || expr
will evaluate to truthy expression
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');
}
//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');`
*/
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 executed 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());
// returns a() and stops execution
This will return as, perhaps confusingly to some as a()
. Reason is quite simple, it's just our eye-sight 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
Now to wrap your head around this:
-
We said the
&&
operator has precedence, so it gets evaluated as first. To help us better imagine the evaluation, think of the definitionexpr1 && expr2
Where:
-
expr2
isfalse
-
expr1
istrue || false
-
-
So that was the tricky part, now
true || false
is evaluated (theexpr1
- left-side of the&&
).- Given the
||
operator stops execution ifexpr1 || expr2
inexpr1
evaluates as truthy, theexpr1
is executed and code execution stops.
- Given the
The returned value is
true
Well.. that was pretty tricky, all because of few weird rules and semantics. But remember, you can always escape operator precedence with the ()
- 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 () escape && operator precedence
2. a() is evaluated as false, so expr2 (c()) to be executed
3. c()
*/
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.