什么时候应该使用??(nullish coalescing)而不是||(逻辑或)?

443

相关于JavaScript中是否有"空合并"运算符? - JavaScript现在具有??运算符,我经常看到它被使用。以前,大多数JavaScript代码使用||

let userAge = null

// These values will be the same. 
let age1 = userAge || 21
let age2 = userAge ?? 21

在什么情况下,??||会有不同的行为?

17
概述和动机本提案旨在引入一个新的操作符,用于处理 JavaScript 中的空值合并。这个新操作符被称为 nullish 合并运算符(nullish coalescing operator),表示为 ??。在目前的 JavaScript 中,使用逻辑或运算符 || 可以实现类似的功能,但是存在一些问题。当左操作数为 falsy 值时(例如空字符串、0、false 等),逻辑或运算符将返回右操作数。这可能会导致意外的行为,尤其是在比较数字或布尔值时。nullish 合并运算符解决了这个问题,它只有在左侧操作数为 null 或 undefined 时才会返回右侧操作数。否则,它将返回左侧操作数。这使得代码更加可读、明确和健壮。举例来说,考虑以下代码片段:let foo = null ?? 'default'; console.log(foo); // 'default' let bar = 0 || 'default'; console.log(bar); // 'default'在上面的代码中,foo 的值将是 'default',而 bar 的值将是 0,这可能不是预期的结果。使用 nullish 合并运算符可以避免这种情况,代码如下所示:let foo = null ?? 'default'; console.log(foo); // 'default' let bar = undefined ?? 'default'; console.log(bar); // 'default' - ASDFGerte
@ASDFGerte 这将是一个很好的答案。 - mikemaccana
阅读答案后,考虑到自己的例子,有一个非常明显的实际情况 - 想象一下一个年龄为0的婴儿 - 值是已知的(因此不是null),但会是假值。 - mikemaccana
1
在阅读答案后,考虑到自己的例子,有一个非常明显的实际情况 - 想象一下一个年龄为0的婴儿,或者某个测试结果为0的人 - 值是已知的(因此不是null),但会被视为假。 - mikemaccana
1
如果在上下文中,假值是可以接受的值,那么请使用 ??,否则我会使用 || - O-9
8个回答

626

逻辑或运算符||在左值为falsy值时使用右值,而空值合并运算符??在左值为nullundefined时使用右值。

这些运算符经常用于在第一个值缺失时提供默认值。

但是如果你的左值可能包含""0false(因为这些是falsy值),那么逻辑或运算符||可能会有问题:

console.log(12 || "not found") // 12
console.log(0  || "not found") // "not found"

console.log("jane" || "not found") // "jane"
console.log(""     || "not found") // "not found"

console.log(true  || "not found") // true
console.log(false || "not found") // "not found"

console.log(undefined || "not found") // "not found"
console.log(null      || "not found") // "not found"

在许多情况下,你可能只想在左侧的值为nullundefined时获取右侧的值。这就是空值合并运算符??的作用:

console.log(12 ?? "not found") // 12
console.log(0  ?? "not found") // 0

console.log("jane" ?? "not found") // "jane"
console.log(""     ?? "not found") // ""

console.log(true  ?? "not found") // true
console.log(false ?? "not found") // false

console.log(undefined ?? "not found") // "not found"
console.log(null      ?? "not found") // "not found"

当前LTS版本的Node(v10和v12)中不支持??操作符,但是您可以在某些TypeScript或Node版本中使用:

??操作符于2019年11月添加到TypeScript 3.7中。

最近,??运算符被包含在ES2020中,由Node 14支持(在2020年4月发布)。

当支持nullish coalescing操作符??时,通常会使用它代替OR操作符||(除非有充分的理由不这样做)。


262

简而言之

空值合并运算符 ?? 区分以下两种情况:

  • 空值nullundefined
  • 虽然被定义但为假值false0'' 等等)

||(逻辑或)对这两种情况都视为相同。

我创建了一个简单的图形来说明 JavaScript 中 空值假值 之间的关系:

enter image description here

进一步解释:

let x, y

x = 0
y = x || 'default'            // y = 'default'
y = x ?? 'default'            // y = 0

如上所示,运算符??||之间的区别在于一个检查nullish值,而另一个检查falsey值。然而,有许多情况下它们的行为是相同的。这是因为在JavaScript中,每个nullish值也是falsey的(但并不是每个falsey值都是nullish)。
根据我们上面学到的知识,我们可以创建一些不同行为的示例:
let y

y = false || 'default'       // y = 'default'
y = false ?? 'default'       // y = false

y = 0n || 'default'          // y = 'default'
y = 0n ?? 'default'          // y = 0n

y = NaN || 'default'         // y = 'default'
y = NaN ?? 'default'         // y = NaN

y = '' || 'default'          // y = 'default'
y = '' ?? 'default'          // y = ''

由于新的Nullish Coalescing Operator可以区分无值和假值,所以如果您需要检查是否没有字符串或空字符串,它可能会有益处。通常情况下,您可能大部分时间都希望使用??而不是||
最后,也是最不重要的是它们行为相同的两个情况。
let y

y = null || 'default'        // y = 'default'
y = null ?? 'default'        // y = 'default'

y = undefined || 'default'   // y = 'default'
y = undefined ?? 'default'   // y = 'default'

26
单看这个图,就能轻松理解 - 很棒! - TimBryanDev
3
喜欢那个解释。非常准确! - sagat
这让我想起了一个古老的水族视频。还有一些虚假的例子:0.0,0x0,0b0,0e0,-0e123 0e-123,当然还有组合:-1.2e-1234 - Andy P

45

作为一个非常简短的规则,你可以把它看成相反的方式:

  • ||(或)返回第一个“真值”(如果没有“真值”,则返回最后一个值)
  • ??(nullish coalescing)返回第一个“已定义”值(如果没有“已定义”值,则返回最后一个值)

示例

x = false || true; // -->  true   (the first 'truthy' value - parameter 2)
x = false ?? true; // -->  false  (the first 'defined' value - parameter 1)

16
正如在MDN中所述:

与逻辑或 (||) 操作符相反,如果左操作数是一个假值,但不是 nullundefined,则返回左操作数。换句话说,如果您使用 || 为另一个变量 foo 提供一些默认值,则如果您将某些假值视为可用 (例如 ''0),则可能会遇到意外的行为。请参见下面的更多示例。

还有在您提供的问题的答案中也有相关说明:

Regardless of the type of the first operand, if casting it to a Boolean results in false, the assignment will use the second operand. Beware of all the cases below:

alert(Boolean(null)); // false
alert(Boolean(undefined)); // false
alert(Boolean(0)); // false
alert(Boolean("")); // false
alert(Boolean("false")); // true -- gotcha! :)

1
我在那个问题的答案中看到了这个描述,但它似乎是一个已知的JS布尔逻辑的重复,而不是一个具体的例子。 - mikemaccana

13

基于MDN文档

逻辑或(||)运算符相反,如果左操作数是一个假值,但不是 nullundefined,该值将被返回。

概念示例:

  • When use || for a falsy value that is NOT undefined or null:

    false || 'name'; // ==> the 'name' is returned
    
  • But when using ?? for above case:

    false ?? 'name'; // ==> the false is returned
    
    

真实例子: 假设我们有一个phone变量,在我们的表单中不是必需的,空字符串('')对我们来说是有效的,如果phone变量为nullundefined,我们想要执行doSomething(),现在猜猜看:

  • When use || for a falsy value that is NOT undefined or null:

    const phone = ''; // assume it became empty string from some action
    
    phone || doSomething(); // ==> damn, the doSomething is run 
    // but we want to run it if it's `undefined` or `null` the empty string is valid for us
    
  • But when using ?? for above case:

    const phone = ''; // same assumption like above
    
    phone ?? doSomething(); // ==> yeah, that's right
    // the empty string is valid for us and the doSomething is not run
    

注意: 实际上这只是一个例子,在实际项目中您可以更好地感受到这个可爱的操作用法。

注意: 对于undefinednull,它们两者的行为都相同。


9

|| 作为一个布尔条件用于判断时,会返回 false。例如:

let userAge = false

// These values will be the same. 
let age1 = userAge || 21 // 21
let age2 = userAge ?? 21 // false

逻辑或运算符仍将为任何不评估为“真”的内容提供下一个值。因此,在此情况下,它会给出值“21”。其中“??”只处理“null”和“undefined”。

9

function f(input) {
  const val = input || 1;

  return 41 + val;
}

function g(input) {
  const val = input ?? 1;

  return 41 + val;
}

console.log("using ||:", f(0));
console.log("using ??:", g(0));

空值合并操作符仅适用于 null 和 undefined 值。当您不希望使用这些值,但愿意接受其他假值时,请使用空值合并操作符。

console.log(null      ?? "nullish");
console.log(undefined ?? "nullish");
console.log(""        ?? "nullish");
console.log(0         ?? "nullish");
console.log(false     ?? "nullish");

逻辑或(logical OR)会跳过任何虚假值,并给你另一个东西。逻辑或将跳过任何虚假值并提供给你另一个参数。这个可以工作,现在已成为惯用语,但这并不总是你想要的:

console.log(null      || "falsy");
console.log(undefined || "falsy");
console.log(""        || "falsy");
console.log(0         || "falsy");
console.log(false     || "falsy");

以下是关于如何确定需要哪种操作符的几条规则。最简单的测试:

  • 你只想防止null(和undefined - 通常是同一件事)吗?那就用??。如果你不确定,最好使用 nullish coalescing 操作符。
  • 知道你也不想要0"",例如吗?那就用||

第二个就比较麻烦了。你怎么知道你需要丢弃 falsy 值呢?第一个代码片段展示了如果这样做会发生什么: f(0) 会丢弃零,从而产生不同的结果。这是一个有些常见的 bug 来源。由于构造a = b || c常用于引入回退值(在这种情况下为c),因此它可能会在你不想要的时候意外回退到它。

function updateAge(years) {
  var yearsToAdd = years || 1;
  return this.age + yearsToAdd
}

如果您想提供 updateAge() (无参数) 的回退,那么这将起作用,但如果您调用 updateAge(0)(不更新),则会失败。同样的,您可以有:

function sayMyName(greeting) {
  var prefix = greeting || "Hello, my name is ";
  return prefix + this.firstName;
}

同样,这对于sayMyName()(没有参数)有效,但是对于sayMyName("")(明确无欢迎语)则失败。

一般来说,如果您提供的默认值与假值不同,那么您可能会遇到问题。但是,如果您的默认值是假值- num || 0str || "" - 那么实际上并不重要。

很少(或者应该很少)预期混合类型(数字、字符串、对象等)并为其提供回退: input || fallback 是有效的,但将拒绝空字符串和零。除非您明确不想要其中任何一个,否则通常都会出现问题。

简而言之,您应该始终使用 nullish 合并运算符 ??。这将减轻记忆负担以确定它是否可能是错误的。也没有太多理由避免它。它比布尔 OR 新,因此在旧环境中不起作用,但是现在您可能应该为这些转译代码。如果您不能或不想这样做,则别无选择,应该使用布尔 OR。


谢谢@vlaz,这是最清晰的答案(我加粗了最好的部分)-如何判断何时使用??或||以及一些示例。 - mikemaccana
@mikemaccana 我正在处理这些:D 简短回答 - 如果你想防止null,那么使用??。如果你想丢弃零和空字符串,那么使用||。老实说,我想不出现在有多少理由需要使用||,除非你支持旧的环境并且不转译你的代码。但那也是比较小众的情况。 - VLAZ

3
简言之,当你想要从可能的 nullundefined 的源中分配一个变量,但希望为该变量提供默认值时,使用空值合并运算符 ??
如果你想避免与 JavaScript 中真假的定义[1] 混淆,那么请避免使用 ||。 空值合并运算符 ?? JavaScript 中真假的定义 Angular 中的一个真实示例。
/* parse the url pattern 
/search?keyword=mykeyword&page=3
/search?keyword=mykeyword
*/

  ngOnInit(): void {
    const keyword = this.route.snapshot.queryParamMap.get('keyword') ?? 'default keyword';
    const page = this.route.snapshot.queryParamMap.get('page') ?? '0';
    this.queryResult$ = this.service.getQueryResult(keyword, +page);
    this.keyword = keyword;
  }

[1]:

每种编程语言都有其自己对于假值和真值的定义。判断一个值是否为真值的基本方法是,如果显式类型转换函数 bool(value) 的结果为 true,那么该值就是真值

对于那些可能跨越多个语言工作的开发者来说,PHP 和 C# 也同时拥有 ?? 这一运算符。详见 PHPC#


1
作为一些反馈,这句话读起来不太好:“如果你想避免X,就不要做Y”。感谢您抽出时间回答。 - mikemaccana

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