JavaScript中的变量赋值解释(OR ||)

410

鉴于这段JavaScript代码片段...

var a;
var b = null;
var c = undefined;
var d = 4;
var e = 'five';

var f = a || b || c || d || e;

alert(f); // 4

有人可以解释一下这个技术叫什么吗?(我最好的猜测是在这个问题的标题中!)? 它是如何/为什么正好起作用的?

我的理解是变量f将被赋予第一个具有值不为null或undefined的变量(从左到右),但我没有找到太多关于这种技术的参考资料,而且我看到它被广泛使用。

此外,这种技术是否特定于JavaScript? 我知道在PHP中做类似的事情会导致f具有真正的布尔值,而不是d本身的值。


4
关于PHP的旧问题,有一个你可以使用的构造:$f=$a or $f=$b or $f=$c; // 等等。PHP有||运算符和or运算符,它们执行相同的工作;然而or在赋值之后被计算,而||在之前被计算。这也使你可以使用Perl风格的$a=getSomething() or die('oops'); - Manngo
1
在 PHP 5.3 中,您可以省略三元运算符的中间部分,因此基于此... 您还可以将其缩短为以下内容: $f = $a ?: $b ?: $c; - Rei
1
从PHP 7开始,您可以使用??来实现此目的。 $a = $b ?? 'default' - Spencer Ruskin
@SpencerRuskin,所以如果$b为真,则$a将被赋值为$b的值,否则为'default' - oldboy
没错。请查看此页面上的空值合并运算符部分:https://www.php.net/manual/en/migration70.new-features.php - Spencer Ruskin
12个回答

250

有关说明请参见短路求值。这是实现这些运算符的常见方式,与JavaScript无关。


62
请注意“陷阱”,即使它们全部未定义、为空或为假,最后一个变量也将被赋值。在链的末尾设置你知道不是false、null或undefined的某个变量,是一种表示没有找到任何内容的好方法。 - Erik Reppen
1
我已经看到这种技术很多年了,但当我想要使用它时,让我感到惊讶的是表达式的结果没有转换为布尔值。你不能后来这样做 if( true == f )。如果一个整数被存储在f中,那么这个测试将总是返回false。 - user1115652
10
实际上,你可以使用 if(true == f) 来代替 if(f),它们的判断结果是相同的。如果你想同时检测 f 的数据类型,可以使用严格比较:if(true === f),但这种情况下测试会失败。 - Alsciende
5
是的,短路求值很常见。但这里的区别在于JavaScript返回了停止执行的最后一个值。@Anurag的回答更好地解释了这一点。 - Ben.12
不确定这是否是初学者的最佳解释。我建议参考:https://javascript.info/logical-operators - csguy

241

这是为了给变量 x 设置一个默认值,如果它是 falsy 的话,则使用变量 y 的值作为默认值。

在 JavaScript 中,布尔运算符可以返回一个操作数,而不总是像其他语言一样返回一个布尔结果。

逻辑或运算符(||)会在第一个操作数为 falsy 时返回其第二个操作数的值,否则返回第一个操作数的值。

例如:

"foo" || "bar"; // returns "foo"
false || "bar"; // returns "bar"

假值是指在布尔上下文中使用时会被强制转换为false的值,包括 0nullundefined、空字符串、NaN,以及当然还有false


3
+1 还有其他类似的运算符吗?还是 || 是互斥的? - OscarRyz
11
@Support (@Oscar): 逻辑运算符 && 的行为类似,如果第一个操作数为 falsy,它返回第一个操作数的值;只有当第一个操作数为 truthy 时,它才返回第二个操作数的值,例如 ("foo" && "bar") == "bar"(0 && "bar") == 0 - Christian C. Salvadó
14
"Falsy"实际上是一个技术术语。 - ChaosPandion
10
在这篇文章中,我们学习了关于 ||、&&、"Falsy" 和 "Truly" 的知识。最佳答案还有一些"隐藏"的礼物。 - Alex
6
@Alex NB: "Truthy" (!"Truly")@Alex 注意:Truthy(真实的)不是指“Truly”(真正的)。 - Bumpy

240

JavaScript中的逻辑运算符||&&使用短路求值。但是与其他语言不同的是,它返回导致执行停止的最后一个值的结果,而不是truefalse值。

以下值在JavaScript中被视为假值:

  • false
  • null
  • ""(空字符串)
  • 0
  • Nan
  • undefined

忽略运算符优先级规则,简单起见,以下示例展示了哪个值导致了求值的停止,并作为结果返回。

false || null || "" || 0 || NaN || "Hello" || undefined // "Hello"

前五个值中的NaN是假值,所以它们从左到右进行求值,直到遇到第一个真值——"Hello",使整个表达式为真,因此任何进一步的内容都不会被求值,并且"Hello"作为表达式的结果返回。类似地,在这种情况下:

1 && [] && {} && true && "World" && null && 2010 // null
前五个值都是“真值”并且会被求职,直到遇到第一个“假值”(null)导致表达式返回false,所以2010不再计算,null作为表达式的结果返回。
你提供的示例利用了JavaScript的这个特性来执行赋值操作。它可以在任何需要从一组值中获取第一个“真值”或“假值”的地方使用。下面的代码将把值"Hello"分配给b,这使得默认值更容易分配,而无需进行if-else检查。
var a = false;
var b = a || "Hello";
你可以将下面的示例称为对该特性的利用,我认为这会使代码更难以阅读。
var messages = 0;
var newMessagesText = "You have " + messages + " messages.";
var noNewMessagesText = "Sorry, you have no new messages.";
alert((messages && newMessagesText) || noNewMessagesText);

在这个警报框中,我们首先检查messages是否为假值。如果是,我们将评估并返回noNewMessagesText;否则,我们将评估并返回newMessagesText。由于在此示例中它是假值,因此我们会停止在noNewMessagesText处,并弹出"对不起,您没有新消息。"


48
在我看来,这是最好的答案,因为以下解释:然而,与其他语言不同的是,它返回停止执行的最后一个值的结果,而不是一个true或false值。 - mastazi
1
@mastazi 没错,我认为应该用粗体字。 - noober
6
答案应该是,它展示了在测试用例之上选择的值。 - Aesthetic
同意,这是我最喜欢的答案,因为它特别解决了JavaScript变量赋值的问题。此外,如果您选择使用三元运算符作为后续变量之一来测试赋值(在运算符之后),则必须将三元运算符括在括号中,以使赋值评估正常工作。 - Joey T

54

Javascript 变量没有类型,所以即使通过布尔运算符赋值,f 也可以被赋一个整数值。

f 被赋予最接近的不等于 false 的值。因此,0、false、null、undefined 都会被跳过:

alert(null || undefined || false || '' || 0 || 4 || 'bar'); // alerts '4'

13
别忘了在这种情况下,单引号 '' 也等同于 false。 - Brigand
点赞指出f被分配了最近的值,这是一个非常重要的观点。 - steviesh
3
“最近的”并不完全准确,尽管它看起来是这样。布尔运算符||是一个布尔运算符,它有两个操作数:左侧和右侧。如果||的左侧为“真值”,则该运算解析为左侧,右侧会被忽略。如果左侧是“假值”,则它解析为右侧。因此,null || undefined || 4 || 0实际上解析为undefined || 4 || 0,这又解析为4 || 0,最终解析为4 - devios1
@devios1 但是 4 是最接近的。 - milos

32

这并没有什么魔法。像 a || b || c || d 这样的布尔表达式是惰性求值的。解释器先查找 a 的值,它是未定义的,所以为 false ,然后继续查找,接着看到 b 是 null,依旧得到 false 结果,于是继续查找,再看到 c 时,也是同样情况。最后看到了 d,并说:“哦,它不是 null,那我就得到了结果”,然后将其赋给最终变量。

这个技巧在所有进行布尔表达式懒惰短路求值的动态语言中都可以使用。但在静态语言中就无法编译(类型错误)。在急切地求值布尔表达式的语言中,它会返回逻辑值(在这种情况下即 true)。


6
在相对静态的C#编程语言中,可以使用 ?? 运算符来表达:object f = a ?? b ?? c ?? d ?? e; 其中,?? 运算符用于进行空值(null)的判断和默认值设定。 - herzmeister
2
herzmeister - 谢谢!我不知道在C#中可以链接使用 ?? 运算符并用于惰性求值技术。 - Marek
3
如前所述,该最后一个 d 变量将被赋值,无论它是 null/undefined 还是其他值。 - BlackVegetable
一个小修正:|| 运算符总是在左侧为假值时解析整个右侧操作数。作为布尔运算符,它只看到两个输入:左侧和右侧。解析器不会将它们视为一系列术语,因此它实际上不会在找到第一个真值时停止,除非该值也是另一个 || 的左操作数。 - devios1

10

这个问题已经收到了几个很好的答案。

总的来说,这种技术利用了语言编译器的一个特性。也就是说,JavaScript“短路”布尔运算符的计算,并返回与第一个非false变量值相关联的值或最后一个变量包含的值。请参见Anurag对会计算为false的值的解释。

然而,出于以下几个原因,使用这种技术并不是好的实践:

  1. 代码可读性:这是使用布尔运算符,如果不了解编译的行为,则预期结果将是布尔值。

  2. 稳定性:这是使用语言编译器的一项功能,该功能在多种语言中不一致,并且由于这个原因,它有可能在未来被更改。

  3. 文档化的特性:存在一种现有的替代方法,可以满足此需求,并且在更多语言中是一致的。这将是三元操作符:

    () ? value 1: Value 2。

使用三元操作符确实需要多输入一点,但它清楚地区分了正在评估的布尔表达式和赋值的值。此外,它可以链接,因此可以重新创建上述执行的默认分配类型。

var a;
var b = null;
var c = undefined;
var d = 4;
var e = 'five';

var f =  ( a ) ? a : 
                ( b ) ? b :
                       ( c ) ? c :
                              ( d ) ? d :
                                      e;

alert(f); // 4

可能将来会成为变更的目标。是的,但我不认为这适用于JavaScript。 - Aesthetic
来到这里看到了以上所有答案,我自己也在想这个任务似乎有些不对劲。我最近一直在阅读罗伯特·C·马丁的《代码整洁之道》,这种类型的任务明显违反了“无副作用”的规则...虽然作者本人表示他的书只是生成良好代码的众多技术之一,但我仍然很惊讶没有其他人反对这种类型的任务。+1 - Albert Rothman
谢谢您的回复。我认为更多的人在编写代码时需要考虑副作用,但在有人花费大量时间维护其他人的代码之前,他们通常不会考虑这一点。 - WSimpson
2
你真的认为那个怪物比 a || b || c || d || e 更清晰吗? - devios1
1
@AlbertRothman 我没有看到任何副作用。没有任何变异。它只是一个简写的空值合并,这是许多语言中非常常见的功能。 - devios1
使用这种技术有几个原因不是很好的实践方法;然而,这基本上是对函数输入进行合理性检查的标准方式 - 你看过任何Javascript吗?你知道它缺乏参数类型提示,对吧?:P - Christoffer Bubach

8

返回第一个真值的输出。

如果所有值都为假,则返回最后一个false值。

例:

  null || undefined || false || 0 || 'apple'  // Return apple

5

它被称为短路运算符。

短路求值是指,只有在第一个参数不能确定表达式的值时,才会执行或评估第二个参数。当OR(||)函数的第一个参数评估为true时,整个表达式的值必须为true。

它还可以用于为函数参数设置默认值。

function theSameOldFoo(name){ 
  name = name || 'Bar' ;
  console.log("My best friend's name is " + name);
}
theSameOldFoo();  // My best friend's name is Bar
theSameOldFoo('Bhaskar');  // My best friend's name is Bhaskar`

4

这段代码是将新变量 z 设置为如下值:如果 x 是“真值”(非零,有效的对象/数组/函数等),则将其赋值给 z;否则将 y 赋值给 z。这是一种比较常见的方式,用于在 x 不存在时提供默认值。

例如,如果你有一个可选回调参数的函数,你可以提供一个默认回调,它不执行任何操作:

function doSomething(data, callback) {
    callback = callback || function() {};
    // do stuff with data
    callback(); // callback will always exist
}

2
这意味着如果设置了x,则z的值将为x,否则如果设置了y,则其值将被设置为z的值。这与以下表达式相同:
if(x)
  z = x;
else
  z = y;

这是可能的,因为JavaScript中的逻辑运算符不返回布尔值,而是返回完成操作所需的最后一个元素的值(在OR语句中,它将是第一个非false值,在AND语句中,它将是最后一个值)。如果操作失败,则返回false


5
这是错误的!如果(x){ z = x; } else {z = y;} 如果第一个值为假,则第二个值始终被分配,而不取决于其实际值。 - evilpie
除非 X 是“false”,否则我认为它只是将 Y 分配给 Z。当然,这就是在我的 FF 中运行的方式,可能也取决于实现。 - tvanfosson
7
关于返回false的最后一部分不是真的(没有双关语)。如果第一个值是假值,|| 运算符只会返回第二个值,无论它是否为真值。 - Matthew Crumley
你的等效代码片段是准确的,但重要的一点是如果x的值为真值,则z将被设置为x的值。否则它将被设置为y的值。这意味着如果x被设置为例如0或空字符串"",那么它不会做你所说的事情,因为这些值是假值 - Daniel Cassidy

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接