Czy w javascript jest operator zerowej koalescencji (Elvis) lub operator bezpiecznej nawigacji?


209

Wytłumaczę na przykładzie:

Elvis Operator (?:)

„Operator Elvisa” to skrót od trójskładnikowego operatora Javy. Jednym z przykładów, w których jest to przydatne, jest zwrócenie „rozsądnej wartości domyślnej”, jeśli wyrażenie ma wartość false lub zero. Prosty przykład może wyglądać następująco:

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

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

Operator bezpiecznej nawigacji (?.)

Operator Bezpiecznej nawigacji służy do unikania wyjątku NullPointerException. Zazwyczaj, gdy masz odwołanie do obiektu, może być konieczne sprawdzenie, czy nie ma on wartości zerowej przed uzyskaniem dostępu do metod lub właściwości obiektu. Aby tego uniknąć, operator bezpiecznej nawigacji po prostu zwróci wartość null zamiast zgłaszania wyjątku:

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
„Elvis Operator” istnieje w C # - ale nazywa się on zerowym operatorem koalescencyjnym (znacznie mniej ekscytującym) :-)
Cameron

Jeśli chcesz alternatywną składnię, możesz zajrzeć do cofeecript
Lime

To pytanie jest trochę bałaganem ... miesza 3 różnych operatorów? : (operator ternery, wymieniony w pytaniu, być może literówka), ?? (zerowe łączenie, które istnieje w JavaScript) i?. (Elvis), który NIE istnieje w JavaScript. Odpowiedzi nie wyjaśniają tego rozróżnienia bardzo dobrze.
JoelFan

2
@JoelFan, czy możesz podać link do dokumentacji dotyczącej prawidłowej zerowej koalescencji ( ??) w javascript? Wszystko, co do tej pory znajdowałem, sugeruje, że JS ma tylko „falsey” koalescencji (używania ||).
Charles Wood

1
Cóż, nie chciałem powiedzieć, że JS dosłownie miał? ale to, że miało zerową koalescencję ... ale nawet tam się myliłem. To powiedziawszy, widziałem DUŻO kodu JS, który używa || jako coalesce null, mimo pułapek falsey
JoelFan

Odpowiedzi:


138

Możesz użyć logicznego operatora „LUB” zamiast operatora Elvisa:

Na przykład displayname = user.name || "Anonymous".

Jednak Javascript nie ma obecnie innych funkcji. Polecam spojrzenie na CoffeeScript, jeśli chcesz alternatywną składnię. Ma trochę skrót, który jest podobny do tego, czego szukasz.

Na przykład operator egzystencjalny

zip = lottery.drawWinner?().address?.zipcode

Skróty funkcji

()->  // equivalent to function(){}

Seksowne wywołanie funkcji

func 'arg1','arg2' // equivalent to func('arg1','arg2')

Istnieją również komentarze i zajęcia wieloliniowe. Oczywiście musisz to skompilować do javascript lub wstawić na stronę, ponieważ <script type='text/coffeescript>'dodaje to wiele funkcji :). Używanie <script type='text/coffeescript'>jest tak naprawdę przeznaczone tylko do rozwoju, a nie do produkcji.


14
logiczne lub nie jest w większości przypadków potrzebne, ponieważ możesz wybrać odpowiedni operand tylko wtedy, gdy lewy jest niezdefiniowany, ale nie wtedy, gdy jest zdefiniowany i fałszywy.
user2451227,

Czy to mój błąd, czy naprawdę <script type='coffee/script>'?
JCCM,

2
Używa strony głównej CoffeeScript <script type="text/coffeescript">.
Elias Zamaria,

19
Chociaż to odpowiada na pytanie, to prawie w całości chodzi o coffeescript niż javascript, a ponad połowa dotyczy opisu korzyści związanych z coffeescript niezwiązanych z PO. Sugerowałbym sprowadzenie tego do tego, co jest istotne dla pytania, tak wspaniałe, jak inne zalety coffeescript.
jinglesthula

4
Idę na banany? Z pewnością sprzeciw użytkownika 2451227 (obecnie z 4 głosami) jest nieważny, ponieważ środkowy operand trójki (tj. Prawy operand z operatorem Elvisa) nie zostałby wybrany, gdyby wyrażenie / lewy operand zostało zdefiniowane i było fałszywe. W obu przypadkach musisz wtedy iść x === undefined.
Mike gryzoni

114

Myślę, że poniższe informacje są równoważne operatorowi bezpiecznej nawigacji, choć nieco dłużej:

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

streetNamebędzie wówczas wartością user.address.streetlub undefined.

Jeśli chcesz, aby domyślnie był ustawiony na coś innego, możesz połączyć go z powyższym skrótem lub podać:

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

7
plus jeden za doskonały przykład zarówno zerowej propagacji, jak i zerowej koalescencji!
Jay Wick

1
działa to z tym wyjątkiem, że nie będziesz wiedział, czy zostaniesz z niego zerowy czy niezdefiniowany
Dave Cousineau,

82

Logiczny operator OR Javascript powoduje zwarcie i może zastąpić operatora „Elvis”:

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

Jednak o ile wiem, twój ?.operator nie ma odpowiednika .


13
+1, zapomniałem, że ||można tego użyć. Należy pamiętać, że to będzie łączyć nie tylko wtedy, gdy wyrażenie jest null, ale także, gdy jest niezdefiniowany, false, 0, lub pusty ciąg.
Cameron

@Cameron, rzeczywiście, ale jest to wspomniane w pytaniu i wydaje się być intencją pytającego. ""lub 0może być nieoczekiwany :)
Frédéric Hamidi

72

Czasami znajduję następujący idiom:

a?.b?.c

może być przepisany jako:

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

Wykorzystuje to fakt, że uzyskiwanie nieznanych atrybutów dla obiektu zwraca niezdefiniowane, zamiast rzucania wyjątku, jak to robi na nulllub undefined, dlatego przed nawigacją zastępujemy pusty i niezdefiniowany pusty obiekt.


14
Cóż, jest trudny do odczytania, ale jest lepszy niż ta pełna &&metoda. +1.
wrzask

1
To właściwie jedyny bezpieczny operator w javascript. Wspomniany powyżej logiczny operator „LUB” to coś innego.
vasilakisfil

@Filippos czy możesz podać przykład innego zachowania w logicznej metodzie OR vs metoda &&? Nie mogę myśleć o różnicy
The Red Pea

Umożliwia także nawigację po anonimowej wartości bez wcześniejszego przypisywania jej do zmiennej.
Matt Jenkins,

1
Kocham to! Jest to bardzo przydatne, jeśli chcesz uzyskać właściwość obiektu po operacji array.find (), która może nie zwrócić żadnych wyników
Shiraz

24

myślę, że lodash _.get()może pomóc tutaj, jak _.get(user, 'name')i przy bardziej złożonych zadaniach, takich jak_.get(o, 'a[0].b.c', 'default-value')


5
Moim głównym problemem związanym z tą metodą jest fakt, że ponieważ nazwy właściwości są ciągami znaków, nie można już używać funkcji refaktoryzacji IDE przy 100% zaufaniu
RPDeshaies

21

Obecnie istnieje wersja robocza specyfikacji:

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

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

Na razie jednak lubię używać lodashget(object, path [,defaultValue]) lub dlvdelve(obj, keypath)

Aktualizacja (stan na 23 grudnia 2019 r.):

opcjonalne łączenie zostało przeniesione do etapu 4


Lodash sprawia, że ​​programowanie w javascript jest bardziej smaczne
gekony

2
opcjonalne łączenie łańcuchów niedawno przeniosłem na etap 4 , więc zobaczymy to w ES2020
Nick Parsons,

1
@NickParsons Thanks! Zaktualizowałem odpowiedź.
Jack Tuck

18

Aktualizacja 2019

JavaScript ma teraz odpowiedniki zarówno dla Elvisa Operatora, jak i Operatora Bezpiecznej Nawigacji.


Bezpieczny dostęp do nieruchomości

Opcjonalnie operatora łańcuchowym ( ?.) jest obecnie etap 4 ECMAScript Wniosek . Możesz go używać dzisiaj z Babel .

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

Logiczny AND operator ( &&) jest „stary”, bardziej rozwlekły sposób obsługiwać ten scenariusz.

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

Zapewnienie wartości domyślnej

Nullish operatora koalescencyjny ( ??) jest obecnie etap 3 ECMAScript Wniosek . Możesz go używać dzisiaj z Babel . Pozwala ustawić wartość domyślną, jeśli lewa strona operatora jest wartością zerową ( 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';

Operator logiczny LUB ( ||) jest alternatywne rozwiązanie z nieco odmienne zachowanie . Pozwala ustawić wartość domyślną, jeśli lewa strona operatora jest fałszywa . Pamiętaj, że wynik myVariable3poniżej różni się od myVariable3powyższego.

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
Ta odpowiedź wymaga więcej głosów pozytywnych. Nullish Coalescing Operator jest teraz na etapie 4.
Yerke

13

W przypadku tego pierwszego możesz użyć ||. Operator „logiczny lub” JavaScript, zamiast po prostu zwracać zapisane w prawdzie i fałszu wartości, kieruje się zasadą zwracania lewego argumentu, jeśli jest prawdziwy, a poza tym oceniania i zwracania prawego argumentu. Gdy interesuje Cię tylko wartość prawdy, działa ona tak samo, ale oznacza to również, że foo || bar || bazzwraca wartość skrajnie lewą z wartości foo, bar lub baz, która zawiera wartość prawdziwą .

Nie znajdziesz jednak takiego, który odróżniałby fałsz od null, a 0 i pusty ciąg są wartościami fałszywymi, więc unikaj używania value || defaultkonstrukcji, gdzie valuemoże to być 0 lub "".


4
Dobra robota, zauważając, że może to spowodować nieoczekiwane zachowanie, gdy lewy operand ma niepustą wartość falsey.
Shog9

11

Tak jest! 🍾

Opcjonalne tworzenie łańcuchów znajduje się na etapie 4, co umożliwia użycie user?.address?.streetformuły.

Jeśli nie możesz się doczekać wydania, zainstaluj @babel/plugin-proposal-optional-chainingi możesz go użyć. Oto moje ustawienia, które działają dla mnie lub po prostu przeczytaj artykuł 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

4
Zapytał, czy jest jeden, a nie czy można go dodać. Myślę, że to nie jest bardzo przydatne, biorąc pod uwagę, że nie o to pytano.
DeanMWake

2
Osiągnął 3. etap procesu standaryzacji ECMAScript. es2020 🚀 - babeljs.io/docs/en/babel-plugin-proposal-optional-chaining
wedi

Myślę, że ta odpowiedź jest myląca.
Leonardo Raele,

1
Ta odpowiedź nie jest całkiem poprawna! Opcjonalne łączenie jest nadal w fazie 3, a ES2020 nie został jeszcze wydany ani nawet sfinalizowany. Przynajmniej wspomniałeś, jak można go używać bez czekania na jego wydanie.
Maxie Berkmann

@gazdagergo Nie ma problemu :).
Maxie Berkmann

6

Oto prosty odpowiednik operatora 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

5

AKTUALIZACJA WRZESIEŃ 2019

Tak, JS obsługuje to teraz. Opcjonalne łączenie już wkrótce w wersji v8 czytaj więcej


Nie do końca to samo. OP jest na temat koalescencji zerowej, ale mimo to fajna odpowiedź.
Maxie Berkmann

4

Jest to bardziej znane jako operator zerowo-koalescencyjny. JavaScript nie ma takiego.


3
prawda w ścisłym tego słowa znaczeniu, ale jak zauważono w innych odpowiedziach, logiczny operator OR JavaScript może zachowywać się jak operator fałszywie koalescencyjny, co pozwala osiągnąć ten sam zwięzłość w wielu sytuacjach.
Shog9,

1
To nie jest operator zerowo-koalescencyjny. Koalescencja zerowa działa tylko na jednej wartości, a nie na łańcuchu dostępu do właściwości / wywołań funkcji. Możesz już wykonywać zerowanie z logicznym operatorem OR w JavaScript.

Nie, możesz wykonać fałszywe połączenie z logiczną operacją OR w JavaScript.
i

3

Możesz osiągnąć mniej więcej ten sam efekt, mówiąc:

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

2

Mam na to rozwiązanie, dostosuj je do własnych potrzeb, fragment jednego z moich libów:

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

działa jak marzenie. Ciesz się mniejszym bólem!


Wygląda obiecująco, czy możesz przesłać pełne źródło? czy masz to gdziekolwiek publiczne? (np. GitHub)
Eran Medan

1
Stworzę mały fragment kodu, w którym go używam, i opublikuję go na GitHub za około tydzień.
balazstth

2

Możesz rzucić własny:

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

I użyj tego w ten sposób:

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

* wynik jest niezdefiniowany, jeśli jeden z a, b, c lub d jest niezdefiniowany.


1

Przeczytałem ten artykuł ( https://www.beyondjava.net/elvis-operator-aka-safe-navigation-javascript-typescript ) i zmodyfikowałem rozwiązanie za pomocą proxy.

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

Nazywasz to tak:

safe.safeGet(example, (x) => x.foo.woo)

Wynik będzie niezdefiniowany dla wyrażenia, które napotka null lub niezdefiniowane na swojej ścieżce. Możesz zwariować i zmodyfikować prototyp Object!

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

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


1

To był dla mnie problem przez długi czas. Musiałem znaleźć rozwiązanie, które można łatwo migrować, gdy tylko dostaniemy operatora Elvisa lub coś takiego.

Tego używam; działa zarówno dla tablic, jak i obiektów

umieść to w pliku tools.js lub coś takiego

// 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]
};

przykład użycia:

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

Cóż, wiem, że sprawia, że ​​kod jest nieco nieczytelny, ale jest to proste rozwiązanie liniowe i działa świetnie. Mam nadzieję, że to komuś pomoże :)


0

Było to interesujące rozwiązanie dla bezpiecznego operatora nawigacji korzystającego z niektórych 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();

0

Stworzyłem pakiet, który sprawia, że ​​jest to o wiele łatwiejsze w użyciu.

NPM jsdig Github jsdig

Możesz obsługiwać proste rzeczy, takie jak i sprzeciwiać się:

const world = {
  locations: {
    europe: 'Munich',
    usa: 'Indianapolis'
  }
};

world.dig('locations', 'usa');
// => 'Indianapolis'

world.dig('locations', 'asia', 'japan');
// => 'null'

lub trochę bardziej skomplikowane:

const germany = () => 'germany';
const world = [0, 1, { location: { europe: germany } }, 3];
world.dig(2, 'location', 'europe') === germany;
world.dig(2, 'location', 'europe')() === 'germany';

-6

Osobiście korzystam

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

i na przykład bezpiecznie uzyskaj:

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

2
Po pierwsze naprawdę nie powinieneś używać eval . Po drugie, to nawet nie działa: e({a:{b:{c:{d:'test'}}}}, 'a.b.c.d')zwraca null.
Pylinux

@Pylinux zasadzie to, co będzie działać to e = eval, var a = eval('obj.a.b.c.d'). evalnie bierze nawet drugiego parametru ... developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
Dorian
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.