Чи є оператор з безпечним навігацією (JavaScript) або оператор безпечної навігації у JavaScript?


209

Я поясню на прикладі:

Оператор Елвіса (?:)

"Оператор Елвіса" - це скорочення потрійного оператора Java. Один екземпляр, де це зручно, - це повернення значення "розумного за замовчуванням", якщо вираз вирішує значення false або null. Простий приклад може виглядати так:

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

9
"Оператор Елвіса" існує в C # - але він називається нульовим оператором злиття (набагато менш захоплюючим) :-)
Cameron

Якщо ви хочете альтернативного синтаксису, ви можете подивитися cofeescript
Lime

Це запитання - це безлад ... це змішування трьох різних операторів? : (оператор землеробства, прописаний у запитанні, можливо, друкарський помилок), ?? (null coalescing, який існує в JavaScript) і?. (Elvis), який НЕ існує в JavaScript. Відповіді не дуже добре прояснюють цю різницю.
JoelFan

2
@JoelFan чи можете ви надати посилання на документацію щодо належного null-coalescence ( ??) у javascript? Все, що я зараз знаходжу, говорить про те, що JS має лише "фальсифікацію" злиття (використання ||).
Чарльз Вуд

1
Ну, я не хотів сказати, що JS буквально мав ?? але що він мав нульовий злиття ... але навіть там я щось помилявся. Попри це, я бачив багато JS-коду, який використовує || як нульовий злиття, незважаючи на підводні камені
фальси

Відповіді:


138

Ви можете використовувати логічний оператор "АБО" замість оператора Елвіса:

Наприклад 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">.
Elias Zamaria

19
Хоча це відповідає на питання, мова йде майже повністю про кофеескрипт, а не про JavaScript, і більше половини про опис переваг кофеескрипту, не пов'язаних з ОП. Я б запропонував звести його до того, що стосується питання, настільки ж чудового, як і інші переваги coffeescript.
jinglesthula

4
Я збираюся банани? Безумовно, заперечення user2451227 (на даний момент з 4-ма голосами) є недійсним, оскільки середній операнд тернара (тобто правий операнд з оператором Елвіса) не був би обраний, якби вираз / лівий операнд визначений і хибний. В обох випадках ви повинні піти x === undefined.
мійський гризун

114

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

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

streetNameтоді буде або значення, user.address.streetабо undefined.

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

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

7
плюс один - чудовий приклад як нульового поширення, так і нульового зрощення!
Джей Вік

1
це працює, за винятком того, що ви не дізнаєтесь, чи отримаєте з нього
нулеві

82

Логічний оператор Javascript АБО має коротке замикання і може замінити оператора "Elvis":

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

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


13
+1, я забув, що це ||може бути використано таким чином. Майте в виду , що це буде зливатися не тільки тоді , коли вираз null, але коли це не визначено, false, 0або порожній рядок.
Камерон

@Cameron, справді, але це згадується у питанні і, здається, є наміром запитувача. ""або, 0можливо, буде несподівано :)
Frédéric Hamidi

72

Іноді я вважаю корисною таку фразу:

a?.b?.c

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

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

Це використовує той факт, що отримання невідомих атрибутів на об'єкт повертається невизначено, а не викидає виняток, як це робиться на nullабо undefined, тому ми замінюємо нулі та невизначені порожнім об’єктом перед навігацією.


14
Ну, це важко читати, але це краще, ніж той багатослівний &&метод. +1.
крик

1
Це єдиний реальний безпечний оператор у JavaScript. Логічний оператор "АБО", який згадується вище, - це щось інше.
vasilakisfil

@Filippos чи можете ви навести приклад різної поведінки в логічному методі АБО та &&? Я не можу придумати різниці
The Red Pea

Це також дозволяє переміщуватися по анонімному значенню, не призначаючи його першої змінної.
Метт Дженкінс

1
Любіть це! Дійсно корисно, якщо ви хочете отримати властивість об’єкта після операції array.find (), яка може не повернути жодних результатів
Шираз

24

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


5
Моя головна проблема цього методу полягає в тому, що оскільки назва властивостей є рядковим, ви більше не можете використовувати функції рефакторингу IDE зі 100% довірою
RPDeshaies

21

Наразі існує проект специфікації:

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

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

Поки що я люблю користуватися lodashget(object, path [,defaultValue]) або dlvdelve(obj, keypath)

Оновлення (станом на 23 грудня 2019 року):

необов'язкове ланцюг перейшов на етап 4


Лодаш зробить програмування в JavaScript більш приємним
geckos

2
Факультативний ланцюжок нещодавно перейшов на етап 4 , тому ми його побачимо в ES2020
Нік Парсонс

1
@NickParsons Дякую! Оновили відповідь.
Джек Так

18

Оновлення 2019 року

Тепер JavaScript має еквіваленти як для оператора Elvis, так і для оператора безпечної навігації.


Безпечний доступ до власності

За бажанням оператора ланцюжка ( ?.) в даний час є 4 -й стадії ECMAScript пропозицію . Ви можете використовувати його сьогодні з Babel .

// `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 пропозицію . Ви можете використовувати його сьогодні з 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';

Логічний оператор АБО ( ||) є альтернативним рішенням з трохи різним поведінкою . Це дозволяє встановити значення за замовчуванням, якщо ліва частина оператора є хибною . Зауважте, що результат 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';

1
Ця відповідь потребує більшої кількості результатів. Nullish Coalescing Operator зараз на етапі 4.
Yerke

13

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

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


4
Хороша робота, зауваживши, що це може призвести до несподіваної поведінки, коли лівий операнд не є нульовим значенням фальси.
Shog9

11

Так, є! 🍾

Необов'язковий ланцюжок знаходиться на етапі 4, і це дозволяє використовувати user?.address?.streetформулу.

Якщо ви не можете дочекатися випуску, встановіть його @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

console.log(user?.address?.street);  // it works

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


4

Це більш відоме як оператор злиття нуля. У Javascript його немає.


3
правда в строгому сенсі, але, як відзначали інші відповіді, логічний оператор JavaScript АБО може вести себе як щось неправдиве оператор -колегування, що дозволяє досягти тієї ж стислості у багатьох ситуаціях.
Shog9

1
Це не оператор, що поєднує нуль. Нульове поєднання працює лише на одному значенні, а не на ланцюзі викликів доступу до власності / функції. Ви вже можете виконати узгодження нуля з логічним оператором АБО у JavaScript.

Ні, ви можете робити помилкове узгодження з логічним АБО в JavaScript.
andresp


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

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


Виглядаєте перспективно, чи можете ви подати будь-ласка повне джерело? у вас це є десь публічно? (наприклад, 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 не визначений.


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

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


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';

-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/…
Доріан
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.