Есть ли в javascript оператор с нулевым слиянием (Elvis) или оператор безопасной навигации? - PullRequest
173 голосов
/ 07 июля 2011

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

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

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

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

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

Оператор безопасной навигации (?.)

Оператор безопасной навигации используется, чтобы избежать исключения NullPointerException.Обычно, когда у вас есть ссылка на объект, вам может потребоваться проверить, что он не является нулевым, прежде чем получить доступ к методам или свойствам объекта.Чтобы избежать этого, оператор безопасной навигации будет просто возвращать ноль вместо выдачи исключения, например:

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

Ответы [ 17 ]

3 голосов
/ 16 февраля 2012

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

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

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

2 голосов
/ 04 ноября 2014

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

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 голос
/ 01 февраля 2019

Вот моя функция "Элвис". Передайте корневой объект и цепочку как строку. Он всегда возвращает первый неопределенный элемент цепочки. Работает как с объектами, так и с массивами, методами и примитивами.

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'));
1 голос
/ 22 января 2019

Очень поздно, есть предложение [1] для необязательной цепочки в настоящее время на этапе 2, с доступным плагином babel [2]. В настоящее время его нет ни в одном браузере, о котором я знаю.

  1. https://github.com/tc39/proposal-optional-chaining
  2. https://www.npmjs.com/package/@babel/plugin-proposal-optional-chaining
0 голосов
/ 29 ноября 2018

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

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

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

example.getSafe((x) => x.foo.woo);
0 голосов
/ 12 декабря 2016

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

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

и, например, безопасно получить:

var a = e(obj,'e.x.y.z.searchedField');
0 голосов
/ 29 июня 2016

Это было интересное решение для оператора безопасной навигации с использованием некоторого смешанного ..

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();
...