JavaScript 中是否有空合并 (Elvis) 运算符或安全导航运算符?

276

我会通过示例来解释:

Elvis操作符(?: )

"Elvis操作符"是Java三元运算符的缩写。其中一个方便的实例是,如果表达式解析为false或null,则返回“合理默认值”。一个简单的示例可能如下所示:

def gender = user.male ? "male" : "female"  //traditional ternary operator usage

def displayName = user.name ?: "Anonymous"  //more compact Elvis operator

安全导航运算符(?.)
安全导航运算符用于避免空指针异常。通常情况下,当您引用一个对象时,您可能需要在访问对象的方法或属性之前验证它不为空。为了避免这种情况,安全导航运算符将简单地返回null而不是抛出异常,如下所示:
def user = User.find( "admin" )           //this might be null if 'admin' does not exist
def streetName = user?.address?.street    //streetName will be null if user or user.address is null - no NPE thrown

11
“Elvis 操作符” 在 C# 中存在,但它被称为 null 合并运算符(相对不那么令人兴奋) :-) - Cameron
5
这个问题有点混乱... 它混淆了三个不同的运算符?:(三目运算符,在问题中拼写出来,可能是一个拼写错误),??(空值合并,JavaScript 中确实存在)和?.(Elvis 运算符),这在 JavaScript 中是不存在的。回答没有很好地澄清这个区别。 - JoelFan
3
@JoelFan,你能提供有关JavaScript中正确使用null合并(??)的文档链接吗?到目前为止,我找到的所有内容都表明JS仅具有“falsey”合并(使用||)。 - Charles Wood
@JoelFan 感谢您的跟进。我猜当您说JS有 ?? 时,那是一个打字错误 :D - Charles Wood
1
好的,我并不是说JS字面上有 ?? 这个运算符,而是它有null-coalesce...但即使在那里,我也有点错了。话虽如此,我已经看到了很多使用||作为null coalesce的JS代码,尽管存在falsey pitfalls。 - JoelFan
显示剩余6条评论
22个回答

173
你可以使用逻辑“或”运算符来替代 Elvis 运算符:
例如,displayname = user.name || "匿名"
但是 JavaScript 目前没有其他功能。如果您想要另一种语法,请参考 CoffeeScript。它有一些类似于您要寻找的简写形式。
例如 存在运算符。
zip = lottery.drawWinner?().address?.zipcode

功能快捷方式

()->  // equivalent to function(){}

性感的函数调用

func 'arg1','arg2' // equivalent to func('arg1','arg2')

还有多行注释和类。 显然,您必须编译此内容为JavaScript或将其插入页面作为<script type='text/coffeescript>,但它可以添加很多功能:)。 使用<script type='text/coffeescript'>只是用于开发而不是生产。


19
在大多数情况下,逻辑或运算符并不是最需要的,因为您可能希望它仅在左操作数未定义时选择右操作数,而不是在左操作数定义但为假时选择右操作数。 - user2451227
2
CoffeeScript主页使用<script type="text/coffeescript"> - Elias Zamaria
28
虽然这篇回答解决了问题,但几乎完全是关于CoffeeScript而不是JavaScript,并且超过一半的内容描述了与OP无关的CoffeeScript优点。我建议将其简化为仅与问题相关的内容,尽管CoffeeScript的其他好处很棒。 - jinglesthula
4
我是不是快要疯了?用户2451227的反对意见(目前有4票)肯定是无效的,因为三元运算符的中间操作数(即伊莉莎操作符的右操作数)如果表达式/左操作数已定义但为假,同样也不会被选中。在这两种情况下,你都需要进行x === undefined的判断。 - mike rodent
5
请考虑更新此内容以提及可选链操作符 ?.。虽然浏览器支持尚未普及到我会在一般代码中使用它的程度,但它正在朝着那个方向发展。此外,现在还有类似状态的空值合并运算符(??)。 - Makyen
显示剩余3条评论

142

我认为以下内容等同于安全导航运算符,尽管有些冗长:

var streetName = user && user.address && user.address.street;

streetName 将是 user.address.street 的值或者是 undefined

如果您想将其默认为其他内容,您可以与上面的快捷方式组合使用或者提供:

var streetName = (user && user.address && user.address.street) || "Unknown Street";

8
感谢您为我们提供如此出色的示例,展现了空值传播和空值合并的优秀应用! - Jay Wick
4
除了你不知道会得到 null 或 undefined 之外,这个方法是有效的。 - Dave Cousineau

142

2020年更新

JavaScript现在有了Elvis运算符和安全导航运算符的等效物。


安全的属性访问

可选链操作符 (?.) 目前是一个 阶段 4 ECMAScript 提案。你可以通过 Babel 立即使用它

// `undefined` if either `a` or `b` are `null`/`undefined`. `a.b.c` otherwise.
const myVariable = a?.b?.c;

逻辑与运算符&&)是处理这种情况的“旧”、更冗长的方式。

const myVariable = a && a.b && a.b.c;

提供默认值

空值合并运算符??)是目前处于第四阶段的ECMAScript 提案。您可以使用Babel立即使用它。如果运算符左侧为nullary值(null/undefined),它允许您设置默认值。

const myVariable = a?.b?.c ?? 'Some other value';

// Evaluates to 'Some other value'
const myVariable2 = null ?? 'Some other value';

// Evaluates to ''
const myVariable3 = '' ?? 'Some other value';
逻辑或运算符 (||) 是一种替代方案,其行为略有不同。它允许您在运算符的左侧是 falsy 时设置默认值。请注意,下面的 myVariable3 的结果与上面的 myVariable3 不同。
const myVariable = a?.b?.c || 'Some other value';

// Evaluates to 'Some other value'
const myVariable2 = null || 'Some other value';

// Evaluates to 'Some other value'
const myVariable3 = '' || 'Some other value';

7
这个答案需要更多的赞。Nullish Coalescing运算符现在已经进入了第四阶段。 - Yerke
3
“a && a.b && a.c” 应该改为 “a && a.b && a.b.c”。我无法自己进行编辑,因为这个更改对于SO来说不够重要,我也不想通过“更改不重要的内容使其达到6个字符”来解决此问题。 - Emily
你可以使用[]语法来实现这个功能 - 参考自https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Optional_chaining,以下所有方式都是可行的:obj.val?.prop obj.val?.[expr] obj.arr?.[index] obj.func?.(args) - Max Waterman
我在开发者工具中尝试了这个代码:"const result = a?.b?.c ?? 'Some other value';",人们本应期望得到的结果是"Some other value",因为我没有定义"a",但实际上它只会抛出一个"Uncaught ReferenceError: a is not defined"的错误。"is not defined"的意思就是未定义,对吗? - Jacques

89

Javascript的逻辑或运算符短路求值,可以替代你的“Elvis”运算符:

var displayName = user.name || "Anonymous";

然而,据我所知,你的?.运算符没有相应的等价物。


17
+1,我忘了||可以以这种方式使用。请注意,这不仅会在表达式为null时合并,还会在其为未定义、false0或空字符串时进行合并。 - Cameron
@Cameron, 确实如此,但这在问题中已经提到,似乎也是询问者的意图。然而,""0可能会出人意料 :) - Frédéric Hamidi

86

我偶尔会发现以下习语很有用:

a?.b?.c

可以重写为:

((a||{}).b||{}).c

这是利用对象的未知属性返回undefined,而不是像nullundefined一样抛出异常的事实,因此在导航之前我们将null和undefined替换为一个空对象。


15
嗯,虽然有点难读,但比那个啰嗦的“&&”方法好。加一。 - shriek
1
这实际上是JavaScript中唯一真正安全的运算符。上面提到的逻辑“或”运算符是另外一回事。 - vasilakisfil
@Filippos,你能举一个逻辑或(OR)和逻辑与(&&)方法中不同行为的例子吗?我想不出有什么区别。 - Nate Anderson
它还允许在不首先将其分配给变量的情况下导航匿名值。 - Matt Jenkins
1
太好了!如果您想在可能不返回任何结果的array.find()操作之后获取对象的属性,这真的非常有用。 - Shiraz

25

我认为lodash的_.get()可以在这里提供帮助,例如使用_.get(user, 'name'),以及更复杂的任务,如_.get(o, 'a[0].b.c', 'default-value')


8
我对这种方法的主要问题在于,由于属性名称是字符串,您无法再完全信任IDE的重构功能。 - RPDeshaies

23

1
Lodash 让 JavaScript 编程更加易懂。 - geckos
5
可选链刚刚被移动到stage 4,因此我们将在ES2020中看到它。 - Nick Parsons

13

对于前者,您可以使用||。Javascript的“逻辑或”运算符不仅仅返回预定的true和false值,而是遵循以下规则:如果它的左参数为true,则返回其左参数;否则评估并返回其右参数。当您只关心真值时,效果相同,但这也意味着foo || bar || baz将返回包含真值的foo、bar或baz中最左边的一个

您找不到可以区分false和null的方法,而0和空字符串为false值,因此避免在value可以合法地为0或""的情况下使用value || default结构。


4
好的,我会尽力进行翻译。请稍等一下。注意到当左操作数为非空的假值时,可能会导致意外的行为,干得好! - Shog9

12

有的!

可选链(optional chaining)已经进入第四阶段,这使您能够使用user?.address?.street的语法。

如果您等不及要使用此功能,请安装@babel/plugin-proposal-optional-chaining插件。以下是我的设置,适用于我,或者只需阅读Nimmo的文章

// package.json

{
  "name": "optional-chaining-test",
  "version": "1.0.0",
  "main": "index.js",
  "devDependencies": {
    "@babel/plugin-proposal-optional-chaining": "7.2.0",
    "@babel/core": "7.2.0",
    "@babel/preset-env": "^7.5.5"
  }
  ...
}
// .babelrc

{
  "presets": [
    [
      "@babel/preset-env",
      {
        "debug": true
      }
    ]
  ],
  "plugins": [
    "@babel/plugin-proposal-optional-chaining"
  ]
}
// index.js

console.log(user?.address?.street);  // it works

4
他问是否有一个,而不是询问你能否添加一个。我认为这并不是非常有用,因为这不是被问到的内容。 - DeanMWake
2
它已经达到了ECMAScript标准化过程的第3阶段。es2020 -- https://babeljs.io/docs/en/babel-plugin-proposal-optional-chaining - wedi
我认为这个答案现在有误导性。 - Leonardo Raele
1
这个答案并不完全正确!可选链仍处于第三阶段,ES2020甚至还没有发布或最终确定。至少你提到了如何在等待其发布之前使用它。 - Maxie Berkmann
@gazdagergo 没问题 :)。 - Maxie Berkmann

6

以下是一个简单的Elvis运算符等效代码:

function elvis(object, path) {
    return path ? path.split('.').reduce(function (nestedObject, key) {
        return nestedObject && nestedObject[key];
    }, object) : object;
}

> var o = { a: { b: 2 }, c: 3 };
> elvis(o)

{ a: { b: 2 }, c: 3 }

> elvis(o, 'a');

{ b: 2 }

> elvis(o, 'a.b');

2

> elvis(o, 'x');

undefined

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