Gibt es einen Null-Koaleszenz-Operator (Elvis) oder einen sicheren Navigationsoperator in Javascript?

209

Ich erkläre anhand eines Beispiels:

Elvis Operator (? :)

Der "Elvis-Operator" ist eine Abkürzung des ternären Operators von Java. Ein Beispiel dafür ist die Rückgabe eines "vernünftigen Standardwerts", wenn ein Ausdruck in "false" oder "null" aufgelöst wird. Ein einfaches Beispiel könnte folgendermaßen aussehen:

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

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

Sicherer Navigationsbetreiber (?.)

Der Operator "Sichere Navigation" wird verwendet, um eine NullPointerException zu vermeiden. Wenn Sie einen Verweis auf ein Objekt haben, müssen Sie möglicherweise überprüfen, ob dieser nicht null ist, bevor Sie auf Methoden oder Eigenschaften des Objekts zugreifen. Um dies zu vermeiden, gibt der Operator für die sichere Navigation einfach null zurück, anstatt eine Ausnahme auszulösen:

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
quelle
9
Der 'Elvis-Operator' existiert in C # - aber er heißt Null-Koaleszenz-Operator (viel weniger aufregend) :-)
Cameron
Wenn Sie eine alternative Syntax wünschen, können Sie sich Cofeescript
Lime
Diese Frage ist eine Art Chaos ... es vermischt 3 verschiedene Operatoren? : (Ternery Operator, in der Frage formuliert, möglicherweise ein Tippfehler), ?? (Null-Koaleszenz, die in JavaScript vorhanden ist) und? (Elvis), das in JavaScript NICHT vorhanden ist. Die Antworten verdeutlichen diese Unterscheidung nicht sehr gut.
JoelFan
2
@JoelFan Können Sie einen Link zur Dokumentation zur korrekten Null-Koaleszenz ( ??) in Javascript bereitstellen ? Alles, was ich bisher finde, deutet darauf hin, dass JS nur "Falsey" hat, das verschmilzt (mit ||).
Charles Wood
1
Nun, ich wollte nicht sagen, dass JS buchstäblich hatte? aber dass es keine Verschmelzung hatte ... aber selbst dort habe ich mich irgendwie geirrt. Davon abgesehen habe ich eine Menge JS-Code gesehen, der || verwendet als Nullverschmelzung, trotz der falsey Fallstricke
JoelFan

Antworten:

138

Sie können den logischen ODER-Operator anstelle des Elvis-Operators verwenden:

Zum Beispiel displayname = user.name || "Anonymous".

Derzeit verfügt Javascript jedoch nicht über die anderen Funktionen. Ich würde empfehlen, sich CoffeeScript anzusehen, wenn Sie eine alternative Syntax wünschen. Es hat eine Abkürzung, die dem ähnelt, wonach Sie suchen.

Zum Beispiel Der Existenzoperator

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

Funktionsverknüpfungen

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

Sexy Funktionsaufruf

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

Es gibt auch mehrzeilige Kommentare und Klassen. Natürlich müssen Sie dies zu Javascript kompilieren oder in die Seite einfügen, <script type='text/coffeescript>'aber es fügt eine Menge Funktionen hinzu :). Die Verwendung <script type='text/coffeescript'>ist eigentlich nur für die Entwicklung und nicht für die Produktion gedacht.

Limette
quelle
14
logisch oder ist in den meisten Fällen nicht ganz das, was benötigt wird, da Sie möglicherweise möchten, dass der rechte Operand nur ausgewählt wird, wenn der linke undefiniert ist, aber nicht, wenn er definiert und falsch ist.
user2451227
Ist es mein Fehler oder wirklich <script type='coffee/script>'?
JCCM
2
Die CoffeeScript-Homepage verwendet <script type="text/coffeescript">.
Elias Zamaria
19
Während dies die Frage beantwortet, geht es fast ausschließlich um Coffeescript und nicht um Javascript, und mehr als die Hälfte geht es um die Beschreibung der Vorteile von Coffeescript, die nicht mit dem OP zusammenhängen. Ich würde vorschlagen, es auf das zu reduzieren, was für die Frage relevant ist, so wunderbar die anderen Vorteile von coffeescript sind.
Jinglesthula
4
Gehe ich bananen Sicherlich ist der Einwand von user2451227 (derzeit mit 4 Stimmen) ungültig, da der mittlere Operand des Ternärs (dh der rechte Operand mit dem Elvis-Operator) ebenfalls nicht ausgewählt würde, wenn der Ausdruck / linker Operand definiert und falsch ist. In beiden Fällen muss man dann gehen x === undefined.
Mike Nagetier
114

Ich denke, das Folgende entspricht dem sicheren Navigationsoperator, wenn auch etwas länger:

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

streetNamewird dann entweder der Wert von user.address.streetoder sein undefined.

Wenn Sie möchten, dass standardmäßig etwas anderes verwendet wird, können Sie es mit der obigen Verknüpfung kombinieren oder Folgendes angeben:

var streetName = (user && user.address && user.address.street) || "Unknown Street";
Samjudson
quelle
7
plus eins für ein großartiges Beispiel für Nullausbreitung und Nullkoaleszenz!
Jay Wick
1
Dies funktioniert mit der Ausnahme, dass Sie nicht wissen, ob Sie null oder undefiniert davon bekommen
Dave Cousineau
82

Der logische ODER-Operator von Javascript ist kurzgeschlossen und kann Ihren "Elvis" -Operator ersetzen:

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

Meines Wissens gibt es jedoch kein Äquivalent zu Ihrem ?.Betreiber.

Frédéric Hamidi
quelle
13
+1, ich habe vergessen, ||könnte so verwendet werden. Seien Sie sich bewusst , dass dies nicht nur zusammenwachsen, wenn der Ausdruck ist null, sondern auch , wenn es nicht definiert, false, 0, oder die leere Zeichenkette.
Cameron
@Cameron zwar, aber das wird in der Frage erwähnt und scheint die Absicht des Fragestellers zu sein. ""oder 0könnte aber unerwartet sein :)
Frédéric Hamidi
72

Ich habe gelegentlich die folgende Redewendung nützlich gefunden:

a?.b?.c

kann umgeschrieben werden als:

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

Dies nutzt die Tatsache aus, dass das Abrufen unbekannter Attribute für ein Objekt undefiniert zurückgibt, anstatt wie bei nulloder eine Ausnahme auszulösen. Daher undefinedersetzen wir null und undefiniert durch ein leeres Objekt, bevor wir navigieren.

James_pic
quelle
14
Nun, es ist schwer zu lesen, aber es ist besser als diese ausführliche &&Methode. +1.
Schrei
1
Das ist der einzige wirklich sichere Operator in Javascript. Der oben erwähnte logische ODER-Operator ist etwas anderes.
Vasilakisfil
@Filippos Kannst du ein Beispiel für ein unterschiedliches Verhalten in der logischen ODER-vs-Methode geben? Ich kann mir keinen Unterschied
vorstellen
Außerdem können Sie durch einen anonymen Wert navigieren, ohne ihn zuvor einer Variablen zuzuweisen.
Matt Jenkins
1
Liebe es! Wirklich nützlich, wenn Sie die Eigenschaft eines Objekts nach einer array.find () -Operation erhalten möchten, die möglicherweise keine Ergebnisse zurückgibt
Shiraz
24

Ich denke, Lodash _.get()kann hier wie in _.get(user, 'name')und bei komplexeren Aufgaben wie helfen_.get(o, 'a[0].b.c', 'default-value')

tony_k
quelle
5
Mein Hauptproblem bei dieser Methode ist die Tatsache, dass Sie, da der Name der Eigenschaften Zeichenfolge ist, keine Refactoring-Funktionen Ihrer IDE mehr mit 100% Vertrauen verwenden können
RPDeshaies
21

Derzeit gibt es einen Spezifikationsentwurf:

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

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

Im Moment benutzeget(object, path [,defaultValue]) ich jedoch gerne lodash oder dlvdelve(obj, keypath)

Update (Stand 23. Dezember 2019):

Die optionale Verkettung wurde auf Stufe 4 verschoben

Jack Tuck
quelle
Lodash macht das Programmieren in Javascript schmackhafter
Geckos
2
Die optionale Verkettung wurde erst kürzlich auf Stufe 4 verschoben , sodass wir sie in ES2020 sehen können
Nick Parsons
1
@ NickParsons Danke! Habe die Antwort aktualisiert.
Jack Tuck
18

Update 2019

JavaScript hat jetzt Entsprechungen sowohl für den Elvis Operator als auch für den Safe Navigation Operator.


Sicherer Zugang zu Eigentum

Der optionale Verkettungsoperator ( ?.) ist derzeit ein ECMAScript- Vorschlag der Stufe 4 . Sie können es heute mit Babel verwenden .

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

Der logische AND-Operator ( &&) ist die "alte", ausführlichere Methode, um mit diesem Szenario umzugehen.

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

Standard angeben

Der nullish coalescing operator ( ??) ist derzeit ein ECMAScript- Vorschlag der Stufe 3 . Sie können es heute mit Babel verwenden . Sie können einen Standardwert festlegen, wenn die linke Seite des Operators ein Nullwert ( / ) ist.nullundefined

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

Der logische ODER-Operator ( ||) ist eine alternative Lösung mit leicht unterschiedlichem Verhalten . Sie können einen Standardwert festlegen, wenn die linke Seite des Operators falsch ist . Beachten Sie, dass sich das Ergebnis von myVariable3unten von myVariable3oben unterscheidet.

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
quelle
1
Diese Antwort braucht mehr Upvotes. Der Nullish Coalescing Operator befindet sich jetzt in Phase 4.
Yerke
13

Für die erstere können Sie verwenden ||. Der Javascript-Operator "logisch oder" gibt nicht nur vordefinierte wahre und falsche Werte zurück, sondern folgt auch der Regel, dass das linke Argument zurückgegeben wird, wenn es wahr ist, und ansonsten das rechte Argument bewertet und zurückgegeben wird. Wenn ich ist nur in dem Wahrheitswert interessiert funktioniert es die gleiche, aber es bedeutet auch , dass foo || bar || bazwieder den ganz links von foo, bar, oder baz , die einen wahren Wert enthält .

Sie werden jedoch keine finden, die false von null unterscheiden kann, und 0 und leere Zeichenfolge sind falsche Werte. Vermeiden Sie daher die Verwendung des value || defaultKonstrukts, bei dem zu valueRecht 0 oder 0 sein kann "".

Hobbs
quelle
4
Gute Arbeit, die darauf hinweist, dass dies zu unerwartetem Verhalten führen kann, wenn der linke Operand ein Falsey-Wert ungleich Null ist.
Shog9
11

Ja da ist! 🍾

Die optionale Verkettung befindet sich in Stufe 4, sodass Sie die user?.address?.streetFormel verwenden können.

Wenn Sie die Veröffentlichung nicht abwarten können, installieren @babel/plugin-proposal-optional-chainingSie sie und Sie können sie verwenden. Hier sind meine Einstellungen, die für mich funktionieren, oder lesen Sie einfach Nimmos Artikel .

// 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
quelle
4
Er fragte, ob es einen gäbe, nicht, ob Sie einen hinzufügen könnten. Ich denke, das ist nicht besonders nützlich, wenn man bedenkt, dass es nicht das ist, was gefragt wurde.
DeanMWake
2
Es erreichte Stufe 3 des ECMAScript-Standardisierungsprozesses. es2020 🚀 - babeljs.io/docs/en/babel-plugin-proposal-optional-chaining
wedi
Ich denke, diese Antwort ist so wie sie ist irreführend.
Leonardo Raele
1
Diese Antwort ist nicht ganz richtig! Die optionale Verkettung befindet sich noch in Stufe 3 und ES2020 wurde noch nicht veröffentlicht oder sogar abgeschlossen. Zumindest haben Sie erwähnt, wie man es verwenden kann, ohne auf die Veröffentlichung warten zu müssen.
Maxie Berkmann
@ Gazdagergo Kein Problem :).
Maxie Berkmann
6

Hier ist ein einfaches Äquivalent zum Elvis-Operator:

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
quelle
5

UPDATE SEP 2019

Ja, JS unterstützt dies jetzt. Optionale Verkettung kommt bald zu v8. Lesen Sie mehr

Taher
quelle
Nicht ganz das Gleiche. OP dreht sich um Null-Koaleszenz, aber trotzdem eine nette Antwort.
Maxie Berkmann
4

Dies ist allgemein als Null-Koaleszenz-Operator bekannt. Javascript hat keine.

CassOnMars
quelle
3
wahr im engeren Sinne, aber wie andere Antworten angemerkt haben, kann sich der logische ODER-Operator von JavaScript wie eine Art falsch verschmelzender Operator verhalten , sodass Sie in vielen Situationen die gleiche Kürze erreichen können.
Shog9
1
Dies ist kein Null-Koaleszenz-Operator. Null-Coalescing funktioniert nur für einen einzelnen Wert, nicht für eine Kette von Eigenschaftszugriffs- / Funktionsaufrufen. Sie können mit dem logischen ODER-Operator in JavaScript bereits eine Null-Koaleszenz durchführen.
Nein, Sie können mit dem logischen ODER in JavaScript eine falsche Verschmelzung durchführen.
andresp
3

Sie können ungefähr den gleichen Effekt erzielen, indem Sie sagen:

var displayName = user.name || "Anonymous";
Justin Ethier
quelle
2

Ich habe eine Lösung dafür, passe sie an deine eigenen Bedürfnisse an, einen Auszug aus einer meiner Bibliotheken:

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

klappt wunderbar. Genieße die weniger Schmerzen!

balazstth
quelle
Sieht vielversprechend aus, können Sie bitte die vollständige Quelle einreichen? Hast du es irgendwo öffentlich? (zB GitHub)
Eran Medan
1
Ich werde einen kleinen Auszug aus dem Code erstellen, in dem ich ihn verwende, und ihn in ungefähr einer Woche auf GitHub veröffentlichen.
Balazstth
2

Sie könnten Ihre eigenen rollen:

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

Und benutze es so:

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

* Ergebnis ist undefiniert, wenn eines von a, b, c oder d undefiniert ist.

Pylinux
quelle
1

Ich habe diesen Artikel gelesen ( https://www.beyondjava.net/elvis-operator-aka-safe-navigation-javascript-typescript ) und die Lösung mithilfe von Proxies geändert.

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

Sie nennen es so:

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

Das Ergebnis ist für einen Ausdruck undefiniert, der auf seinem Pfad auf null oder undefiniert stößt. Sie könnten wild werden und den Objektprototyp modifizieren!

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

example.getSafe((x) => x.foo.woo);
Sam
quelle
1

Das war lange Zeit ein Problem für mich. Ich musste eine Lösung finden, die leicht migriert werden kann, sobald wir den Elvis-Operator oder etwas anderes haben.

Das benutze ich; funktioniert sowohl für Arrays als auch für Objekte

füge dies in die Datei tools.js oder so ein

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

Anwendungsbeispiel:

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

Nun, ich weiß, es macht den Code ein bisschen unlesbar, aber es ist eine einfache Einzeiler-Lösung und funktioniert großartig. Ich hoffe es hilft jemandem :)

Neut
quelle
0

Dies war eine interessante Lösung für den sicheren Navigationsbetreiber, der ein Mixin verwendete.

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();
Dean Hiller
quelle
0

Ich habe ein Paket erstellt, das die Verwendung erheblich vereinfacht.

NPM jsdig Github jsdig

Sie können mit einfachen Dingen wie und Objekt umgehen:

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

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

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

oder etwas komplizierter:

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

Persönlich benutze ich

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

und zum Beispiel sicher bekommen:

var a = e(obj,'e.x.y.z.searchedField');
kudłaty
quelle
2
Zuerst sollten Sie eval wirklich nicht verwenden . Zweitens funktioniert das nicht einmal: e({a:{b:{c:{d:'test'}}}}, 'a.b.c.d')kehrt zurück null.
Pylinux
@Pylinux im Grunde, was funktionieren würde, ist e = eval, var a = eval('obj.a.b.c.d'). evalnimmt nicht einmal einen zweiten Parameter ... developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
Dorian