Esiste un operatore a coalescenza nulla (Elvis) o un operatore di navigazione sicura in JavaScript?


210

Spiegherò con l'esempio:

Elvis Operator (?:)

L '"operatore Elvis" è un accorciamento dell'operatore ternario di Java. Un'istanza in cui ciò è utile è per restituire un valore "ragionevole sensato" se un'espressione si risolve in falso o nullo. Un semplice esempio potrebbe apparire così:

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

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

Operatore di navigazione sicura (?.)

L'operatore Navigazione sicura viene utilizzato per evitare una NullPointerException. In genere, quando si ha un riferimento a un oggetto, potrebbe essere necessario verificare che non sia nullo prima di accedere ai metodi o alle proprietà dell'oggetto. Per evitare ciò, l'operatore di navigazione sicura restituirà semplicemente null invece di generare un'eccezione, in questo modo:

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
L '"Operatore Elvis" esiste in C # - ma si chiama operatore a coalescenza nulla (molto meno eccitante) :-)
Cameron

Se desideri una sintassi alternativa, puoi dare un'occhiata a cofeescript
Lime

Questa domanda è una specie di pasticcio ... sta mescolando 3 operatori diversi? : (operatore ternery, chiarito nella domanda, forse un errore di battitura), ?? (null coalescing, che esiste in JavaScript) e?. (Elvis) che NON esiste in JavaScript. Le risposte non chiariscono molto bene questa distinzione.
JoelFan

2
@JoelFan puoi fornire un link alla documentazione relativa alla corretta null-coalescenza ( ??) in javascript? Tutto ciò che sto trovando finora suggerisce che JS abbia solo una "falsa" coalescenza (usando ||).
Charles Wood,

1
Bene, non intendevo dire che JS aveva letteralmente ?? ma che aveva una coalescenza nulla ... ma anche lì mi sbagliavo un po '. Detto questo, ho visto MOLTO codice JS che utilizza || come una coalizione nulla, nonostante le false insidie
JoelFan

Risposte:


139

È possibile utilizzare l'operatore logico 'OR' al posto dell'operatore Elvis:

Per esempio displayname = user.name || "Anonymous".

Ma Javascript attualmente non ha le altre funzionalità. Consiglierei di guardare CoffeeScript se desideri una sintassi alternativa. Ha una scorciatoia che è simile a quello che stai cercando.

Ad esempio The Existential Operator

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

Scorciatoie di funzione

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

Chiamata di funzione sexy

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

Ci sono anche commenti e classi multilinea. Ovviamente devi compilarlo in javascript o inserirlo nella pagina come <script type='text/coffeescript>'ma aggiunge molte funzionalità :). L'uso <script type='text/coffeescript'>è davvero destinato esclusivamente allo sviluppo e non alla produzione.


14
logico o non è proprio la cosa necessaria nella maggior parte dei casi, poiché potresti voler scegliere l'operando destro solo se la sinistra non è definita ma non quando è definita e falsa.
user2451227

È un mio errore o è davvero <script type='coffee/script>'?
JCCM

2
Viene utilizzata la home page di CoffeeScript <script type="text/coffeescript">.
Elias Zamaria,

19
Mentre questo risponde alla domanda, si tratta quasi interamente di coffeescript piuttosto che di javascript, ed è più della metà della descrizione dei vantaggi di coffeescript non correlati all'OP. Suggerirei di ridurlo a ciò che è rilevante per la domanda, meraviglioso come gli altri vantaggi di coffeescript.
jinglesthula,

4
Sto andando banane? Sicuramente l'obiezione dell'utente2451227 (attualmente con 4 voti) non è valida perché l'operando di mezzo del ternario (cioè l'operando di destra con l'operatore Elvis) non verrebbe ugualmente raccolto se l'espressione / operando di sinistra è definito e falsa. In entrambi i casi devi andare x === undefined.
mike rodent,

115

Penso che quanto segue sia equivalente all'operatore di navigazione sicura, anche se un po 'più a lungo:

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

streetNamesarà quindi il valore di user.address.streeto undefined.

Se vuoi che sia impostato diversamente su qualcos'altro, puoi combinarlo con il collegamento sopra o dare:

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

7
più uno per un grande esempio di propagazione nulla e di coalescenza nulla!
Jay Wick,

1
questo funziona, tranne per il fatto che non si saprà se si ottiene nulla o indefinito
Dave Cousineau,

82

L' operatore logico OR di Javascript è in corto circuito e può sostituire il tuo operatore "Elvis":

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

Tuttavia, per quanto ne sappia, non esiste un equivalente per il tuo ?.operatore.


13
+1, ho dimenticato che ||potrebbe essere usato in questo modo. Essere consapevoli del fatto che questo si fondono non solo quando l'espressione è null, ma anche quando è undefined, false, 0, o la stringa vuota.
Cameron,

@Cameron, in effetti, ma questo è menzionato nella domanda e sembra essere l'intento dell'interrogatore. ""o 0potrebbe essere inaspettato, però :)
Frédéric Hamidi

72

Occasionalmente ho trovato utile il seguente linguaggio:

a?.b?.c

può essere riscritto come:

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

Questo sfrutta il fatto che ottenere attributi sconosciuti su un oggetto restituisce un valore indefinito, piuttosto che generare un'eccezione come fa su nullo undefined, quindi sostituiamo null e undefined con un oggetto vuoto prima di navigare.


14
Bene, è difficile da leggere ma è meglio di quel &&metodo dettagliato . +1.
grido

1
Questo è l'unico vero operatore sicuro in JavaScript in realtà. L'operatore logico "OR" menzionato sopra è qualcos'altro.
vasilakisfil,

@Filippos puoi dare un esempio di comportamento diverso nel metodo OR logico vs &&? Non riesco a pensare a una differenza
The Red Pea,

Consente inoltre la navigazione di un valore anonimo senza prima assegnarlo a una variabile.
Matt Jenkins,

1
Lo adoro! Molto utile se si desidera ottenere la proprietà di un oggetto dopo un'operazione array.find () che potrebbe non restituire alcun risultato
Shiraz

24

penso che lodash _.get()può aiutare qui, come in _.get(user, 'name'), e compiti più complessi come_.get(o, 'a[0].b.c', 'default-value')


5
Il mio problema principale con questo metodo è il fatto che poiché il nome delle proprietà è stringa, non puoi più utilizzare le funzionalità di refactoring del tuo IDE con un trust del 100%
RPDeshaies

21

Al momento esiste una bozza di specifiche:

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

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

Per ora, però, mi piace usare lodashget(object, path [,defaultValue]) o dlvdelve(obj, keypath)

Aggiornamento (dal 23 dicembre 2019):

il concatenamento opzionale è passato alla fase 4


Lodash rende la programmazione in javascript più appetibile
gechi

2
il concatenamento opzionale è appena passato allo stadio 4 , quindi lo vedremo in ES2020
Nick Parsons il

1
@NickParsons Grazie! Ho aggiornato la risposta.
Jack Tuck,

18

Aggiornamento 2019

JavaScript ora ha equivalenti sia per l'operatore Elvis che per l'operatore di navigazione sicura.


Accesso sicuro alle proprietà

L' operatore di concatenamento opzionale ( ?.) è attualmente una proposta ECMAScript di fase 4 . Puoi usarlo oggi con Babel .

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

L' operatore AND logico ( &&) è il "vecchio" modo più dettagliato per gestire questo scenario.

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

Fornire un valore predefinito

L' operatore nullish coalescing ( ??) è attualmente una proposta ECMAScript di fase 3 . Puoi usarlo oggi con Babel . Consente di impostare un valore predefinito se il lato sinistro dell'operatore è un valore nullo ( / ).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';

L' operatore logico OR ( ||) è una soluzione alternativa con un comportamento leggermente diverso . Consente di impostare un valore predefinito se il lato sinistro dell'operatore è errato . Si noti che il risultato di myVariable3sotto differisce da myVariable3sopra.

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
Questa risposta richiede più voti. Nullish Coalescing Operator è ora nella fase 4.
Yerke

13

Per il primo, puoi usare ||. L'operatore "logico o" di Javascript, anziché semplicemente restituire valori reali e falsi predefiniti, segue la regola di restituire l'argomento sinistro se è vero, e altrimenti valutare e restituire l'argomento giusto. Quando sei interessato solo al valore della verità, funziona allo stesso modo, ma significa anche che foo || bar || bazrestituisce quello più a sinistra di foo, bar o baz che contiene un valore vero .

Tuttavia, non troverai uno che possa distinguere false da null e 0 e stringa vuota sono valori falsi, quindi evita di usare il value || defaultcostrutto dove valuepuò essere legittimamente 0 o "".


4
Buon lavoro osservando che ciò può comportare comportamenti imprevisti quando l'operando di sinistra è un valore di falso non nullo.
Shog9

11

Si C'è! 🍾

Il concatenamento opzionale è nella fase 4 e ciò consente di utilizzare la user?.address?.streetformula.

Se non puoi aspettare il rilascio, installa @babel/plugin-proposal-optional-chaininge puoi usarlo. Ecco le mie impostazioni che funzionano per me, o semplicemente leggi l'articolo di 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
Ha chiesto se ce n'era uno, non se fosse possibile aggiungerne uno. Penso che questo non sia molto utile considerando che non è quello che è stato chiesto.
DeanMWake,

2
Ha raggiunto la fase 3 del processo di standardizzazione ECMAScript. es2020 🚀 - babeljs.io/docs/en/babel-plugin-proposal-optional-chaining
wedi,

Penso che questa risposta sia fuorviante così com'è.
Leonardo Raele,

1
Questa risposta non è del tutto corretta! Il concatenamento opzionale è ancora nella fase 3 e ES2020 non è stato ancora rilasciato o finalizzato. Almeno hai menzionato come si può usare senza dover aspettare che venga rilasciato.
Maxie Berkmann

@gazdagergo Nessun problema :).
Maxie Berkmann,

6

Ecco un semplice equivalente operatore 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

AGGIORNAMENTO SETTEMBRE 2019

Sì, JS ora supporta questo. Il concatenamento opzionale arriverà presto su v8 per saperne di più


Non esattamente la stessa cosa. OP si occupa di coalescenza nulla, ma comunque bella risposta.
Maxie Berkmann,

4

Questo è più comunemente noto come operatore a coalescenza nulla. Javascript non ne ha uno.


3
vero in senso stretto, ma come hanno notato altre risposte, l'operatore logico OR di JavaScript può comportarsi come una sorta di operatore a falsa coalescenza, che consente di ottenere la stessa brevità in molte situazioni.
Shog9

1
Questo non è un operatore a coalescenza nulla. La coalescenza nulla funziona solo su un singolo valore, non su una catena di invocazioni di proprietà / accesso a proprietà. È già possibile eseguire la coalescenza nulla con l'operatore logico OR in JavaScript.

No, puoi eseguire la falsa coalescenza con l'OR logico in JavaScript.
andresp,

3

Puoi ottenere approssimativamente lo stesso effetto dicendo:

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

2

Ho una soluzione per questo, su misura per le tue esigenze, un estratto da una delle mie librerie:

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

funziona come un fascino. Goditi meno dolore!


Sembra promettente, puoi inviare per favore la fonte completa? ce l'hai ovunque pubblico? (es. GitHub)
Eran Medan,

1
Creerò un piccolo estratto dal codice in cui lo uso e lo pubblicherò su GitHub tra circa una settimana.
balazstth

2

Puoi creare il tuo:

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

E usalo in questo modo:

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

* il risultato non è definito se uno qualsiasi di a, b, c o d non è definito.


1

Ho letto questo articolo ( https://www.beyondjava.net/elvis-operator-aka-safe-navigation-javascript-typescript ) e modificato la soluzione usando Proxies.

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

Lo chiami così:

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

Il risultato sarà indefinito per un'espressione che incontra null o indefinita lungo il suo percorso. Potresti scatenarti e modificare il prototipo dell'Oggetto!

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

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


1

Questo è stato un problema per me per molto tempo. Ho dovuto trovare una soluzione che può essere facilmente migrata una volta ottenuto l'operatore Elvis o qualcosa del genere.

Questo è quello che uso; funziona per matrici e oggetti

mettilo nel file tools.js o qualcosa del genere

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

esempio di utilizzo:

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

bene so che rende il codice un po 'illeggibile, ma è una soluzione semplice da usare e funziona alla grande. Spero che aiuti qualcuno :)


0

Questa è stata una soluzione interessante per l'operatore di navigazione sicura usando un po 'di 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

Ho creato un pacchetto che lo rende molto più facile da usare.

NPM jsdig Github jsdig

Puoi gestire cose semplici come e oggetti:

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

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

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

o un po 'più complicato:

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

-6

Personalmente lo uso

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

e ad esempio sicuro ottenere:

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

2
Il primo di voi non dovrebbe usare Eval . In secondo luogo, questo non funziona nemmeno: e({a:{b:{c:{d:'test'}}}}, 'a.b.c.d')ritorna null.
Pylinux,

@Pylinux fondamentalmente ciò che avrebbe funzionato è e = eval, var a = eval('obj.a.b.c.d'). evalnon accetta nemmeno un secondo parametro ... developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
Dorian
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.