javascript中是否存在null-coalescing(Elvis)运算符或安全导航运算符?


209

我将举例说明:

猫王算子(?:)

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

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

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

安全导航操作员(?。)

安全导航运算符用于避免NullPointerException。通常,当您引用对象时,可能需要在访问对象的方法或属性之前验证其是否为null。为了避免这种情况,安全的导航运算符将只返回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

9
“猫王运算符”存在于C#中-但它被称为空合并运算符(不那么令人兴奋):-)
Cameron

如果您想使用其他语法,可以看看cofeescript
Lime

这个问题有点混乱...它混淆了3个不同的运算符?:(实习生,在问题中有明确拼写,可能是错字),?? (空合并,JavaScript中确实存在)和?。(Elvis)在JavaScript中不存在。答案并不能很好地阐明这种区别。
JoelFan 2015年

2
@JoelFan您可以提供指向??javascript中正确的null-coalescence()文档的链接吗?到目前为止,我发现的所有内容都表明JS仅具有“假”合并(使用||)。
查尔斯伍德

1
好吧,我并不是说JS确实有??吗?但是它有空值...但是即使在那儿我还是有点错。话虽如此,我已经看到很多使用||的JS代码。尽管存在虚假陷阱,但仍为无效合并
JoelFan

Answers:


138

您可以使用逻辑“ OR”运算符代替Elvis运算符:

例如displayname = user.name || "Anonymous"

但是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'>实际上仅用于开发而非生产。


14
在大多数情况下是合乎逻辑的还是不太需要的东西,因为您可能希望它仅在未定义左边的情况下才选择右边的操作数,而不是在定义和错误的情况下才选择右边的操作数。
user2451227 2014年

是我的错误,还是真的<script type='coffee/script>'
JCCM

2
CoffeeScript主页使用<script type="text/coffeescript">
Elias Zamaria'8

19
虽然这回答了问题,但它几乎完全是关于coffeescript而不是javascript的,并且超过一半以上是关于描述与OP无关的coffeescript好处的。我建议将其简化为与问题相关的内容,就像coffeescript的其他好处一样。
jinglesthula

4
我要去香蕉吗?当然,对user2451227(目前有4票)的反对是无效的,因为如果定义了表达式/左操作数并且伪造了三元数的中间操作数(即Elvis运算符的右操作数),则同样不会被选择。在这两种情况下,您都必须走x === undefined
麦克啮齿动物

114

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

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

streetName然后将是任一值user.address.streetundefined

如果您希望它默认为其他选项,则可以将其与上述快捷方式结合使用或提供:

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

7
再加上一个有关空传播和空合并的绝佳示例!
杰伊·威克

1
这行得通,只是您不知道是否会得到null或undefined
Dave Cousineau

82

JavaScript的逻辑OR运算符短路,并可以取代你的“猫王”操作符:

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

但是,据我所知,这与您的?.操作员并不相同。


13
+1,我忘了||可以那样使用。请注意,这不仅凝聚时表达null,而且当它的不确定,false0,或空字符串。
卡梅伦

@Cameron,的确如此,但这是问题中提到的,似乎是提问者的意图。""0可能是意外,虽然:)
弗雷德里克·哈米迪

72

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

a?.b?.c

可以重写为:

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

这利用了以下事实:在对象上获取未知属性会返回未定义,而不是像null或上那样引发异常undefined,因此我们在导航之前将null和undefined替换为空对象。


14
好吧,这很难读,但是比那种冗长的&&方法更好。+1。
尖叫

1
实际上,那是javascript中唯一真正安全的运算符。上面提到的逻辑“或”运算符是另外一回事。
vasilakisfil

@Filippos您可以举一个逻辑OR vs &&&方法中不同行为的示例吗?我想不出有什么不同
The Red Pea

它还允许浏览匿名值,而无需先将其分配给变量。
马特·詹金斯

1
爱它!如果要在可能不返回任何结果的array.find()操作之后获取对象的属性,则非常有用
Shiraz

24

我认为lodash _.get()可以为您提供帮助,例如和中_.get(user, 'name'),以及诸如_.get(o, 'a[0].b.c', 'default-value')


5
这种方法的主要问题在于,由于属性的名称是字符串,因此您无法再以100%的信任度使用IDE的重构功能
RPDeshaies

21

当前有一个规范草案:

https://github.com/tc39/proposal-optional-chaining

https://tc39.github.io/proposal-optional-chaining/

现在,虽然,我喜欢用lodashget(object, path [,defaultValue])DLVdelve(obj, keypath)

更新(截至2019年12月23日):

可选链接已移至阶段4


Lodash使javascript编程更可口
壁虎

2
可选的链接最近刚搬到舞台4,所以我们会看到它在ES2020
尼克·帕森斯

1
@NickParsons谢谢!已更新答案。
杰克·塔克

18

2019更新

JavaScript现在具有猫王操作员和安全导航操作员的等效项。


安全财产通道

可选的链接运营商?.)是目前阶段4 ECMAScript的建议。您现在可以在Babel中使用它

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

逻辑AND运算符&&)是“老”,更详细的处理这种情况的方式。

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

提供默认值

nullish合并运算符??)是目前第3阶段的ECMAScript 建议。您现在可以在Babel中使用它。如果运算符的左侧为空值(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';

逻辑OR运算符||)是一种替代解决方案略有不同的行为。如果运算符的左侧为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';

1
这个答案需要更多的投票。Nullish合并运算符,现在是第4阶段
Yerke

13

对于前者,您可以使用||。Javascript的“逻辑或”运算符,而不是简单地返回固定的true和false值,遵循遵循以下规则:如果为true,则返回其left参数;否则,求值并返回其right参数。当您仅对真值感兴趣时,它的工作原理相同,但是这也意味着将foo || bar || baz返回foo,bar或baz中包含真值的最左边的一个

不过,您不会找到可以区分false和null的值,并且0和empty string是false值,因此请避免使用可以合法为0或的value || default构造。value""


4
很好,请注意,当左操作数为非null的falsey值时,这可能导致意外的行为。
Shog9 2011年

11

就在这里!🍾

可选链接处于阶段4,这使您可以使用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版本-babeljs.io/docs/zh/babel-plugin-proposal-optional-chaining
wedi

我认为这个答案确实会产生误导。
莱昂纳多·雷尔

1
这个答案不太正确!可选链接仍处于第3阶段,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


4

这通常称为空合并运算符。JavaScript没有一个。


3
从严格意义上讲,它为true,但正如其他答案所指出的那样,JavaScript的逻辑OR运算符的行为可能类似于假的 coalescing运算符,从而使您在许多情况下都能达到同样的简洁性。
Shog9 2011年

1
这不是null运算符。空合并仅适用于单个值,不适用于属性访问/函数调用链。您已经可以在JavaScript中使用逻辑OR运算符进行空合并。

不,您可以使用JavaScript中的逻辑OR进行虚假合并。
andresp '17


2

我有一个解决方案,可以根据您的需要进行调整,摘录自我的一个库:

    elvisStructureSeparator: '.',

    // An Elvis operator replacement. See:
    // http://coffeescript.org/ --> The Existential Operator
    // http://fantom.org/doc/docLang/Expressions.html#safeInvoke
    //
    // The fn parameter has a SPECIAL SYNTAX. E.g.
    // some.structure['with a selector like this'].value transforms to
    // 'some.structure.with a selector like this.value' as an fn parameter.
    //
    // Configurable with tulebox.elvisStructureSeparator.
    //
    // Usage examples: 
    // tulebox.elvis(scope, 'arbitrary.path.to.a.function', fnParamA, fnParamB, fnParamC);
    // tulebox.elvis(this, 'currentNode.favicon.filename');
    elvis: function (scope, fn) {
        tulebox.dbg('tulebox.elvis(' + scope + ', ' + fn + ', args...)');

        var implicitMsg = '....implicit value: undefined ';

        if (arguments.length < 2) {
            tulebox.dbg(implicitMsg + '(1)');
            return undefined;
        }

        // prepare args
        var args = [].slice.call(arguments, 2);
        if (scope === null || fn === null || scope === undefined || fn === undefined 
            || typeof fn !== 'string') {
            tulebox.dbg(implicitMsg + '(2)');
            return undefined;   
        }

        // check levels
        var levels = fn.split(tulebox.elvisStructureSeparator);
        if (levels.length < 1) {
            tulebox.dbg(implicitMsg + '(3)');
            return undefined;
        }

        var lastLevel = scope;

        for (var i = 0; i < levels.length; i++) {
            if (lastLevel[levels[i]] === undefined) {
                tulebox.dbg(implicitMsg + '(4)');
                return undefined;
            }
            lastLevel = lastLevel[levels[i]];
        }

        // real return value
        if (typeof lastLevel === 'function') {
            var ret = lastLevel.apply(scope, args);
            tulebox.dbg('....function value: ' + ret);
            return ret;
        } else {
            tulebox.dbg('....direct value: ' + lastLevel);
            return lastLevel;
        }
    },

奇迹般有效。享受更少的痛苦!


看起来很有希望,请您提交完整资料吗?你在公共场所有它吗?(例如GitHub)
Eran Medan'3

1
我将从使用它的代码中摘录一个片段,并将在一周左右的时间内将其发布到GitHub上。
balazstth 2012年

2

您可以自己滚动:

function resolve(objectToGetValueFrom, stringOfDotSeparatedParameters) {
    var returnObject = objectToGetValueFrom,
        parameters = stringOfDotSeparatedParameters.split('.'),
        i,
        parameter;

    for (i = 0; i < parameters.length; i++) {
        parameter = parameters[i];

        returnObject = returnObject[parameter];

        if (returnObject === undefined) {
            break;
        }
    }
    return returnObject;
};

并像这样使用它:

var result = resolve(obj, 'a.b.c.d'); 

*如果a,b,c或d中的任何一个未定义,则结果不确定。


1

我阅读了这篇文章(https://www.beyondjava.net/elvis-operator-aka-safe-navigation-javascript-typescript),并使用代理修改了该解决方案。

function safe(obj) {
    return new Proxy(obj, {
        get: function(target, name) {
            const result = target[name];
            if (!!result) {
                return (result instanceof Object)? safe(result) : result;
            }
            return safe.nullObj;
        },
    });
}

safe.nullObj = safe({});
safe.safeGet= function(obj, expression) {
    let safeObj = safe(obj);
    let safeResult = expression(safeObj);

    if (safeResult === safe.nullObj) {
        return undefined;
    }
    return safeResult;
}

您这样称呼它:

safe.safeGet(example, (x) => x.foo.woo)

对于沿其路径遇到null或undefined的表达式,结果将是不确定的。您可以疯狂修改Object原型!

Object.prototype.getSafe = function (expression) {
    return safe.safeGet(this, expression);
};

example.getSafe((x) => x.foo.woo);


1

长期以来,这对我来说都是一个问题。我必须想出一种解决方案,一旦获得Elvis操作员之类的东西,就可以轻松移植。

这就是我用的;适用于数组和对象

把它放在tools.js文件或其他东西中

// this will create the object/array if null
Object.prototype.__ = function (prop) {
    if (this[prop] === undefined)
        this[prop] = typeof prop == 'number' ? [] : {}
    return this[prop]
};

// this will just check if object/array is null
Object.prototype._ = function (prop) {
    return this[prop] === undefined ? {} : this[prop]
};

用法示例:

let student = {
    classes: [
        'math',
        'whatev'
    ],
    scores: {
        math: 9,
        whatev: 20
    },
    loans: [
        200,
        { 'hey': 'sup' },
        500,
        300,
        8000,
        3000000
    ]
}

// use one underscore to test

console.log(student._('classes')._(0)) // math
console.log(student._('classes')._(3)) // {}
console.log(student._('sports')._(3)._('injuries')) // {}
console.log(student._('scores')._('whatev')) // 20
console.log(student._('blabla')._('whatev')) // {}
console.log(student._('loans')._(2)) // 500 
console.log(student._('loans')._(1)._('hey')) // sup
console.log(student._('loans')._(6)._('hey')) // {} 

// use two underscores to create if null

student.__('loans').__(6)['test'] = 'whatev'

console.log(student.__('loans').__(6).__('test')) // whatev

好吧,我知道这会使代码有点不可读,但这是一个简单的线性解决方案,效果很好。我希望它可以帮助某人:)


0

对于使用某些mixin的安全导航操作员来说,这是一个有趣的解决方案。

http://jsfiddle.net/avernet/npcmv/

  // Assume you have the following data structure
  var companies = {
      orbeon: {
          cfo: "Erik",
          cto: "Alex"
      }
  };

  // Extend Underscore.js
  _.mixin({ 
      // Safe navigation
      attr: function(obj, name) { return obj == null ? obj : obj[name]; },
      // So we can chain console.log
      log: function(obj) { console.log(obj); }
  });

  // Shortcut, 'cause I'm lazy
  var C = _(companies).chain();

  // Simple case: returns Erik
  C.attr("orbeon").attr("cfo").log();
  // Simple case too, no CEO in Orbeon, returns undefined
  C.attr("orbeon").attr("ceo").log();
  // IBM unknown, but doesn't lead to an error, returns undefined
  C.attr("ibm").attr("ceo").log();

0

我创建了一个软件包,使它更易于使用。

NPM jsdig Github jsdig

您可以处理诸如和对象之类的简单事情:

const world = {
  locations: {
    europe: 'Munich',
    usa: 'Indianapolis'
  }
};

world.dig('locations', 'usa');
// => 'Indianapolis'

world.dig('locations', 'asia', 'japan');
// => 'null'

或更复杂:

const germany = () => 'germany';
const world = [0, 1, { location: { europe: germany } }, 3];
world.dig(2, 'location', 'europe') === germany;
world.dig(2, 'location', 'europe')() === 'germany';

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.