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

210

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

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

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

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
tiagomac
источник
9
«Оператор Элвиса» существует в C #, но он называется оператором объединения нулей (гораздо менее захватывающим) :-)
Кэмерон
Если вам нужен альтернативный синтаксис, вы можете посмотреть cofeescript
Lime
Этот вопрос - беспорядок ... это смешивает 3 разных оператора? : (Тернери оператор, прописанный в вопросе, возможно опечатка), ?? (объединение нулей, которое существует в JavaScript) и?. (Элвис), который не существует в JavaScript. Ответы не очень хорошо проясняют это различие.
JoelFan
2
@JoelFan Можете ли вы предоставить ссылку на документацию относительно правильного null-coalescence ( ??) в javascript? Все, что я обнаружил до сих пор, говорит о том, что у JS есть только «фальси» слияние (использование ||).
Чарльз Вуд
1
Ну, я не хотел сказать, что JS буквально имел ?? но то, что это было слито в ноль ... но даже там я был отчасти неправ. Тем не менее, я видел много кода JS, который использует || как нулевое слияние, несмотря на фальшивые ловушки
JoelFan

Ответы:

139

Вы можете использовать логический оператор «ИЛИ» вместо оператора Элвиса:

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

Лайм
источник
14
логично или не совсем то, что нужно в большинстве случаев, так как вы можете захотеть, чтобы он выбирал правый операнд, только если левый не определен, но не тогда, когда он определен и ложен.
user2451227
Это моя ошибка или это правда <script type='coffee/script>'?
JCCM
2
Домашняя страница CoffeeScript использует <script type="text/coffeescript">.
Элиас Замария
19
В то время как это отвечает на вопрос, это почти полностью о coffeescript, а не javascript, и более половины описывает описание преимуществ coffeescript, не связанных с OP. Я бы предложил свести его к тому, что имеет отношение к вопросу, так же прекрасно, как и другие преимущества coffeescript.
Jinglesthula
4
Я собираюсь бананы? Конечно, возражение user2451227 (в настоящее время имеет 4 голоса) недопустимо, потому что средний операнд троицы (т. Е. Правый операнд с оператором Элвиса) также не будет выбран, если выражение / левый операнд определено и ложно. В обоих случаях вам нужно идти x === undefined.
Майк Грызун
115

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

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

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

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

var streetName = (user && user.address && user.address.street) || "Unknown Street";
samjudson
источник
7
плюс один за отличный пример как нулевого распространения, так и нулевого слияния!
Джей Уик
1
это работает за исключением того, что вы не будете знать, получите ли вы значение null или undefined из него
Дейв Кузино
82

Оператор логического ИЛИ Javascript имеет короткое замыкание и может заменить ваш оператор «Элвис»:

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

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

Фредерик Хамиди
источник
13
+1, я забыл, ||можно использовать таким образом. Имейте в виду , что это будет сливаться не только тогда , когда выражение null, но когда это не определено, false, 0или пустая строка.
Кэмерон
@ Камерон, действительно, но это упомянуто в вопросе и, похоже, является намерением спрашивающего. ""или 0может быть неожиданным, хотя :)
Фредерик Хамиди
72

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

a?.b?.c

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

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

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

James_pic
источник
14
Ну, это трудно читать, но это лучше, чем этот многословный &&метод. +1.
Визг
1
На самом деле это единственный реальный безопасный оператор в javascript. Упомянутый выше логический оператор «ИЛИ» - это нечто иное.
vasilakisfil
@ Филиппос, можете ли вы привести пример различного поведения в методе логического ИЛИ против &&? Я не могу думать о разнице
Красный горох
Это также позволяет перемещаться по анонимному значению, не назначая его сначала переменной.
Мэтт Дженкинс
1
Любить это! Действительно полезно, если вы хотите получить свойство объекта после операции array.find (), которое может не
Shiraz
24

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

tony_k
источник
5
Моя основная проблема с этим методом заключается в том, что, поскольку имена свойств являются строковыми, вы больше не можете использовать функции рефакторинга вашей IDE со 100% доверием
RPDeshaies
21

В настоящее время существует проект спецификации:

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

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

Пока что мне нравится использовать lodashget(object, path [,defaultValue]) или dlvdelve(obj, keypath)

Обновление (по состоянию на 23 декабря 2019 г.):

необязательная цепочка переместилась на этап 4

Джек Так
источник
Lodash делает программирование в javascript более приемлемым
гекконы
2
необязательная цепочка только недавно перешла на этап 4 , поэтому мы увидим это в ES2020
Ник Парсонс
1
@NickParsons Спасибо! Обновили ответ.
Джек Так
18

2019 Обновление

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


Безопасный доступ к собственности

По желанию оператора цепочки ( ?.) в настоящее время является 4 -й стадии ECMAScript предложение . Вы можете использовать это сегодня с Бабелем .

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

Логический оператор ( &&) является «старым», более-многословнымом способом справиться с таким сценарием.

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

Предоставление дефолта

Nullish оператор коалесцирующий ( ??) в настоящее время является стадия 3 ECMAScript предложение . Вы можете использовать это сегодня с Бабелем . Это позволяет вам установить значение по умолчанию, если левая часть оператора является нулевым значением ( 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';

Логический оператор ИЛИ ( ||) является альтернативным решением с немного различным поведением . Это позволяет вам установить значение по умолчанию, если левая часть оператора ложная . Обратите внимание, что результат 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';
jabacchetta
источник
1
Этот ответ нуждается в большем количестве голосов. Нулевой оператор слияния в настоящее время находится в стадии 4.
Йерк
13

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

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

Hobbs
источник
4
Хорошая работа, отметив, что это может привести к неожиданному поведению, когда левый операнд имеет ненулевое значение Ложь.
Shog9
11

Да, есть! 🍾

Необязательная цепочка находится на стадии 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
gazdagergo
источник
4
Он спросил, есть ли такой, а не можете ли вы добавить. Я думаю, что это не супер полезно, учитывая, что это не то, что спросили.
DeanMWake
2
Это достигло стадии 3 процесса стандартизации ECMAScript. es2020 🚀 - babeljs.io/docs/en/babel-plugin-proposal-optional-chaining
WEDI
Я думаю, что этот ответ вводит в заблуждение, как есть.
Леонардо Раэле
1
Этот ответ не совсем правильный! Дополнительная цепочка все еще находится на стадии 3, а ES2020 еще не был выпущен или даже завершен. По крайней мере, вы упомянули, как можно использовать его, не дожидаясь его выпуска.
Макси Беркманн
@gazdagergo Нет проблем :).
Макси Беркманн
6

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

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
cdmckay
источник
5

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

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

Тахир
источник
Не совсем то же самое. OP о нулевом объединении, но, тем не менее, хороший ответ.
Макси Беркманн
4

Это чаще всего называют оператором слияния нулей. У Javascript его нет.

CassOnMars
источник
3
Значение true в строгом смысле, но, как уже отмечалось в других ответах, логический оператор ИЛИ JavaScript может вести себя как своего рода оператор ложного слияния, позволяющий достичь той же краткости во многих ситуациях.
Shog9
1
Это не нуль-сливающийся оператор. Нулевое слияние работает только для одного значения, а не для цепочки вызовов свойств / вызовов функций. Вы уже можете выполнить объединение нулей с помощью логического оператора ИЛИ в JavaScript.
Нет, вы можете сделать ложное слияние с логическим ИЛИ в JavaScript.
andresp
3

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

var displayName = user.name || "Anonymous";
Джастин этир
источник
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;
        }
    },

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

balazstth
источник
Выглядит многообещающе, не могли бы вы представить полный источник? у вас есть это где-нибудь публично? (например, GitHub)
Эран Медан
1
Я создам небольшой отрывок из кода, в котором я его использую, и выложу его на GitHub примерно через неделю.
Балазст
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 не определен.

Pylinux
источник
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)

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

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

example.getSafe((x) => x.foo.woo);
Сэм
источник
1

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

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

положить это в файл 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

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

Neut
источник
0

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

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();
Дин Хиллер
источник
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';
Devchris
источник
-6

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

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

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

var a = e(obj,'e.x.y.z.searchedField');
Кудлатый
источник
2
Прежде всего, вы действительно не должны использовать eval . Во-вторых, это даже не работает: e({a:{b:{c:{d:'test'}}}}, 'a.b.c.d')возвращается null.
Pylinux
@Pylinux в основном то , что будет работать это e = eval, var a = eval('obj.a.b.c.d'). evalдаже не принимает второй параметр ... developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
Дориан