Существует ли оператор null-coalescing (Elvis) или оператор безопасной навигации в javascript?

я объясню на примере:

Оператор Элвиса (?: )

"оператор Элвиса" - это сокращение тернарного оператора Java. Один пример того, где это удобно для возвращая 'значение по умолчанию' значение если выражение принимает значение false или ноль. Простой пример может выглядеть это:

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

14 ответов


вы можете использовать логический оператор " или " вместо оператора Элвиса:

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'> действительно предназначен только для разработки, а не производства.


в JavaScript логический оператор или is короткое замыкание и может заменить оператор "Элвис":

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

однако, насколько мне известно, нет эквивалента вашему ?. оператора.


Я думаю, что следующее эквивалентно безопасному оператору навигации, хотя и немного дольше:

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

streetName будет либо именем улицы, либо null/undefined.

Если вы хотите, чтобы по умолчанию что-то еще вы можете объединить с указанным выше ярлыком или дать:

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

Я иногда находил следующую идиому полезной:

a?.b.?c

можно переписать так:

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

это использует тот факт, что получение неизвестных атрибутов на объекте возвращает undefined, а не бросает исключение, как это происходит на null или undefined, поэтому мы заменяем null и undefined пустым объектом перед навигацией.


Я думаю лодашь _.get() может помочь здесь, как в _.get(user, 'name'), и более сложные задачи, такие как _.get(o, 'a[0].b.c', 'default-value')


для первого, вы можете использовать ||. Оператор Javascript "logical or" вместо простого возврата консервированных значений true и false следует правилу возврата левого аргумента, если он истинен, и в противном случае оценки и возврата правого аргумента. Когда вас интересует только значение истины, оно работает одинаково, но это также означает, что foo || bar || baz возвращает самый левый из foo, bar или baz, который содержит истинное значение.

вы не найдете однако можно отличить false от null, а 0 и пустая строка являются ложными значениями, поэтому избегайте использования value || default строительство, где value может быть 0 или "".


пока нет. Может быть, скоро. В настоящее время существует проект спецификации:

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

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

пока, однако, мне нравится использовать лодашь get(object, path, [defaultValue]) или dlv delve(obj, keypath)


Это более широко известно как оператор null-coalescing. Javascript не имеет его.


У меня есть решение для этого, адаптировать его к вашим собственным потребностям, отрывок из одной из моих библиотек:

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

работает как шарм. Наслаждайтесь меньше боли!


вот простой эквивалент оператора Элвиса:

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

вы можете достичь примерно того же эффекта, сказав:

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

вы можете свернуть свой собственный:

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) {
            return undefined;
        }
    }
    return returnObject;
};

и использовать его так:

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

* результат не определен ни один из a, b, c или d не определен


Это было интересное решение для безопасного оператора навигации с использованием некоторого 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();

лично я использую

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

и например safe get:

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