Acesso à propriedade com segurança nula (e atribuição condicional) no ES6 / 2015


149

Existe um nulloperador de acesso à propriedade -safe (propagação / existência nula) no ES6 (ES2015 / JavaScript.next / Harmony) como ?.no CoffeeScript, por exemplo? Ou está planejado para o ES7?

var aThing = getSomething()
...
aThing = possiblyNull?.thing

Isso será mais ou menos como:

if (possiblyNull != null) aThing = possiblyNull.thing

Idealmente, a solução não deve atribuir (mesmo undefined) a aThingse possiblyNullénull


3
@naomik Esse tipo de verificação nula pode ser muito útil para instruções if nas quais você está verificando uma propriedade profundamente aninhada, por exemplo, em if( obj?.nested?.property?.value )vez deif( obj && obj.nested && obj.nested.property && obj.nested.property.value )
Sean Walsh

@SeanWalsh se seus objetos estão profundamente aninhados ou se suas funções estão cavando profundamente em seus objetos, provavelmente também há vários outros problemas com seu aplicativo.
Obrigado

1
compare var appConfig = loadConfig(config, process.env); connect(appConfig.database);com connect(config). Você pode passar um objeto muito mais simples connectem vez de passar todo o configobjeto, você pode usar conf.username, conf.passwordem vez de tentar algo como config[process.env]?.database?.username, config[process.env]?.database?.password. Referência: Lei de Demeter .
Obrigado

Além disso, se você fizer algo como definir padrões ou desinfetar propriedades (isso pode ser feito no loadConfigexemplo acima), você pode fazer suposições sobre a existência de propriedades e pular a verificação nula em inúmeras áreas do seu aplicativo.
obrigado

4
@naomik Enquanto a linguagem suportar objetos de aninhamento, ainda será um recurso útil - independentemente do que você ou eu pensamos da arquitetura do aplicativo em si. Além disso, gráficos de objetos complexos como esse são muito comuns em ORMs que estão modelando um modelo de dados complexo.
Sean Walsh

Respostas:


98

Atualização (2020-01-31): Parece que as pessoas ainda estão encontrando isso, aqui está a história atual:

Atualização (01/08/2017): Se você deseja usar um plug-in oficial, pode tentar a versão alfa do Babel 7 com a nova transformação. Sua milhagem pode variar

https://www.npmjs.com/package/babel-plugin-transform-optional-chaining

Original :

Um recurso que realiza o que está atualmente no estágio 1: encadeamento opcional.

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

Se você quiser usá-lo hoje, existe um plugin Babel que realiza isso.

https://github.com/davidyaha/ecmascript-optionals-proposal


Eu entendi corretamente que isso não inclui atribuição condicional? street = user.address?.streetdefiniria streetem qualquer caso?
ᆼ ᆺ ᆼ

1
Na verdade, infelizmente, acho que você está certo. Street Eu acho que seria atribuído undefined. Mas pelo menos não tentaria acessar propriedades indefinidas.
basicdays 31/01

2
Também parece que você pode usar o operador na mão esquerda e, se avaliar algo indefinido, a mão direita não será avaliada. O plugin babel pode variar um pouco, ainda não o testei muito.
basicdays 31/01

1
Em relação à atribuição condicional no lado esquerdo da =, parece que isso não é suportado na especificação oficial atualmente. github.com/tc39/proposal-optional-chaining#not-supported
basicdays

1
Em maio de 2020, parece que os navegadores atuais e o Typecript implementaram isso!
Josh Diehl

65

Não é tão bom quanto o. operador, mas para obter um resultado semelhante, você pode:

user && user.address && user.address.postcode

Como nulle undefinedsão ambos valores falsos ( consulte esta referência ), a propriedade após o &&operador é acessada apenas se o precedente não for nula ou indefinida.

Como alternativa, você pode escrever uma função como esta:

function _try(func, fallbackValue) {
    try {
        var value = func();
        return (value === null || value === undefined) ? fallbackValue : value;
    } catch (e) {
        return fallbackValue;
    }
}

Uso:

_try(() => user.address.postcode) // return postcode or undefined 

Ou, com um valor de fallback:

_try(() => user.address.postcode, "none") // return postcode or a custom string

Certo, provavelmente eu deveria esclarecer a pergunta, a principal coisa que eu
procurava

apenas um corolário disso; para encontrar o inverso com segurança, você pode usar !(user && user.address && user.address.postcode) :)
rob2d

foo && foo.bar && foo.bar.quux ...em uma grande base de código, isso é feio e acrescenta muita complexidade que você evitaria.
Skylar Saveland

1
Esta é a solução mais limpa que encontrei na internet. Eu uso isso com o _get<T>(func: () => T, fallbackValue?: T) :T
texto

3
Se user.address.postcodefor indefinido, _try(() => user.address.postcode, "")retornará em undefinedvez de "". Portanto, o código _try(() => user.address.postcode, "").lengthgerará uma exceção.
Alexander Chen

33

Não. Você pode usar lodash # get ou algo parecido para isso em JavaScript.


4
Existem propostas para adicioná-lo ao ES7?
ᆼ ᆺ ᆼ

1
Não encontrou nenhum.
Girafa

3
@ PeterVarga: Muitos. Muita discussão. Gramática não é uma coisa fácil para esta função
Bergi

1
lodash.geté um pouco diferente, pois obviamente não pode fazer uma atribuição condicional.
ᆼ ᆺ ᆼ

17

Alternativa de baunilha para acesso seguro à propriedade

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

A atribuição condicional mais concisa provavelmente seria essa

try { b = a.b.c.d.e } catch(e) {}

Então, como você escreveria a tarefa na pergunta usando esse mecanismo? Você ainda não precisa de um condicional para não atribuir, caso o possiblyNull/ não esteja definido / null?
ᆼ ᆺ ᆼ

Claro, você ainda precisa verificar se deseja ou não uma tarefa. Se você usar o operador '=', algo será inevitavelmente atribuído, seja um dado, um indefinido ou nulo. Portanto, o acima é apenas um acesso seguro à propriedade. O operador de atribuição condicional existe em algum idioma?
yagger

É claro, por exemplo CoffeeScript , ele também tem||=
ᆼ ᆺ ᆼ

+1 Até meu primeiro pensamento foi (((a.b || {}).c || {}).d || {}).e. Mas mais precisa seria((((a || {}).b || {}).c || {}).d || {}).e
Ismails

6

Não, não há operador de propagação nulo no ES6. Você terá que seguir um dos padrões conhecidos .

Você pode usar a desestruturação, no entanto:

({thing: aThing} = possiblyNull);

Existem muitas discussões (por exemplo, isso ) para adicionar um operador no ES7, mas nenhuma realmente decolou.


Isso parecia promissor, no entanto, pelo menos o que Babel faz com ele não é diferente de apenasaThing = possiblyNull.thing
#: 21815

1
@PeterVarga: Opa, você está certo, a desestruturação funciona quando a propriedade não existe, mas não quando o objeto está null. Você precisaria fornecer um valor padrão, muito parecido com esse padrão, mas com uma sintaxe mais confusa.
Bergi

4

Indo pela lista aqui , atualmente não há nenhuma proposta para adicionar uma travessia segura ao Ecmascript. Portanto, não apenas não há uma maneira agradável de fazer isso, mas não será adicionado em um futuro próximo.


1
// Typescript
static nullsafe<T, R>(instance: T, func: (T) => R): R {
    return func(instance)
}

// Javascript
function nullsafe(instance, func) {
    return func(instance);
};

// use like this
const instance = getSomething();
let thing = nullsafe(instance, t => t.thing0.thing1.thingx);

1

Encadeamento opcional ?. e coalescência nula??

Agora você pode usar diretamente ?. inline para testar com segurança a existência. Todos os navegadores modernos são compatíveis.

?? pode ser usado para definir um valor padrão se indefinido ou nulo.

aThing = possiblyNull ?? aThing
aThing = a?.b?.c ?? possiblyNullFallback ?? aThing

Se existir uma propriedade, ?.prossiga para a próxima verificação ou retorne o valor válido. Qualquer falha irá imediatamente causar um curto-circuito e retornar undefined.

const example = {a: ["first", {b:3}, false]}

example?.a  // ["first", {b:3}, false]
example?.b  // undefined

example?.a?.[0]     // "first"
example?.a?.[1]?.a  // undefined
example?.a?.[1]?.b  // 3

domElement?.parentElement?.children?.[3]?.nextElementSibling

null?.()                // undefined
validFunction?.()       // result
(() => {return 1})?.()  // 1

Para garantir um valor definido padrão, você pode usar ??. Se você precisar do primeiro valor de verdade, poderá usar ||.

example?.c ?? "c"  // "c"
example?.c || "c"  // "c"

example?.a?.[2] ?? 2  // false
example?.a?.[2] || 2  // 2

Se você não verificar um caso, a propriedade do lado esquerdo deverá existir. Caso contrário, lançará uma exceção.

example?.First         // undefined
example?.First.Second  // Uncaught TypeError: Cannot read property 'Second' of undefined

?.Suporte do navegador - 78%, julho de 2020

??Suporte ao navegador - 78%

Documentação Mozilla

-

Designação nula lógica, solução 2020+

Novos operadores estão sendo adicionados aos navegadores, ??=, ||=e &&=. Eles não fazem exatamente o que você está procurando, mas podem levar ao mesmo resultado, dependendo do objetivo do seu código.

NOTA: Estas não são comuns nas versões públicas do navegador ainda , mas Babel deve transpile bem. Será atualizado conforme a disponibilidade for alterada.

??=verifica se o lado esquerdo está indefinido ou nulo, em curto-circuito, se já definido. Caso contrário, o lado esquerdo recebe o valor do lado direito. ||=e &&=são semelhantes, mas com base no ||e &&operadores.

Exemplos básicos

let a          // undefined
let b = null
let c = false

a ??= true  // true
b ??= true  // true
c ??= true  // false

Exemplos de objeto / matriz

let x = ["foo"]
let y = { foo: "fizz" }

x[0] ??= "bar"  // "foo"
x[1] ??= "bar"  // "bar"

y.foo ??= "buzz"  // "fizz"
y.bar ??= "buzz"  // "buzz"

x  // Array [ "foo", "bar" ]
y  // Object { foo: "fizz", bar: "buzz" }

Suporte para navegadores julho de 2020 - 0,03%

Documentação Mozilla


1
A pergunta também é sobre atribuição condicional
ᆼ ᆺ ᆼ 10/07

Foram adicionados alguns exemplos com ?.e ??, e uma solução futura detalhada que pode funcionar para sua situação. Provavelmente, a melhor solução atual é apenas fazeraThing = possiblyNull?.thing ?? aThing
Gibolt

0

Um método deep get seguro parece um ajuste natural para o underscore.js, mas o problema é evitar a programação de strings. Modificando a resposta de @ Felipe para evitar a programação de strings (ou pelo menos empurra as bordas de volta ao chamador):

function safeGet(obj, props) {
   return (props.length==1) ? obj[keys[0]] :safeGet(obj[props[0]], props.slice(1))
}

Exemplo:

var test = { 
  a: { 
    b: 'b property value',
    c: { }
  } 
}
safeGet(test, ['a', 'b']) 
safeGet(test, "a.b".split('.'))  

Parafraseando Rick: isso soa como programação de strings com etapas extras.
tocqueville

1
O lodash agora implementa o get / set profundo _.get(obj, array_or_dotstring)
protótipo

1
Mas para ser justo, mesmo o ponto Javascript e notação corda acessor é basicamente programação corda, obj.a.b.cvsobj['a']['b']['c']
protótipo

1
De onde keysvem?
Levi Roberts

-2

Sei que essa é uma pergunta sobre JavaScript, mas acho que Ruby lida com isso de todas as formas solicitadas, então acho que é um ponto de referência relevante.

.&, tryE && têm seus pontos fortes e potenciais armadilhas. Um excelente resumo dessas opções aqui: http://mitrev.net/ruby/2015/11/13/the-operator-in-ruby/

TLDR; A conclusão dos rubiístas é que digé mais fácil aos olhos e uma garantia mais forte de que um valor ou nullserá atribuído.

Aqui está uma implementação simples no TypeScript:

export function dig(target: any, ...keys: Array<string>): any {
  let digged = target
  for (const key of keys) {
    if (typeof digged === 'undefined') {
      return undefined // can also return null or a default value
    }
    if (typeof key === 'function') {
      digged = key(digged)
    } else {
      digged = digged[key]
    }
  }
  return digged
}

Isso pode ser usado para qualquer profundidade de aninhamento e manipula funções.

a = dig(b, 'c', 'd', 'e');
foo = () => ({});
bar = dig(a, foo, 'b', 'c')

A tryabordagem é igualmente agradável de ler em JS, como mostrado nas respostas anteriores. Também não requer loop, que é uma desvantagem desta implementação.


Coloquei essa resposta aqui como uma solução divertida / completa / alternativa b / c Eu gosto da maneira como o ruby ​​faz isso com o açúcar sintático. Apenas divulgando isso para a comunidade - vote novamente mais uma vez e eu excluirei esta postagem com prazer. Felicidades!
theUtherSide 25/09/19

É muito legal ver que o ECMAScript2019 tem uma ótima proposta para o "Encadeamento opcional" do tipo Ruby: github.com/tc39/proposal-optional-chaining Está disponível hoje via Babel Plugin e funciona para chamar métodos em um objeto também: babeljs.io / docs / pt-br / babel-plugin-proposta-opcional-encadeamento
theUtherSide

-5

Eu pensei que essa pergunta precisava de uma atualização para 2018. Isso pode ser feito sem a necessidade de bibliotecas Object.defineProperty()e pode ser usado da seguinte maneira:

myVariable.safeGet('propA.propB.propC');

Considero isso seguro (e js-ético) por causa das definições writeablee enumerableagora disponíveis para o definePropertymétodo de Object, conforme documentado no MDN

definição da função abaixo:

Object.defineProperty(Object.prototype, 'safeGet', { 
    enumerable: false,
    writable: false,
    value: function(p) {
        return p.split('.').reduce((acc, k) => {
            if (acc && k in acc) return acc[k];
            return undefined;
        }, this);
    }
});

Reuni um jsBin com a saída do console para demonstrar isso. Observe que na versão jsBin também adicionei uma exceção personalizada para valores vazios. Isso é opcional e, portanto, deixei de fora da definição mínima acima.

Melhorias são bem-vindas


2
Com isso, você está de fato escrevendo código em strings. É realmente uma má ideia fazer isso. Torna seu código impossível de refatorar e não é compatível com IDE.
tocqueville
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.