我想知道JavaScript是否有像C#中的“短路”评估一样的
&&
运算符。如果没有,我想知道是否有一种解决方法是有意义的采用。&&
运算符。如果没有,我想知道是否有一种解决方法是有意义的采用。本答案详细介绍了JavaScript中短路求值的工作原理,包括所有需要注意的问题以及相关主题,例如操作符优先级。 如果你想要快速了解定义并已经了解短路求值的工作原理,我建议查看其他答案。
首先让我们检查我们都熟悉的行为,在 if
条件语句中,我们使用 &&
来检查两个表达式是否都为 true
:
const expr1 = true;
const expr2 = true;
if (expr1 && expr2) {
console.log("bar");
}
expr1
和expr2
都被评估为true
,那么代码将执行语句”。&&
和||
究竟如何解释?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);
好的,结果是260
,但为什么呢?
为了得到答案,我们需要了解短路求值的工作原理。
根据MDN定义,&&
运算符在expr1 && expr2
中的执行方式如下:
&&
)从左到右评估操作数,遇到第一个falsy操作数立即返回其值;如果所有值都是truthy,则返回最后一个操作数的值。
如果一个值可以转换为true
,那么这个值就被称为truthy。
如果一个值可以转换为false
,那么这个值就被称为falsy。
[...]
由于每个操作数都被转换为布尔值,如果一个转换的结果为false
,则AND运算符将停止并返回该falsy操作数的原始值;它不会评估其余的操作数。
或者更简单地说,在文档的旧版本中:
运算符 语法 描述 逻辑 AND ( &&
)expr1 && expr2
如果 expr1
可以转换为true
,则返回expr2
; 否则返回expr1
。
因此,这意味着在我们的实际示例中,const res
的计算方式如下:
expr1
,它是sanitize(userInput)
或sanitize(0xFF)
。sanitize(0xFF)
:检查isNaN(x)
或isNaN(0xFF)
(结果为false
,因为0xFF
是255的有效十六进制数值),返回x
,即0xFF
或255
。如果isNaN(x)
为true
,sanitize
将返回NaN
。expr1
的结果为255
,是“truthy”值,所以现在要评估expr2
。如果返回NaN
,它将停止,因为NaN
是falsy。sanitize(userInput)
是“truthy”(一个非零有限数字),继续并将5
添加到userInput
中。&&
运算符来避免额外的 if
块和进一步的 isNaN
检查。通用规则是:
a && b &&
… && z
的计算结果为第一个假值操作数,如果没有假值操作数,则结果为最后一个操作数。a || b ||
… || z
的计算结果为第一个真值操作数,如果没有真值操作数,则结果为最后一个操作数。a ?? b ??
… ?? z
的计算结果为第一个既不是null
也不是undefined
的操作数,如果没有这样的操作数,则结果为最后一个操作数。a?.b?.
…?.z
访问链中的每个属性并计算嵌套z
属性的值,如果链的所有先前链接都计算为可以转换为对象(即既不是null
也不是undefined
),则返回嵌套属性的值;否则,无论哪个嵌套属性失败,都将返回undefined
。以下是一些更好理解的例子:
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.
*/
很好,希望你已经掌握了!
我们需要知道的最后一件事是关于运算符优先级的规则,即:&&
运算符总是在 ||
运算符之前进行计算。
考虑以下示例:
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.
a() || b() && c()
可能会让一些人感到困惑,其结果实际上是a()
。
原因很简单,只是我们的视觉有点欺骗我们,因为我们习惯从左到右阅读。
让我们把console.log()
之类的东西拿掉,专注于评估:true || false && false
&&
比||
的优先级更高,这意味着最靠近&&
的操作数首先被计算,但在已经计算了更高优先级的所有操作之后才会计算。由于这里只有&&
和||
,因此它们都是false
。
||
也是同样的规则,包括所有优先级更高的操作都应该已经被计算的规则。
false && false
,这只是false
。true || false
(结果为false
),这是true
。(true || (false && false))
,这是(true || (false))
,这是(true)
。expr1 || expr2
遵循一种称为LogicalORExpression的模式。
根据定义,简单来说,expr1
和expr2
都是它们自己的LogicalANDExpression。
true || false && false
这样的内容,则需要评估LogicalORExpression:(true
) ||
(false && false
)。
您知道(true
)只是true
,但您不会立即知道(false && false
)是什么。false
) &&
(false
)。
现在您完成了,因为(false
)只是false
。&&
在||
之前被评估或&&
优先级高于||
的含义。&&
在||
之前))。(
…)
)来避免运算符优先级 - 就像在数学中一样。
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.
*/
我们还没有讨论在优先级方面放置??
运算符的位置!但是不用担心:由于&&
和||
以及??
之间的运算符优先级规则会太混乱和复杂,因此实际上不允许将它们放在一起!只有当非常清楚哪个先被评估时,它们才能在同一个表达式中出现。
(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.
?.
) 和空值合并 (??
) 运算符!老实说,即使 &&=
, ||=
, 和 ??=
没有被提及,这个答案现在感觉有点过多;也许关于优先级的细节并不需要那么多。 - Sebastian Simonfunction 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>
true
和false
,您甚至不会看到模态窗口询问您是否按“确定”或“取消”,因为左侧条件已足以定义总体结果。
相反,对于第3-6种情况,您将看到模态窗口询问您的选择,因为前两种情况取决于右侧部分(即您的选择),而后两种情况——尽管这些表达式的聚合值不取决于您的选择——因为左侧条件首先被读取。因此,根据您想要首先处理哪些条件,将条件从左到右放置非常重要。
https://www.google.com/search?q=site:stackoverflow.com+%s
作为搜索快捷方式(Chrome/Firefox),以加快搜索速度。 - Rob W