Existe-t-il un opérateur à coalescence nulle (Elvis) ou un opérateur de navigation sécurisée en javascript?


210

Je vais vous expliquer par exemple:

Opérateur Elvis (?:)

L '"opérateur Elvis" est un raccourci de l'opérateur ternaire de Java. Un exemple où cela est pratique consiste à renvoyer une valeur «sensible par défaut» si une expression se résout à faux ou null. Un exemple simple pourrait ressembler à ceci:

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

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

Opérateur de navigation sécurisée (?.)

L'opérateur de navigation sécurisée est utilisé pour éviter une exception NullPointerException. En règle générale, lorsque vous avez une référence à un objet, vous devrez peut-être vérifier qu'il n'est pas nul avant d'accéder aux méthodes ou aux propriétés de l'objet. Pour éviter cela, l'opérateur de navigation sécurisée retournera simplement null au lieu de lever une exception, comme ceci:

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'opérateur Elvis existe en C # - mais il s'appelle l'opérateur coalescent nul (beaucoup moins excitant) :-)
Cameron

Si vous voulez une syntaxe alternative, vous pouvez jeter un œil à cofeescript
Lime

Cette question est en quelque sorte un gâchis ... elle mélange 3 opérateurs différents? : (opérateur de ternerie, précisé dans la question, éventuellement une faute de frappe), ?? (coalescence nulle, qui existe en JavaScript) et?. (Elvis) qui n'existe PAS en JavaScript. Les réponses ne clarifient pas très bien cette distinction.
JoelFan

2
@JoelFan pouvez-vous fournir un lien vers la documentation concernant la bonne coalescence nulle ( ??) en javascript? Tout ce que je trouve jusqu'à présent suggère que JS a seulement une fusion (falsey) (utilisation ||).
Charles Wood

1
Eh bien, je ne voulais pas dire que JS avait littéralement ?? mais qu'il avait une fusion nulle ... mais même là, je me trompais un peu. Cela étant dit, j'ai vu BEAUCOUP de code JS qui utilise || comme un coalesce nul, malgré les pièges de
Falsey

Réponses:


139

Vous pouvez utiliser l'opérateur logique «OU» à la place de l'opérateur Elvis:

Par exemple displayname = user.name || "Anonymous" .

Mais Javascript n'a actuellement pas les autres fonctionnalités. Je recommanderais de regarder CoffeeScript si vous voulez une syntaxe alternative. Il a une sténographie similaire à ce que vous recherchez.

Par exemple, l'opérateur existentiel

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

Raccourcis de fonction

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

Appel de fonction sexy

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

Il existe également des commentaires et des classes multilignes. Évidemment, vous devez le compiler en javascript ou l'insérer dans la page, <script type='text/coffeescript>'mais cela ajoute beaucoup de fonctionnalités :). L'utilisation <script type='text/coffeescript'>n'est vraiment destinée qu'au développement et non à la production.


14
logique ou n'est pas tout à fait la chose nécessaire dans la plupart des cas, car vous voudrez peut-être choisir l'opérande droit uniquement si la gauche n'est pas définie, mais pas lorsqu'elle est définie et faussée.
user2451227

Est-ce mon erreur, ou c'est vraiment le cas <script type='coffee/script>'?
JCCM

2
La page d'accueil de CoffeeScript utilise <script type="text/coffeescript">.
Elias Zamaria

19
Bien que cela réponde à la question, il s'agit presque entièrement de coffeescript plutôt que de javascript, et concerne plus de la moitié de la description des avantages de coffeescript sans rapport avec le PO. Je suggère de le résumer à ce qui est pertinent à la question, aussi merveilleux que les autres avantages de coffeescript.
jinglesthula

4
Vais-je des bananes? Certes, l'objection de user2451227 (actuellement avec 4 votes) n'est pas valide car l'opérande moyen du ternaire (c'est-à-dire l'opérande droit avec l'opérateur Elvis) ne serait pas non plus choisi si l'expression / l'opérande gauche était définie et faussée. Dans les deux cas, vous devez alors partir x === undefined.
mike rodent

115

Je pense que ce qui suit est équivalent à l'opérateur de navigation sécurisée, bien qu'un peu plus long:

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

streetNamesera alors soit la valeur de user.address.streetou undefined.

Si vous voulez qu'il par défaut soit différent, vous pouvez le combiner avec le raccourci ci-dessus ou donner:

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

7
plus un pour un excellent exemple de propagation nulle et de coalescence nulle!
Jay Wick

1
cela fonctionne sauf que vous ne saurez pas si vous en êtes nul ou non défini
Dave Cousineau

82

Javascript de l' opérateur logique OR est un court-circuit et peut remplacer votre opérateur « Elvis »:

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

Cependant, à ma connaissance, il n'y a pas d'équivalent à votre ?.opérateur.


13
+1, j'ai oublié qu'on ||pouvait l'utiliser de cette façon. Sachez que ce fusionneront non seulement lorsque l'expression est null, mais aussi quand il est indéfini, false, 0ou la chaîne vide.
Cameron

@Cameron, en effet, mais cela est mentionné dans la question et semble être l'intention du questionneur. ""ou 0peut être inattendu, cependant :)
Frédéric Hamidi

72

J'ai parfois trouvé l'idiome suivant utile:

a?.b?.c

peut être réécrit comme:

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

Cela profite du fait que l'obtention d'attributs inconnus sur un objet renvoie undefined, plutôt que de lever une exception comme il le fait sur nullou undefined, nous remplaçons donc null et undefined par un objet vide avant de naviguer.


14
Eh bien, c'est difficile à lire, mais c'est mieux que cette &&méthode verbeuse . +1.
hurlement du

1
C'est le seul véritable opérateur sûr en javascript en fait. L'opérateur logique «OU» mentionné ci-dessus est autre chose.
vasilakisfil

@Filippos pouvez-vous donner un exemple de comportement différent dans la méthode logique OU vs &&? Je ne peux pas penser à une différence
The Red Pea

Il permet également de naviguer dans une valeur anonyme sans l'assigner à une variable au préalable.
Matt Jenkins

1
Aimer! Vraiment utile si vous voulez obtenir la propriété d'un objet après une opération array.find () qui pourrait ne pas retourner de résultats
Shiraz

24

je pense que lodash _.get()peut aider ici, comme dans _.get(user, 'name'), et des tâches plus complexes comme_.get(o, 'a[0].b.c', 'default-value')


5
Mon principal problème avec cette méthode est le fait que puisque le nom des propriétés est une chaîne, vous ne pouvez plus utiliser les fonctionnalités de refactoring de votre IDE avec une confiance à 100%
RPDeshaies

21

Il existe actuellement un projet de spécification:

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

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

Pour l'instant, cependant, j'aime utiliser lodashget(object, path [,defaultValue]) ou dlvdelve(obj, keypath)

Mise à jour (au 23 décembre 2019):

le chaînage optionnel est passé à l'étape 4


Lodash rend la programmation en javascript plus agréable
geckos

2
le chaînage optionnel vient de passer à l' étape 4 , donc nous le verrons dans ES2020
Nick Parsons

1
@NickParsons Merci! J'ai mis à jour la réponse.
Jack Tuck

18

Mise à jour 2019

JavaScript a maintenant des équivalents à la fois pour l'opérateur Elvis et pour l'opérateur de navigation sécurisée.


Accès sécurisé aux propriétés

L' opérateur de chaînage facultatif ( ?.) est actuellement une proposition ECMAScript de stade 4 . Vous pouvez l' utiliser aujourd'hui avec Babel .

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

L' opérateur logique ET ( &&) est la «vieille» manière plus détaillée de gérer ce scénario.

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

Fournir un défaut

L' opérateur coalescent nul ( ??) est actuellement une proposition ECMAScript de stade 3 . Vous pouvez l' utiliser aujourd'hui avec Babel . Il vous permet de définir une valeur par défaut si le côté gauche de l'opérateur est une valeur nulle ( / ).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' opérateur logique OR ( ||) est une solution alternative avec un comportement légèrement différent . Il vous permet de définir une valeur par défaut si le côté gauche de l'opérateur est faux . Notez que le résultat myVariable3ci - dessous diffère de celui myVariable3ci-dessus.

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
Cette réponse a besoin de plus de votes positifs. Nullish Coalescing Operator est maintenant à l'étape 4.
Yerke

13

Pour les premiers, vous pouvez utiliser ||. L'opérateur Javascript "logique ou", plutôt que de simplement renvoyer des valeurs vraies et fausses prédéfinies, suit la règle de renvoyer son argument gauche s'il est vrai, et autrement d'évaluer et de renvoyer son argument droit. Lorsque vous êtes uniquement intéressé par la valeur de vérité, cela fonctionne de la même manière, mais cela signifie également que foo || bar || bazrenvoie le plus à gauche de foo, bar ou baz qui contient une vraie valeur .

Cependant, vous n'en trouverez pas qui puisse distinguer faux de null, et 0 et une chaîne vide sont de fausses valeurs, alors évitez d'utiliser la value || defaultconstruction où valuepeut légitimement être 0 ou "".


4
Bon travail en notant que cela peut entraîner un comportement inattendu lorsque l'opérande de gauche est une valeur de falsey non nulle.
Shog9

11

Oui il y a! 🍾

Le chaînage facultatif est à l'étape 4 et cela vous permet d'utiliser la user?.address?.streetformule.

Si vous ne pouvez pas attendre la sortie, installez @babel/plugin-proposal-optional-chaininget vous pouvez l'utiliser. Voici mes paramètres qui fonctionnent pour moi, ou lisez simplement l'article de 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
Il a demandé s'il y en avait un, pas si vous pouviez en ajouter un. Je pense que ce n'est pas super utile étant donné que ce n'est pas ce qui a été demandé.
DeanMWake

2
Il a atteint l'étape 3 du processus de normalisation ECMAScript. es2020 bab - babeljs.io/docs/en/babel-plugin-proposal-optional-chaining
wedi

Je pense que cette réponse est trompeuse telle quelle.
Leonardo Raele

1
Cette réponse n'est pas tout à fait correcte! Le chaînage facultatif est encore à l'étape 3 et ES2020 n'a pas encore été publié ni même finalisé. Au moins, vous avez mentionné comment on peut l'utiliser sans avoir à attendre sa sortie.
Maxie Berkmann

@gazdagergo Pas de problème :).
Maxie Berkmann

6

Voici un simple équivalent d'opérateur 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

MISE À JOUR SEP 2019

Oui, JS prend désormais cela en charge. Le chaînage optionnel arrive bientôt sur v8 en savoir plus


Pas exactement pareil. OP est sur la coalescence nulle, mais belle réponse néanmoins.
Maxie Berkmann

4

Ceci est plus communément appelé opérateur à coalescence nulle. Javascript n'en a pas.


3
vrai au sens strict, mais comme d'autres réponses l'ont noté, l'opérateur OU logique de JavaScript peut se comporter comme une sorte de faux opérateur de fusion, vous permettant d'obtenir la même brièveté dans de nombreuses situations.
Shog9

1
Ce n'est pas un opérateur à coalescence nulle. La coalescence nulle ne fonctionne que sur une seule valeur, et non sur une chaîne d'appels de propriété / fonction. Vous pouvez déjà effectuer une fusion nulle avec l'opérateur OU logique en JavaScript.

Non, vous pouvez faire une fausse fusion avec le OU logique en JavaScript.
andresp

3

Vous pouvez obtenir à peu près le même effet en disant:

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

2

J'ai une solution pour cela, adaptez-la à vos propres besoins, un extrait de l'une de mes bibliothèques:

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

fonctionne comme un charme. Profitez du moins de douleur!


Semble prometteur, pouvez-vous soumettre s'il vous plaît la source complète? l'avez-vous partout public? (par exemple GitHub)
Eran Medan

1
Je vais créer un petit extrait du code dans lequel je l'utilise et le publierai sur GitHub dans une semaine environ.
balazstth

2

Vous pouvez rouler le vôtre:

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

Et utilisez-le comme ceci:

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

* le résultat n'est pas défini si l'un des éléments a, b, c ou d n'est pas défini.


1

J'ai lu cet article ( https://www.beyondjava.net/elvis-operator-aka-safe-navigation-javascript-typescript ) et modifié la solution à l'aide de proxys.

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

Vous l'appelez comme ceci:

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

Le résultat sera indéfini pour une expression qui rencontre null ou indéfini le long de son chemin. Vous pouvez vous déchaîner et modifier le prototype d'objet!

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

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


1

Cela a été un problème pour moi pendant longtemps. J'ai dû trouver une solution qui peut être facilement migrée une fois que nous obtenons l'opérateur Elvis ou quelque chose.

C'est ce que j'utilise; fonctionne pour les tableaux et les objets

mettre cela dans le fichier tools.js ou quelque chose

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

exemple d'utilisation:

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

Eh bien, je sais que cela rend le code un peu illisible, mais c'est une solution simple et fonctionne très bien. J'espère que ça aide quelqu'un :)


0

C'était une solution intéressante pour l'opérateur de navigation en toute sécurité utilisant un 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

J'ai créé un package qui rend cela beaucoup plus facile à utiliser.

NPM jsdig Github jsdig

Vous pouvez gérer des choses simples comme et objet:

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

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

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

ou un peu plus compliqué:

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

-6

Personnellement j'utilise

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

et par exemple obtenir en toute sécurité:

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

2
Tout d'abord, vous ne devriez vraiment pas utiliser eval . Deuxièmement, cela ne fonctionne même pas: e({a:{b:{c:{d:'test'}}}}, 'a.b.c.d')revient null.
Pylinux

@Pylinux essentiellement ce qui fonctionnerait est e = eval, var a = eval('obj.a.b.c.d'). evalne prend même pas un deuxième paramètre ... developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
Dorian
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.