Есть ли в javascript оператор с нулевой связью (Elvis) или безопасный навигатор?

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

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

"Оператор Элвиса" - это сокращение тройного оператора 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

Ответ 1

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

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

Ответ 2

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

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

streetName тогда будет либо значением user.address.street или undefined.

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

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

Ответ 4

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

a?.b?.c

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

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

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

Ответ 5

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

Ответ 7

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

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

Ответ 8

Это более широко известно как оператор с нулевым коалесцированием. У Javascript нет этого.

Ответ 9

Здесь простой оператор 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

Ответ 10

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

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

Ответ 11

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

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

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

Ответ 12

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

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 не определен.

Ответ 13

Да, есть! 🍾

foo?.bar наконец-то работает в JS!

Вы должны установить @babel/plugin-proposal-optional-chaining для этого. Вот мои настройки, которые позволяют это:

// 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

const foo = { bar: "hello" }
console.log(foo?.bar);  // it works

Ответ 14

Я прочитал эту статью (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)

Результат будет неопределенным для выражения, которое встречает ноль или неопределенное на своем пути. Вы можете сойти с ума и изменить прототип Object!

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

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

Ответ 16

Это было проблемой для меня долгое время. Мне нужно было найти решение, которое можно легко перенести, как только мы получим оператора Элвиса или что-то в этом роде.

Это то, что я использую; работает как для массивов, так и для объектов

поместите это в файл 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

хорошо, я знаю, что это делает код немного нечитаемым, но это простое решение с одним вкладышем и отлично работает. Надеюсь, это кому-нибудь поможет :)

Ответ 17

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

Ответ 18

ОБНОВЛЕНИЕ СЕНТЯБРЬ 2019

Да, JS теперь поддерживает это. Опциональная цепочка скоро появится в v8 подробнее

Ответ 19

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

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

и, например, безопасный get:

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