245

我将举例说明:

猫王接线员 (?: )

“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 而不是抛出异常,如下所示:

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
4

22 回答 22

165

您可以使用逻辑“或”运算符代替 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'>实际上仅用于开发而不是生产。

于 2011-07-07T16:40:14.197 回答
138

我认为以下相当于安全导航运算符,虽然有点长:

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

streetNameuser.address.street然后将是或的值undefined

如果您希望它默认为其他内容,您可以结合上述快捷方式或提供:

var streetName = (user && user.address && user.address.street) || "Unknown Street";
于 2015-09-04T11:31:23.297 回答
108

2020 更新

JavaScript 现在具有 Elvis Operator 和 Safe Navigation Operator 的等价物。


安全的财产访问

可选链操作符( ?.) 目前是ECMAScript的第4 阶段提案。您今天可以将它与 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.b.c;

提供默认值

nullish合并运算符( ??) 目前是第 4 阶段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 ,它允许您设置默认值。请注意,下面的结果与上面的不同。myVariable3myVariable3

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';
于 2019-12-20T09:58:11.223 回答
88

Javascript 的逻辑 OR 运算符短路的,可以替换您的“Elvis”运算符:

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

但是,据我所知,没有与您的?.运营商等效的产品。

于 2011-07-07T16:36:17.200 回答
85

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

a?.b?.c

可以改写为:

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

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

于 2016-06-17T15:20:59.303 回答
25

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

于 2016-01-16T17:35:10.317 回答
23

目前有一个草案规范:

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 阶段

于 2018-01-16T17:06:01.003 回答
13

对于前者,您可以使用||. Javascript“逻辑或”运算符不是简单地返回固定的真假值,而是遵循如果为真则返回其左参数的规则,否则评估并返回其右参数。当你只对真值感兴趣时,它的工作原理是一样的,但这也意味着foo || bar || baz返回foo、bar 或 baz 中最左边的一个,它包含一个真值

但是,您不会找到可以区分 false 和 null 的方法,并且 0 和空字符串是 false 值,因此请避免使用value || default可以value合法为 0 或"".

于 2011-07-07T16:36:42.810 回答
12

就在这里!

可选链接处于第 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
于 2019-02-01T15:49:24.560 回答
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
于 2015-07-14T21:10:31.203 回答
5

您可以通过以下方式达到大致相同的效果:

var displayName = user.name || "Anonymous";
于 2011-07-07T16:36:38.583 回答
5

2019 年 9 月更新

是的,JS 现在支持这个。v8 即将推出可选链接阅读更多

于 2019-09-18T18:02:50.720 回答
3

这通常称为空合并运算符。Javascript 没有。

于 2011-07-07T16:34:15.600 回答
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;
        }
    },

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

于 2012-02-16T09:50:58.130 回答
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 中的任何一个未定义,则结果未定义。

于 2014-11-04T13:44:31.177 回答
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.prototype.getSafe = function (expression) {
    return safe.safeGet(this, expression);
};

example.getSafe((x) => x.foo.woo);
于 2018-11-28T23:44:43.887 回答
1

很晚才加入,目前在第 2 阶段有一个关于可选链的提议[1],并提供了一个 babel 插件[2]。它目前不在我所知道的任何浏览器中。

  1. https://github.com/tc39/proposal-optional-chaining
  2. https://www.npmjs.com/package/@babel/plugin-proposal-optional-chaining
于 2019-01-22T00:58:45.937 回答
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

好吧,我知道它使代码有点难以阅读,但它是一个简单的单行解决方案并且效果很好。我希望它可以帮助某人:)

于 2019-08-19T14:11:00.607 回答
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();
于 2016-06-29T14:55:59.717 回答
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';
于 2020-04-27T07:44:54.563 回答
0

??可以在 js 中工作,这相当于?:在 kotlin

于 2021-12-18T15:07:35.280 回答
-8

我个人使用

function e(e,expr){try{return eval(expr);}catch(e){return null;}};

例如安全获取:

var a = e(obj,'e.x.y.z.searchedField');
于 2016-12-12T07:56:44.443 回答