Existe um operador de coalescência nula (Elvis) ou um operador de navegação segura em javascript?


210

Vou explicar pelo exemplo:

Operador de Elvis (?:)

O "operador Elvis" é uma abreviação do operador ternário de Java. Uma instância em que isso é útil é o retorno de um valor 'sensível padrão' se uma expressão resolver como falsa ou nula. Um exemplo simples pode ser assim:

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

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

Operador de navegação segura (?.)

O operador Navegação segura é usado para evitar uma NullPointerException. Normalmente, quando você tem uma referência a um objeto, pode ser necessário verificar se ele não é nulo antes de acessar métodos ou propriedades do objeto. Para evitar isso, o operador de navegação segura simplesmente retornará nulo em vez de lançar uma exceção, da seguinte forma:

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
O 'Elvis Operator' existe em C # - mas ele é chamado o operador coalescentes nulo (muito menos emocionante) :-)
Cameron

Se você quiser uma sintaxe alternativa que você pode dar uma olhada de coffeescript
Lime

Essa pergunta é meio bagunçada ... está misturando 3 operadores diferentes? : (operador de ternery, explicitado na pergunta, possivelmente um erro de digitação), ?? (coalescência nula, que existe em JavaScript) e?. (Elvis) que NÃO existe em JavaScript. As respostas não esclarecem muito bem essa distinção.
JoelFan

2
@JoelFan, você pode fornecer um link para a documentação referente à coalescência nula adequada ( ??) em javascript? Tudo o que estou encontrando até agora sugere que o JS só tem coalescência "falsey" (usando ||).
Charles Wood

1
Bem, eu não quis dizer que JS literalmente tinha ?? mas que tinha coalescência nula ... mas mesmo lá eu estava meio errado. Dito isto, eu vi um monte de código JS que usa || como coalesce nulo, apesar das armadilhas Falsey
JoelFan

Respostas:


139

Você pode usar o operador lógico 'OR' no lugar do operador Elvis:

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

Mas o Javascript atualmente não tem a outra funcionalidade. Eu recomendo olhar o CoffeeScript se você quiser uma sintaxe alternativa. Tem alguma abreviação que é semelhante ao que você está procurando.

Por exemplo, O Operador Existencial

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

Atalhos de função

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

Chamada de função sexy

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

Há também comentários e classes com várias linhas. Obviamente, você precisa compilar isso em javascript ou inserir na página, <script type='text/coffeescript>'mas isso adiciona muitas funcionalidades :). O uso <script type='text/coffeescript'>é realmente destinado apenas ao desenvolvimento e não à produção.


14
lógico ou não é exatamente o necessário na maioria dos casos, pois você pode escolher o operando certo apenas se a esquerda estiver indefinida, mas não quando definida e falsificada.
User2451227

É meu erro ou é mesmo <script type='coffee/script>'?
JCCM 07/07

2
A página inicial do CoffeeScript usa <script type="text/coffeescript">.
Elias Zamaria

19
Embora isso responda à pergunta, trata-se quase inteiramente de café expresso em vez de javascript, e mais da metade da descrição de benefícios de café expresso não relacionados ao OP. Eu sugiro resumir exatamente o que é relevante para a pergunta, por mais maravilhosos que sejam os outros benefícios do coffeescript.
jinglesthula

4
Vou bananas? Certamente a objeção do usuário2451227 (atualmente com 4 votos) é inválida porque o operando do ternário do meio (isto é, operando da direita com o operador Elvis) também não seria escolhido se a expressão / operando da esquerda for definida e falsificada. Nos dois casos, você precisa ir embora x === undefined.
mike roedor

115

Penso que o seguinte é equivalente ao operador de navegação segura, embora um pouco mais longo:

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

streetNameserá então o valor de user.address.streetou undefined.

Se você deseja que o padrão seja outra coisa, você pode combinar com o atalho acima ou fornecer:

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

7
mais um por um ótimo exemplo de propagação nula e coalescência nula!
Jay Wick

1
isso funciona, exceto que você não vai saber se você ficar nulo ou indefinido fora dele
Dave Cousineau

82

O operador OR lógico do Javascript está em curto-circuito e pode substituir o operador "Elvis":

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

No entanto, que eu saiba, não há equivalente ao seu ?.operador.


13
+1, esqueci que ||poderia ser usado dessa maneira. Esteja ciente de que isso vai se unir não só quando a expressão é null, mas também quando é indefinido, false, 0, ou a cadeia vazia.
Cameron

@Cameron, de fato, mas isso é mencionado na pergunta e parece ser a intenção do questionador. ""ou 0pode ser inesperado, embora :)
Frédéric Hamidi

72

Ocasionalmente, achei útil o seguinte idioma:

a?.b?.c

pode ser reescrito como:

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

Isso tira vantagem do fato de que obter atributos desconhecidos em um objeto retorna indefinido, em vez de lançar uma exceção como ocorre em nullou undefined, portanto, substituímos nulo e indefinido por um objeto vazio antes de navegar.


14
Bem, é difícil de ler, mas é melhor que esse &&método detalhado . +1.
grito

1
Esse é o único operador seguro real em javascript, na verdade. O operador lógico 'OU' mencionado acima é outra coisa.
vasilakisfil

@Filippos, você pode dar um exemplo de comportamento diferente no método OR vs && lógico? Não consigo pensar em uma diferença
The Red Pea

Também permite navegar em um valor anônimo sem atribuí-lo a uma variável primeiro.
Matt Jenkins

1
Adoro! Realmente útil se você deseja obter a propriedade de um objeto após uma operação array.find () que pode não retornar resultados
Shiraz

24

Eu acho que o Lodash _.get()pode ajudar aqui, como em _.get(user, 'name'), e tarefas mais complexas, como_.get(o, 'a[0].b.c', 'default-value')


5
A principal problema com este método é o fato de que, desde o nome das propriedades são string, você não pode usar refatoração funcionalidades do seu IDE, com uma confiança de 100% mais
RPDeshaies

21

Atualmente, há uma especificação de rascunho:

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

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

Por enquanto, porém, eu gosto de usar lodashget(object, path [,defaultValue]) ou DLVdelve(obj, keypath)

Atualização (em 23 de dezembro de 2019):

encadeamento opcional mudou-se para o estágio 4


Lodash torna a programação em javascript mais agradável
geckos

2
opcional encadeamento apenas recentemente mudou-se para a fase 4 , por isso vamos estar vendo em ES2020
Nick Parsons

1
@NickParsons Thanks! Atualizou a resposta.
Jack Tuck

18

Atualização de 2019

O JavaScript agora tem equivalentes para o Operador Elvis e o Operador de Navegação Segura.


Acesso seguro à propriedade

O operador de encadeamento opcional ( ?.) atualmente é uma proposta de estágio 4 do ECMAScript . Você pode usá-lo hoje com Babel .

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

O operador AND lógico ( &&) é a maneira "antiga" e mais detalhada de lidar com esse cenário.

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

Fornecendo um padrão

O operador coalescente nulo ( ??) é atualmente uma proposta de estágio 3 do ECMAScript . Você pode usá-lo hoje com Babel . Permite definir um valor padrão se o lado esquerdo do operador for um 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';

O operador OR lógico ( ||) é uma solução alternativa com comportamento ligeiramente diferente . Permite definir um valor padrão se o lado esquerdo do operador for falso . Observe que o resultado myVariable3abaixo difere do myVariable3acima.

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
Esta resposta precisa de mais votos. O operador de coalizão nulo está agora no estágio 4.
Yerke

13

Para o primeiro, você pode usar ||. O operador "lógico ou" Javascript, em vez de simplesmente retornar valores verdadeiros e falsos enlatados, segue a regra de retornar seu argumento esquerdo, se for verdadeiro, e avaliar e retornar o argumento correto. Quando você está interessado apenas no valor verdade, ele funciona da mesma forma, mas também significa que foo || bar || bazretorna o valor mais à esquerda de foo, bar ou baz que contém um valor verdadeiro .

Você não encontrará um que possa distinguir false de null, e 0 e string vazia são valores falsos; portanto, evite usar a value || defaultconstrução em valueque legitimamente pode ser 0 ou "".


4
Bom trabalho, observando que isso pode resultar em comportamento inesperado quando o operando esquerdo é um valor de falsey não nulo.
precisa saber é o seguinte

11

Sim existe! 🍾

O encadeamento opcional está no estágio 4 e isso permite que você use a user?.address?.streetfórmula.

Se você não pode esperar o lançamento, instale @babel/plugin-proposal-optional-chaininge você pode usá-lo. Aqui estão minhas configurações que funcionam para mim ou apenas leia o artigo 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
Ele perguntou se havia um, não se você poderia adicionar um. Eu acho que isso não é super útil, considerando que não é o que foi solicitado.
precisa saber é o seguinte

2
Atingiu o estágio 3 do processo de padronização do ECMAScript. es2020 🚀 - babeljs.io/docs/en/babel-plugin-proposal-optional-chaining
wedi

Eu acho que essa resposta é enganosa como é.
Leonardo Raele 11/10/19

1
Esta resposta não está correta! O encadeamento opcional ainda está no estágio 3 e o ES2020 ainda não foi lançado ou finalizado. Pelo menos você mencionou como alguém pode usá-lo sem ter que esperar o lançamento.
Maxie Berkmann

@gazdagergo Sem problemas :).
Maxie Berkmann

6

Aqui está um simples 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

5

ATUALIZAÇÃO SEP 2019

Sim, o JS agora suporta isso. O encadeamento opcional está chegando em breve à v8 leia mais


Não exatamente o mesmo. O OP está ligado à coalescência nula, mas mesmo assim é uma boa resposta.
Maxie Berkmann

4

Isso é mais conhecido como um operador de coalescência nula. Javascript não possui um.


3
true no sentido estrito, mas como outras respostas observaram, o operador OR lógico do JavaScript pode se comportar como uma espécie de operador de falso coalescimento, permitindo que você atinja a mesma brevidade em muitas situações.
Shog9

1
Este não é um operador de coalescência nula. A coalescência nula funciona apenas em um único valor, não em uma cadeia de chamadas de acesso / função à propriedade. Você já pode fazer coalescência nula com o operador OR lógico em JavaScript.

Não, você pode fazer uma coalizão falsa com o OR lógico em JavaScript.
22417 Andresp 13:15

3

Você pode obter aproximadamente o mesmo efeito dizendo:

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

2

Eu tenho uma solução para isso, adapte-a às suas próprias necessidades, um trecho de uma das minhas bibliotecas:

    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 como um encanto. Aproveite a menos dor!


Parece promissor, você pode enviar a fonte completa? você tem em algum lugar público? (eg GitHub)
Eran Medan 27/03

1
Vou criar um pequeno trecho do código em que o uso e publicá-lo no GitHub dentro de uma semana.
balazstth

2

Você pode fazer o seu próprio:

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 use-o assim:

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

* resultado é indefinido se qualquer um de a, b, c ou d for indefinido.


1

Li este artigo ( https://www.beyondjava.net/elvis-operator-aka-safe-navigation-javascript-typescript ) e modifiquei a solução 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;
}

Você chama assim:

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

O resultado será indefinido para uma expressão que encontre nula ou indefinida ao longo do caminho. Você pode ir à loucura e modificar o protótipo de objeto!

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

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


1

Isso foi um problema para mim por um longo tempo. Eu tive que encontrar uma solução que pudesse ser facilmente migrada quando obtivemos o operador Elvis ou algo assim.

É isso que eu uso; funciona para matrizes e objetos

coloque isso no arquivo tools.js ou algo assim

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

exemplo 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

bem, eu sei que isso torna o código um pouco ilegível, mas é uma solução simples de um liner e funciona muito bem. Eu espero que isso ajude alguém :)


0

Esta foi uma solução interessante para o operador de navegação segura usando algumas combinações ..

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

Criei um pacote que facilita muito o uso.

NPM jsdig Github jsdig

Você pode lidar com coisas simples como e objeto:

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

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

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

ou um pouco mais complicado:

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

-6

Pessoalmente eu uso

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

e, por exemplo, get seguro:

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

2
Primeiro, você realmente não deve usar eval . Em segundo lugar, isso nem funciona: e({a:{b:{c:{d:'test'}}}}, 'a.b.c.d')retorna null.
Pylinux

@Pylinux basicamente o que iria trabalhar é e = eval, var a = eval('obj.a.b.c.d'). evalnem sequer usa um segundo parâmetro ... developer.mozilla.org/pt-BR/docs/Web/JavaScript/Reference/…
Dorian
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.