¿Hay un operador de fusión nula (Elvis) u operador de navegación segura en javascript?

210

Explicaré con el ejemplo:

Operador de Elvis (?:)

El "operador de Elvis" es una abreviatura del operador ternario de Java. Una instancia de donde esto es útil es para devolver un valor 'sensible por defecto' si una expresión se resuelve como falsa o nula. Un ejemplo simple podría verse así:

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

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

Operador de navegación segura (?.)

El operador de navegación segura se utiliza para evitar una excepción NullPointerException. Normalmente, cuando tiene una referencia a un objeto, es posible que deba verificar que no sea nulo antes de acceder a los métodos o propiedades del objeto. Para evitar esto, el operador de navegación segura simplemente devolverá nulo en lugar de lanzar una excepción, de esta manera:

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
fuente
9
El 'Operador de Elvis' existe en C #, pero se llama operador de fusión nulo (mucho menos emocionante) :-)
Cameron
Si desea una sintaxis alternativa, puede echar un vistazo a cofeescript
Lime el
Esta pregunta es un desastre ... ¿está mezclando 3 operadores diferentes? : (operador de ternery, enunciado en la pregunta, posiblemente un error tipográfico), ?? (fusión nula, que existe en JavaScript) y?. (Elvis) que NO existe en JavaScript. Las respuestas no aclaran muy bien esta distinción.
JoelFan
2
@JoelFan, ¿puede proporcionar un enlace a la documentación sobre la nulos-coalescencia adecuada ( ??) en javascript? Todo lo que he encontrado hasta ahora sugiere que JS solo tiene una fusión (uso ||) "falsey" .
Charles Wood
1
Bueno, no quise decir que JS literalmente tenía ?? pero que tenía una fusión nula ... pero incluso allí estaba un poco equivocado. Dicho esto, he visto MUCHO código JS que usa || como una fusión nula, a pesar de las trampas
falsey

Respuestas:

139

Puede usar el operador lógico 'OR' en lugar del operador de Elvis:

Por ejemplo displayname = user.name || "Anonymous".

Pero Javascript actualmente no tiene la otra funcionalidad. Recomiendo mirar CoffeeScript si desea una sintaxis alternativa. Tiene una taquigrafía similar a la que estás buscando.

Por ejemplo, el operador existencial

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

Atajos de funciones

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

Llamada de función sexy

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

También hay comentarios y clases de varias líneas. Obviamente, debe compilar esto en JavaScript o insertarlo en la página, <script type='text/coffeescript>'pero agrega mucha funcionalidad :). El uso <script type='text/coffeescript'>realmente solo está destinado al desarrollo y no a la producción.

Lima
fuente
14
lógico o no es lo que se necesita en la mayoría de los casos, ya que es posible que desee elegir el operando correcto solo si el izquierdo no está definido, pero no cuando está definido y es falso.
user2451227
¿Es mi error, o es realmente <script type='coffee/script>'?
JCCM
2
La página de inicio de CoffeeScript utiliza <script type="text/coffeescript">.
Elias Zamaria
19
Si bien esto responde a la pregunta, se trata casi por completo de coffeescript en lugar de javascript, y se trata más de la mitad de describir los beneficios de coffeescript no relacionados con el OP. Sugeriría reducirlo a lo que es relevante para la pregunta, tan maravilloso como lo son los otros beneficios de coffeescript.
jinglesthula
44
¿Me estoy volviendo loco? Seguramente, la objeción del usuario2451227 (actualmente con 4 votos) no es válida porque el operando medio del ternario (es decir, el operando derecho con el operador de Elvis) tampoco se seleccionaría si la expresión / operando izquierdo se define como falso. En ambos casos, debes irte x === undefined.
Mike roedor
115

Creo que lo siguiente es equivalente al operador de navegación segura, aunque un poco más largo:

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

streetNameentonces será el valor de user.address.streeto undefined.

Si desea que el valor predeterminado sea otra cosa, puede combinarlo con el acceso directo anterior o dar:

var streetName = (user && user.address && user.address.street) || "Unknown Street";
samjudson
fuente
77
¡más uno para un gran ejemplo de propagación nula y fusión nula!
Jay Wick
1
esto funciona, excepto que no sabrá si queda nulo o indefinido
Dave Cousineau
82

El operador lógico OR de Javascript está en cortocircuito y puede reemplazar su operador "Elvis":

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

Sin embargo, que yo sepa, no hay equivalente para su ?.operador.

Frédéric Hamidi
fuente
13
+1, olvidé que ||podría usarse de esa manera. Tenga en cuenta que esto no sólo se aglutinan cuando la expresión es null, sino también cuando es indefinido, false, 0, o la cadena vacía.
Cameron
@Cameron, de hecho, pero eso se menciona en la pregunta y parece ser la intención del interrogador. ""o 0podría ser inesperado, sin embargo :)
Frédéric Hamidi
72

En ocasiones he encontrado útil el siguiente idioma:

a?.b?.c

puede reescribirse como:

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

Esto aprovecha el hecho de que obtener atributos desconocidos en un objeto devuelve indefinido, en lugar de lanzar una excepción como lo hace en nullo undefined, por lo que reemplazamos nulo e indefinido con un objeto vacío antes de navegar.

James_pic
fuente
14
Bueno, es difícil de leer, pero es mejor que ese &&método detallado . +1.
chillido
1
Ese es el único operador seguro real en JavaScript en realidad. El operador lógico 'OR' que se menciona anteriormente es otra cosa.
vasilakisfil
@Filippos, ¿puede dar un ejemplo de comportamiento diferente en el método lógico OR vs &&? No puedo pensar en una diferencia
The Red Pea
También permite navegar por un valor anónimo sin asignarlo primero a una variable.
Matt Jenkins
1
¡Quiéralo! Realmente útil si desea obtener la propiedad de un objeto después de una operación array.find () que podría no devolver ningún resultado
Shiraz
24

Creo que lodash _.get()puede ayudar aquí, como en _.get(user, 'name'), y tareas más complejas como_.get(o, 'a[0].b.c', 'default-value')

tony_k
fuente
55
Mi principal problema con este método es el hecho de que, dado que el nombre de las propiedades son cadenas, ya no puede usar las funcionalidades de refactorización de su IDE con una confianza del 100%
RPDeshaies
21

Actualmente hay un borrador de especificación:

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

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

Por ahora, sin embargo, me gusta usar lodashget(object, path [,defaultValue]) o dlvdelve(obj, keypath)

Actualización (a partir del 23 de diciembre de 2019):

el encadenamiento opcional se ha movido a la etapa 4

Jack Tuck
fuente
Programación de maquillaje Lodash en javascript más apetecibles
gecos
2
el encadenamiento opcional se mudó recientemente a la etapa 4 , por lo que lo veremos en ES2020
Nick Parsons
1
@NickParsons ¡Gracias! Han actualizado la respuesta.
Jack Tuck
18

Actualización 2019

JavaScript ahora tiene equivalentes tanto para el operador de Elvis como para el operador de navegación segura.


Acceso seguro a la propiedad

El operador de encadenamiento opcional ( ?.) es actualmente una propuesta de ECMAScript de etapa 4 . Puedes usarlo hoy con Babel .

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

El operador lógico AND ( &&) es la forma "antigua" y más detallada de manejar este escenario.

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

Proporcionar un valor predeterminado

El operador de fusión nulo ( ??) es actualmente una propuesta de ECMAScript de etapa 3 . Puedes usarlo hoy con Babel . Le permite establecer un valor predeterminado si el lado izquierdo del operador es un valor nulo ( / ).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';

El operador lógico OR ( ||) es una solución alternativa con un comportamiento ligeramente diferente . Le permite establecer un valor predeterminado si el lado izquierdo del operador es falso . Tenga en cuenta que el resultado de myVariable3abajo difiere del de myVariable3arriba.

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
fuente
1
Esta respuesta necesita más votos a favor. El Operador Coalescente Nulo está ahora en la etapa 4.
Yerke
13

Para el primero, puedes usar ||. El operador "lógico o" Javascript, en lugar de simplemente devolver valores verdaderos y falsos enlatados, sigue la regla de devolver su argumento izquierdo si es verdadero y, de lo contrario, evaluar y devolver su argumento derecho. Cuando solo le interesa el valor de verdad, funciona igual, pero también significa que foo || bar || bazdevuelve el extremo izquierdo de foo, bar o baz que contiene un valor verdadero .

Sin embargo, no encontrará uno que pueda distinguir falso de nulo, y 0 y la cadena vacía son valores falsos, así que evite usar la value || defaultconstrucción donde valuelegítimamente puede ser 0 o "".

hobbs
fuente
44
Buen trabajo señalando que esto puede dar lugar a un comportamiento inesperado cuando el operando izquierdo es un valor de falsey no nulo.
Shog9
11

¡Sí hay! 🍾

El encadenamiento opcional está en la etapa 4 y esto le permite utilizar eluser?.address?.street fórmula.

Si no puede esperar el lanzamiento, instálelo @babel/plugin-proposal-optional-chainingy puede usarlo. Aquí están mis configuraciones que funcionan para mí, o simplemente lea el artículo 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
gazdagergo
fuente
44
Preguntó si había uno, no si podía agregar uno. Creo que esto no es muy útil teniendo en cuenta que no es lo que se le preguntó.
DeanMWake
2
Llegó a la etapa 3 del proceso de estandarización ECMAScript. es2020 🚀 - babeljs.io/docs/en/babel-plugin-proposal-optional-chaining
wedi
Creo que esta respuesta es engañosa como es.
Leonardo Raele el
1
¡Esta respuesta no es del todo correcta! El encadenamiento opcional todavía está en la etapa 3 y ES2020 aún no se ha lanzado o incluso finalizado. Al menos mencionaste cómo se puede usar sin tener que esperar a que se lance.
Maxie Berkmann el
@gazdagergo No hay problema :).
Maxie Berkmann el
6

Aquí hay un simple operador de Elvis equivalente:

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

ACTUALIZACIÓN SEP 2019

Sí, JS ahora es compatible con esto. El encadenamiento opcional llegará pronto a v8 leer más

Taher
fuente
No es lo mismo. OP está en marcha sobre fusión nula, pero buena respuesta, no obstante.
Maxie Berkmann el
4

Esto se conoce más comúnmente como operador de fusión nula. Javascript no tiene uno.

CassOnMars
fuente
3
cierto en sentido estricto, pero como han señalado otras respuestas, el operador lógico OR de JavaScript puede comportarse como una especie de operador de fusión falsa , lo que le permite lograr la misma brevedad en muchas situaciones.
Shog9
1
Este no es un operador de fusión nula. La fusión nula solo funciona en un valor único, no en una cadena de acceso a la propiedad / invocaciones de funciones. Ya puede hacer una fusión nula con el operador lógico OR en JavaScript.
No, puede hacer una fusión falsa con el OR lógico en JavaScript.
andresp
3

Puede lograr aproximadamente el mismo efecto diciendo:

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

Tengo una solución para eso, adaptarlo a sus propias necesidades, un extracto de una de mis librerías:

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

Funciona de maravilla. ¡Disfruta del menor dolor!

balazstth
fuente
Parece prometedor, ¿puede enviar por favor la fuente completa? ¿Lo tienes en algún lugar público? (por ejemplo, GitHub)
Eran Medan
1
Crearé un pequeño extracto del código en el que lo uso y lo publicaré en GitHub en una semana más o menos.
balazstth
2

Podrías rodar el tuyo:

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

Y úsalo así:

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

* el resultado no está definido si alguno de a, b, c o d no está definido.

Pylinux
fuente
1

Leí este artículo ( https://www.beyondjava.net/elvis-operator-aka-safe-navigation-javascript-typescript ) y modifiqué la solución 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 llamas así:

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

El resultado será indefinido para una expresión que encuentre nulo o indefinido a lo largo de su ruta. ¡Podría enloquecer y modificar el prototipo de Objeto!

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

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

Esto fue un problema para mí por mucho tiempo. Tuve que encontrar una solución que se pueda migrar fácilmente una vez que obtengamos el operador de Elvis o algo así.

Esto es lo que uso; funciona tanto para matrices como para objetos

poner esto en el archivo tools.js o algo

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

ejemplo de uso:

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

bueno, sé que hace que el código sea un poco ilegible, pero es una solución simple y funciona muy bien. Espero que esto ayude a alguien :)

Neut
fuente
0

Esta fue una solución interesante para el operador de navegación segura que utilizaba algunas combinaciones.

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
fuente
0

Creé un paquete que hace que esto sea mucho más fácil de usar.

NPM jsdig Github jsdig

Puede manejar cosas simples como y objeto:

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

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

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

o un poco más complicado:

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

Personalmente uso

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

y por ejemplo seguro obtener:

var a = e(obj,'e.x.y.z.searchedField');
kudłaty
fuente
2
Primero de ustedes realmente no debería usar eval . En segundo lugar, esto ni siquiera funciona: e({a:{b:{c:{d:'test'}}}}, 'a.b.c.d')vuelve null.
Pylinux
@Pylinux básicamente lo que funcionaría es e = eval, var a = eval('obj.a.b.c.d'). evalni siquiera toma un segundo parámetro ... developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
Dorian