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.
3 Answers
Reset to default 163Yes, 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 tofalse
, 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 totrue
, returnsexpr2
; else, returnsexpr1
.
So this means, in our practical example, the const res
is evaluated the following way:
- Invoke
expr1
, which issanitize(userInput)
, orsanitize(0xFF)
. - Run
sanitize(0xFF)
: checkisNaN(x)
, orisNaN(0xFF)
, (which results infalse
because0xFF
is a valid hexadecimal number literal for 255), returnx
which is0xFF
, or255
. IfisNaN(x)
wastrue
,sanitize
would’ve returnedNaN
. - The
expr1
resulted in255
, a “truthy” value, so it’s time to evaluateexpr2
. IfNaN
was returned, it’d stop asNaN
is falsy. - Since
sanitize(userInput)
is truthy (a non-zero, finite number), keep going and add5
touserInput
.
“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 neithernull
norundefined
, or to the last operand, otherwise.a?.b?.
…?.z
accesses each property in the chain and evaluates to the value of the nestedz
property if all previous links of the chain evaluate to something that can be converted to an object (i.e. neithernull
norundefined
); otherwise it returnsundefined
, 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.
- So first, we need to evaluate
false && false
, which is justfalse
. - Then we evaluate
true || false
(with the resultingfalse
), which istrue
. - 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.
- 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 justtrue
, but you don’t immediately know what (false && false
) is. - So then, you’d have to evaluate the LogicalANDExpression: (
false
)&&
(false
). And now you’re done, because (false
) is justfalse
.
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
版权声明:本文标题:Does JavaScript have "Short-circuit" evaluation? - Stack Overflow 内容由网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:http://www.betaflare.com/web/1736893947a1955995.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
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