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

时间:2011-07-07 16:30:52

标签: javascript jquery groovy safe-navigation-operator

我将通过例子解释:

猫王运营商(?:)

  

“猫王操作员”是一种缩短   Java的三元运算符。一   这个方便的例子是   返回'合理的默认值'   如果表达式解析为false或   空值。一个简单的例子可能看起来像   这样:

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

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

安全导航操作员(?。)

     

使用安全导航操作符   避免NullPointerException。   通常在您有参考时   您可能需要验证的对象   在访问之前它不是null   对象的方法或属性。   为避免这种情况,安全导航   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

20 个答案:

答案 0 :(得分:122)

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

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

但Javascript目前没有其他功能。如果您想要其他语法,我建议您查看CoffeeScript。它有一些类似于你正在寻找的速记。

例如The Existential Operator

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

答案 1 :(得分:87)

我认为以下内容相当于安全导航操作符,但有点长:

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

streetName将是user.address.streetundefined的值。

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

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

答案 2 :(得分:79)

Javascript的logical OR operatorshort-circuiting,可以取代您的“Elvis”运营商:

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

但是,据我所知,与您的?.运营商无关。

答案 3 :(得分:54)

我偶尔会发现以下习惯用法:

a?.b.?c

可以改写为:

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

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

答案 4 :(得分:22)

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

答案 5 :(得分:11)

对于前者,您可以使用||。 Javascript“逻辑或”运算符,而不是简单地返回固定的true和false值,如果它为真,则遵循返回其左参数的规则,否则评估并返回其右参数。如果您只对真值感兴趣,那么它的效果会相同,但这也意味着foo || bar || baz会返回包含真值的foo,bar或baz中最左边的一个。

但是,您找不到可以区分false和null的字符,而0和空字符串都是false值,因此请避免使用value || default构造,其中value可以合法地为0或{{ 1}}。

答案 6 :(得分:11)

答案 7 :(得分:5)

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

答案 8 :(得分:4)

UPDATE SEP 2019

是的,JS现在支持此功能。 v8 read more

即将推出可选链接

答案 9 :(得分:3)

我有一个解决方案,根据你自己的需要定制它,摘自我的一个libs:

    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;
        }
    },

就像一个魅力。享受更少的痛苦!

答案 10 :(得分:3)

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

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

答案 11 :(得分: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中的任何一个未定义,则

*结果未定义。

答案 12 :(得分:1)

跳转到很晚,目前在第2阶段有一个可选链的建议[1],其中提供了babel插件[2]。我目前所知道的浏览器中都没有它。

  1. https://github.com/tc39/proposal-optional-chaining
  2. https://www.npmjs.com/package/@babel/plugin-proposal-optional-chaining

答案 13 :(得分:1)

这是我的“猫王”功能。将根对象和链作为字符串传递。它总是返回链的第一个“未定义”元素。同时适用于对象,数组,方法和基元。

elvis(myObject, 'categories.shirts[0].getPrice().currency');

工作示例:

const elvis = (obj, keychain) => {
  const handleArray = (parent, key) => {
    if (key.indexOf('[') > -1) {
      const arrayName = key.split('[')[0];
      const arrayIndex = +key.split('[')[1].slice(0, -1);
        return parent[arrayName] && parent[arrayName][arrayIndex];
    }
    if (key.indexOf('(') > -1) {
      const methodName = key.split('(')[0];
      return parent[methodName] && parent[methodName]();
    }      
    return parent[key];
  }

  const keys = keychain.split('.');
  let base = obj;

  for (let i = 0; i < keys.length; i += 1) {
    base = handleArray(base, keys[i]);
    if (typeof base === 'undefined') return base;
  }
  return base;
}

//--------

const myObject = {
  categories: {
    getFoo: () => 'foo',
    shirts: [
      { color: 'red' },
      { color: 'blue' }
    ]
  }
}

console.log(elvis(myObject, 'categories.shirts[0].color'));
console.log(elvis(myObject, 'categories.getFoo()'));
console.log(elvis(myObject, 'categories.getBar()'));
console.log(elvis(myObject, 'categories.shirts[0].length'));
console.log(elvis(myObject, 'categories.pans[2].color'));

答案 14 :(得分: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

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

答案 15 :(得分: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';

答案 16 :(得分:0)

2019更新

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


安全财产访问

optional chaining operator?.)当前是stage 4 ECMAScript proposal。您可以use it today with Babel

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

logical AND operator&&)是处理这种情况的“旧”,更详细的方法。

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

提供默认值

nullish coalescing operator??)当前是stage 3 ECMAScript proposal。您可以use it today with 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';

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

答案 17 :(得分:0)

我阅读了这篇文章(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);

答案 18 :(得分: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();

答案 19 :(得分:-5)

我个人使用

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

,例如safe get:

var a = e(obj,'e.x.y.z.searchedField');