web-dev-qa-db-fra.com

Existe-t-il un opérateur Elvis (null-coalescing) ou un opérateur de navigation sûr en javascript?

Je vais expliquer par exemple:

Elvis Opérateur (?:)

"L'opérateur Elvis" est un raccourcissement de l'opérateur ternaire de Java. Un exemple de où cela est pratique est pour renvoyer une valeur 'par défaut raisonnable' si une expression est fausse ou nul. Un exemple simple pourrait ressembler à ce:

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 Safe Navigation est utilisé pour éviter une exception NullPointerException . Généralement, lorsque vous faites référence à un objet que vous pourriez avoir besoin de vérifier qu'il n'est pas nul avant d'accéder à méthodes ou propriétés de l'objet . Pour éviter cela, la navigation en toute sécurité l'opérateur retournera simplement null au lieu de lancer une exception, comme alors:

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
146
tiagomac

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

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

Mais Javascript n’a actuellement aucune autre fonctionnalité. Je vous recommande de regarder CoffeeScript si vous voulez une syntaxe alternative. Il possède des raccourcis similaires à ceux 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 y a aussi des commentaires et des classes multilignes. Évidemment, vous devez le compiler en javascript ou l'insérer dans la page en tant que <script type='text/coffeescript>' mais cela ajoute beaucoup de fonctionnalités :) L'utilisation de <script type='text/coffeescript'> n'est en réalité que destinée au développement et non à la production.

110
William A

L'opérateur logique OR de Javascript est court-circuitant 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 ?..

75
Frédéric Hamidi

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;

streetName sera alors soit la valeur de user.address.street, soit undefined.

Si vous voulez utiliser autre chose par défaut, vous pouvez combiner le raccourci ci-dessus ou donner:

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

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

a?.b.?c

peut être réécrit comme:

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

Cela tire parti du fait que l'obtention d'attributs inconnus sur un objet renvoie non défini, plutôt que de lever une exception comme sur null ou undefined. Nous remplaçons donc null et non défini par un objet vide avant la navigation.

43
James_pic

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

21
tony_k

Pour les premiers, vous pouvez utiliser ||. L'opérateur Javascript "logique ou", plutôt que de simplement renvoyer des valeurs vraies et fausses, conserve la règle de retourner son argument de gauche s'il est vrai, sinon d'évaluer et de retourner son argument de droite. 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 || baz renvoie le plus à gauche de foo, bar ou baz qui contient une valeur vraie .

Vous n'en trouverez pas cependant qui puisse distinguer false de null, null, et 0 et chaîne vide sont des valeurs fausses, évitez donc d'utiliser la construction value || defaultvalue peut légitimement être 0 ou "".

10
hobbs

Pas encore. Peut-être bientôt. Il existe actuellement un brouillon de spécification:

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

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

Pour l'instant, cependant, j'aime bien utiliser lodash get(object, path, [defaultValue]) ou dlv delve(obj, keypath)

9
Jack Tuck

Voici un équivalent simple 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
4
cdmckay

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

4
doctorless

J'ai une solution pour cela, adaptez-la à vos 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 de moins de douleur!

3
balazstth

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

* résultat est indéfini si l'un des a, b, c ou d est indéfini.

2
Pylinux

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

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

Voici ma fonction "elvis". Passez l'objet racine et la chaîne sous forme de chaîne. Il retourne toujours le premier élément 'non défini' de la chaîne. Fonctionne avec les objets, les tableaux, les méthodes et les primitives.

elvis(myObject, 'categories.shirts[0].getPrice().currency');

Exemple de travail:

const elvis = (obj, keychain) => {
  const handleArray = (parent, key) => {
    if (key.indexOf('[') > -1) {
      const arrayName = key.split('[')[0];
      const arrayIndex = +key.split('[')[1].slice(0, -1);
        return parent[arrayName] && parent[arrayName][arrayIndex];
    }
    if (key.indexOf('(') > -1) {
      const methodName = key.split('(')[0];
      return parent[methodName] && parent[methodName]();
    }      
    return parent[key];
  }

  const keys = keychain.split('.');
  let base = obj;

  for (let i = 0; i < keys.length; i += 1) {
    base = handleArray(base, keys[i]);
    if (typeof base === 'undefined') return base;
  }
  return base;
}

//--------

const myObject = {
  categories: {
    getFoo: () => 'foo',
    shirts: [
      { color: 'red' },
      { color: 'blue' }
    ]
  }
}

console.log(elvis(myObject, 'categories.shirts[0].color'));
console.log(elvis(myObject, 'categories.getFoo()'));
console.log(elvis(myObject, 'categories.getBar()'));
console.log(elvis(myObject, 'categories.shirts[0].length'));
console.log(elvis(myObject, 'categories.pans[2].color'));

0
gazdagergo

Très tardivement, il existe une proposition [1] d’enchaînement optionnel actuellement à la phase 2, avec un plugin babel [2] disponible. Ce n'est pas actuellement dans aucun navigateur que je sache.

  1. https://github.com/tc39/proposal-optional-chaining
  2. https://www.npmjs.com/package/@babel/plugin-proposal-optional-chaining
0
Tracker1

C’était une solution intéressante pour l’opérateur de navigation sûre qui utilisait du 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
Dean Hiller

J'ai lu cet article ( https://www.beyondjava.net/elvis-operator-aka-safe-navigation-javascript-TypeScript ) et modifié la solution à l'aide de 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;
}

Vous l'appelez comme ça:

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

Le résultat sera indéfini pour une expression rencontrant null ou indéfini le long de son chemin. Vous pouvez aller wild et modifier le prototype d'objet!

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

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

Cela a été un problème pour moi pendant une longue période. Je devais trouver une solution qui puisse être facilement migrée une fois que nous obtenons un opérateur Elvis ou quelque chose du genre.

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 à un seul support qui fonctionne très bien. J'espère que ça aide quelqu'un :)

0
Neut