Acceso a la propiedad nulo-seguro (y asignación condicional) en ES6 / 2015


149

¿Hay un nulloperador de acceso a propiedad seguro (propagación / existencia nula) en ES6 (ES2015 / JavaScript.next / Harmony) como ?.en CoffeeScript, por ejemplo? ¿O está planeado para ES7?

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

Esto será más o menos como:

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

Idealmente, la solución no debería asignar (incluso undefined) a aThingif possiblyNullisnull


3
@naomik Este tipo de comprobación nula puede ser muy útil si las declaraciones en las que está comprobando una propiedad profundamente anidada, por ejemplo, en if( obj?.nested?.property?.value )lugar deif( obj && obj.nested && obj.nested.property && obj.nested.property.value )
Sean Walsh

@SeanWalsh si sus objetos están tan anidados, o si sus funciones están cavando tan profundamente en sus objetos, probablemente también haya otros problemas con su aplicación.
Gracias

1
comparar var appConfig = loadConfig(config, process.env); connect(appConfig.database);a connect(config). Puede pasar un objeto mucho más sencillo connecten lugar de pasar todo el configobjeto, puede utilizar conf.username, conf.passworden lugar de intentar algo así como config[process.env]?.database?.username, config[process.env]?.database?.password. Referencia: Ley de Demeter .
Gracias

Además, si hace algo como establecer valores predeterminados o desinfectar propiedades (esto podría hacerse en loadConfigel ejemplo anterior), puede hacer suposiciones sobre la existencia de propiedades y omitir la comprobación nula en innumerables áreas de su aplicación.
Gracias

44
@naomik Siempre que el lenguaje admita objetos de anidación, sigue siendo una característica útil, independientemente de lo que usted o yo piense de la arquitectura de la aplicación en sí. Por otro lado, los gráficos de objetos complejos como este son muy comunes en los ORM que modelan un modelo de datos complejo.
Sean Walsh

Respuestas:


98

Actualización (2020-01-31): Parece que la gente todavía está encontrando esto, aquí está la historia actual:

Actualización (2017-08-01): si desea utilizar un complemento oficial, puede probar la compilación alfa de Babel 7 con la nueva transformación. Su experiencia puede ser diferente

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

Original :

Una característica que logra que se encuentra actualmente en la etapa 1: Encadenamiento opcional.

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

Si quiere usarlo hoy, hay un complemento de Babel que lo logra.

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


¿Entiendo correctamente que eso no incluye la asignación condicional? street = user.address?.streetpondría streeten cualquier caso?
ᆼ ᆺ ᆼ

1
En realidad, desafortunadamente creo que tienes razón. Street Creo que sería asignado undefined. Pero al menos no se trataría de acceder a propiedades indefinidas.
basicdays

2
También parece que usted puede ser el operador en la mano izquierda, y si evalúa algo indefinido, la mano derecha no se evalúa. El complemento babel puede variar un poco, todavía no lo he probado mucho.
basicdays

1
En cuanto a la asignación condicional en el lado izquierdo de la =, parece que eso no es compatible con la especificación oficial actualmente. github.com/tc39/proposal-optional-chaining#not-supported
basicdays

1
¡A partir de mayo de 2020, parece que los navegadores actuales y Typecript lo han implementado!
Josh Diehl

65

No es tan bueno como el? operador, pero para lograr un resultado similar podría hacer:

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

Desde nully undefinedson ambos Falsy valores ( ver esta referencia ), la propiedad después de que el &&operador sólo se accede si el precedente que no nulo o sin definir.

Alternativamente, podría escribir una función 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 

O, con un valor de reserva:

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

Probablemente debería aclarar la pregunta, lo principal que
buscaba

solo un corolario de eso; para encontrar el inverso de forma segura, puede usar !(user && user.address && user.address.postcode) :)
rob2d

foo && foo.bar && foo.bar.quux ...en una base de código grande, esto es feo y agrega mucha complejidad que sería mejor evitar.
Skylar Saveland

1
Esta es la solución más limpia que he encontrado en Internet. Lo uso con mecanografiado:_get<T>(func: () => T, fallbackValue?: T) :T
tomwassing

3
Si user.address.postcodeno está definido, _try(() => user.address.postcode, "")regresará en undefinedlugar de "". Entonces el código _try(() => user.address.postcode, "").lengthgenerará una excepción.
Alexander Chen

33

No. Puede usar lodash # get o algo así para esto en JavaScript.


44
¿Hay propuestas para agregarlo a ES7?
ᆼ ᆺ ᆼ

1
No he encontrado ninguno.
Girafa

3
@PeterVarga: Muchos. Demasiada discusión. La gramática no es algo fácil para esta función
Bergi

1
lodash.getes ligeramente diferente en que obviamente no puede hacer una asignación condicional.
ᆼ ᆺ ᆼ

17

Alternativa a la vainilla para un acceso seguro a la propiedad

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

La asignación condicional más concisa probablemente sería esta

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

Entonces, ¿cómo escribirías la tarea en la pregunta usando este mecanismo? ¿Todavía no necesita un condicional para no asignar en caso de possiblyNullque no esté definido / null?
ᆼ ᆺ ᆼ

Claro, aún necesita verificar si desea que una tarea suceda o no. Si usa el operador '=', inevitablemente se asignará algo, ya sea un dato, un indefinido o nulo. Entonces, lo anterior es solo un acceso seguro a la propiedad. ¿El operador de asignación condicional existe incluso en algún idioma?
yagger

Por supuesto, por ejemplo CoffeeScript , también tiene||=
ᆼ ᆺ ᆼ

+1 Incluso mi primer pensamiento fue (((a.b || {}).c || {}).d || {}).e. Pero más preciso sería((((a || {}).b || {}).c || {}).d || {}).e
IsmailS

6

No, no hay operador de propagación nulo en ES6. Tendrá que ir con uno de los patrones conocidos .

Sin embargo, puede utilizar la desestructuración:

({thing: aThing} = possiblyNull);

Hay muchas discusiones (por ejemplo, esto ) para agregar dicho operador en ES7, pero ninguna realmente despegó.


Eso parecía prometedor, sin embargo, al menos lo que Babel hace con él no es diferente de soloaThing = possiblyNull.thing
ᆼ ᆺ ᆼ

1
@ PeterVarga: Vaya, tienes razón, la desestructuración funciona cuando la propiedad no existe, pero no cuando el objeto sí null. Tendría que proporcionar un valor predeterminado, más o menos como este patrón pero con una sintaxis más confusa.
Bergi

4

Yendo por la lista aquí , actualmente no hay una propuesta para agregar un recorrido seguro a Ecmascript. Entonces, no solo no hay una buena manera de hacer esto, sino que no se agregará en el futuro previsible.


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

Encadenamiento opcional ?.y fusión nula??

Ahora puede usar directamente en ?.línea para probar la existencia de forma segura. Todos los navegadores modernos lo admiten.

?? se puede usar para establecer un valor predeterminado si no está definido o es nulo.

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

Si existe una propiedad, ?.pasa a la siguiente verificación o devuelve el valor válido. Cualquier falla provocará un cortocircuito y regresará de inmediato 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 garantizar un valor definido predeterminado, puede usar ??. Si necesita el primer valor verdadero, puede usarlo ||.

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

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

Si no marca un caso, la propiedad del lado izquierdo debe existir. Si no, arrojará una excepción.

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

?.Soporte de navegador : 78%, julio de 2020

??Soporte de navegador : 78%

Documentación de Mozilla

-

Asignación nula lógica, solución 2020+

Actualmente se están agregando nuevos operadores a los navegadores ??=, ||=y &&=. No hacen exactamente lo que está buscando, pero podrían conducir al mismo resultado dependiendo del objetivo de su código.

NOTA: Todavía no son comunes en las versiones de navegador público , pero Babel debería compilarse bien. Se actualizará a medida que cambie la disponibilidad.

??=comprueba si el lado izquierdo es indefinido o nulo, cortocircuito si ya está definido. Si no, al lado izquierdo se le asigna el valor del lado derecho. ||=y &&=son similares, pero se basan en los operadores ||y &&.

Ejemplos básicos

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

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

Ejemplos 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" }

Soporte de navegador Julio 2020 - .03%

Documentación de Mozilla


1
La pregunta también es sobre la asignación condicional
ᆼ ᆺ ᆼ

Se agregaron algunos ejemplos con ?.y ??, y una próxima solución detallada que puede funcionar para su situación. Probablemente la mejor solución actual sea simplemente haceraThing = possiblyNull?.thing ?? aThing
Gibolt

0

Un método seguro de obtención profunda parece un ajuste natural para underscore.js, pero el problema es evitar la programación de cadenas. Modificando la respuesta de @ Felipe para evitar la programación de cadenas (o al menos retrasa los casos límite a la persona que llama):

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

Ejemplo:

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

Parafraseando a Rick: esto solo suena como programación de cadenas con pasos adicionales.
tocqueville

1
lodash ahora implementa get / set profundo _.get(obj, array_or_dotstring)
prototipo el

1
Pero para ser justos, incluso la notación de JavaScript de punto y cadena es básicamente una programación de cadena, obj.a.b.cvsobj['a']['b']['c']
prototipo del

1
De donde keysviene
Levi Roberts,

-2

Sé que esta es una pregunta de JavaScript, pero creo que Ruby maneja esto de todas las formas solicitadas, así que creo que es un punto de referencia relevante.

.&, tryY && tienen sus puntos fuertes y los peligros potenciales. Un gran descuido de esas opciones aquí: http://mitrev.net/ruby/2015/11/13/the-operator-in-ruby/

TLDR; La conclusión es que Rubyistas diges tanto más fácil en los ojos y una garantía más fuerte que un valor o nullse le asignará.

Aquí hay una implementación simple en 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
}

Esto se puede utilizar para cualquier profundidad de anidamiento y funciones de manejo.

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

El tryenfoque es igualmente agradable de leer en JS, como se muestra en las respuestas anteriores. Tampoco requiere bucles, lo cual es un inconveniente de esta implementación.


Pongo esta respuesta aquí como una solución divertida / completa / alternativa b / c Me gusta la forma en que Ruby hace esto con azúcar sintáctico. Solo exponiendo esto para la comunidad: vote una vez más y con gusto eliminaré esta publicación. ¡Salud!
theUtherSide

Realmente genial ver que ECMAScript2019 tiene una gran propuesta para el "Encadenamiento opcional" similar a Ruby: github.com/tc39/proposal-optional-chaining Está disponible hoy a través de Babel Plugin, y también funciona para invocar métodos en un objeto: babeljs.io / docs / es / babel-plugin-
offer

-5

Pensé que esta pregunta necesitaba un poco de actualización para 2018. Esto se puede hacer muy bien sin el uso de bibliotecas Object.defineProperty()y se puede usar de la siguiente manera:

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

Considero que esto es seguro (y js-ético) debido a las definiciones writeabley enumerableahora disponibles para el definePropertymétodo de Object, como se documenta en MDN

definición de la función a continuación:

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

He reunido un jsBin con salida de consola para demostrar esto. Tenga en cuenta que en la versión jsBin también he agregado una excepción personalizada para valores vacíos. Esto es opcional, por lo que lo he dejado fuera de la definición mínima anterior.

Las mejoras son bienvenidas


2
Con esto, estás escribiendo de facto el código en cadenas. Realmente es una mala idea hacer eso. Hace que su código sea imposible de refactorizar y no sea compatible con IDE.
tocqueville
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.