Accesso alla proprietà null-safe (e assegnazione condizionale) in ES6 / 2015


150

Esiste un nulloperatore di accesso alla proprietà sicuro (propagazione / esistenza nulla) in ES6 (ES2015 / JavaScript.next / Harmony) come ad esempio ?.in CoffeeScript ? O è previsto per ES7?

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

Questo sarà approssimativamente come:

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

Idealmente la soluzione non dovrebbe essere assegnata (nemmeno undefined) a aThingif possiblyNullisnull


3
@naomik Questo tipo di controllo null può essere molto utile per le istruzioni if ​​in cui stai controllando una proprietà profondamente nidificata, ad esempio if( obj?.nested?.property?.value )invece diif( obj && obj.nested && obj.nested.property && obj.nested.property.value )
Sean Walsh

@SeanWalsh se i tuoi oggetti sono così nidificati o se le tue funzioni lo stanno scavando profondamente negli oggetti, probabilmente ci sono anche molti altri problemi con la tua app.
Grazie,

1
confrontare var appConfig = loadConfig(config, process.env); connect(appConfig.database);con connect(config). È possibile passare un oggetto molto più semplice per connectinvece di passare l'intero configoggetto, è possibile utilizzare conf.username, conf.passwordinvece di tentare qualcosa di simile config[process.env]?.database?.username, config[process.env]?.database?.password. Riferimento: Legge di Demetra .
Grazie,

Inoltre, se fai qualcosa come impostare i valori predefiniti o disinfettare le proprietà (questo potrebbe essere fatto loadConfignell'esempio sopra), puoi fare ipotesi sull'esistenza di proprietà e saltare il controllo null in innumerevoli aree della tua app.
Grazie,

4
@naomik Fintanto che il linguaggio supporta gli oggetti di nidificazione, è comunque una funzione utile, indipendentemente da ciò che tu o io pensiamo dell'architettura dell'app stessa. Per inciso, grafici di oggetti complessi come questo sono molto comuni negli ORM che modellano un modello di dati complesso.
Sean Walsh,

Risposte:


100

Aggiornamento (2020-01-31): sembra che la gente stia ancora trovando questo, ecco la storia attuale:

Aggiornamento (01-08-2017): se desideri utilizzare un plug-in ufficiale, puoi provare la build alpha di Babel 7 con la nuova trasformazione. Il tuo chilometraggio può variare

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

Originale :

Una funzionalità che è attualmente in fase 1: Concatenamento opzionale.

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

Se vuoi usarlo oggi, c'è un plugin Babel che lo realizza.

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


Capisco correttamente che ciò non include l'assegnazione condizionale però? street = user.address?.streetsarebbe impostato streetin ogni caso?
ᆼ ᆺ ᆼ

1
In realtà purtroppo penso che tu abbia ragione. Street Penso che sarebbe assegnato undefined. Ma almeno non avrebbe tentato di accedere alle proprietà su un indefinito.
giorni base

2
Sembra anche che tu possa fare l'operatore con la mano sinistra e, se si evoca qualcosa di indefinito, la mano destra non viene valutata. Il plugin babel può variare leggermente, non l'ho ancora testato molto da solo.
giorni base

1
Per quanto riguarda l'assegnazione condizionale sul lato sinistro di =, sembra che al momento non sia supportato nelle specifiche ufficiali. github.com/tc39/proposal-optional-chaining#not-supported
basicdays

1
A partire da maggio 2020, sembra che i browser attuali e Typescript abbiano implementato questo!
Josh Diehl,

66

Non è bello come il?. operatore, ma per ottenere un risultato simile potresti fare:

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

Poiché nulle undefinedsono entrambi valori falsi ( vedere questo riferimento ), &&si accede alla proprietà dopo l' operatore solo se il precedente non è nullo o indefinito.

In alternativa, potresti scrivere una funzione come questa:

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 

Oppure, con un valore di fallback:

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

Bene, probabilmente dovrei chiarire la domanda, la cosa principale che stavo
cercando

solo un corollario a quello; per trovare l'inverso in sicurezza, puoi usare !(user && user.address && user.address.postcode) :)
rob2d

foo && foo.bar && foo.bar.quux ...in una grande base di codice questo è brutto e aggiunge molta complessità che sarebbe meglio evitare.
Skylar Saveland,

1
Questa è la soluzione più pulita che ho trovato su Internet. Lo uso con dattiloscritto:_get<T>(func: () => T, fallbackValue?: T) :T
tomwassing il

3
Se user.address.postcodenon è definito, _try(() => user.address.postcode, "")verrà restituito undefinedanziché "". Quindi il codice _try(() => user.address.postcode, "").lengthgenererà un'eccezione.
Alexander Chen,

33

No. Puoi usare lodash # get o qualcosa del genere per questo in JavaScript.


4
Ci sono proposte per aggiungerlo a ES7?
ᆼ ᆺ ᆼ

1
Non ne ho incontrato nessuno.
Girafa,

3
@PeterVarga: molti. Troppa discussione. Grammatica non è una cosa facile per questa funzione
Bergi,

1
lodash.getè leggermente diverso in quanto ovviamente non può fare un incarico condizionato.
ᆼ ᆺ ᆼ

17

Alternativa alla vaniglia per un accesso sicuro alle proprietà

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

Il compito condizionale più conciso sarebbe probabilmente questo

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

Quindi come scriveresti il ​​compito nella domanda usando questo meccanismo? Non hai ancora bisogno di un condizionale per non assegnare nel caso in cui possiblyNullnon sia definito / null?
ᆼ ᆺ ᆼ

Certo, devi ancora verificare se vuoi che un compito accada o meno. Se usi l'operatore '=', qualcosa verrà inevitabilmente assegnato, sia esso un dato, un indefinito o nullo. Quindi quanto sopra è solo un accesso sicuro alla proprietà. Esiste anche un operatore di assegnazione condizionale, in qualsiasi lingua?
yagger,

Naturalmente, per esempio CoffeeScript , ha anche||=
ᆼ ᆺ ᆼ

+1 Anche il mio primo pensiero è stato (((a.b || {}).c || {}).d || {}).e. Ma sarebbe più preciso((((a || {}).b || {}).c || {}).d || {}).e
IsmailS,

6

No, non esiste un operatore di propagazione null in ES6. Dovrai seguire uno dei modelli noti .

Potresti essere in grado di usare la destrutturazione, tuttavia:

({thing: aThing} = possiblyNull);

Ci sono molte discussioni (ad esempio questo ) per aggiungere un tale operatore in ES7, ma nessuna è decollata.


Sembrava promettente, tuttavia almeno ciò che Babel fa con esso non è diverso dal soloaThing = possiblyNull.thing
ᆼ ᆺ ᆼ

1
@PeterVarga: Oops, hai ragione, la destrutturazione funziona quando la proprietà non esiste, ma non quando l'oggetto lo è null. Dovresti fornire un valore predefinito, più o meno come questo modello ma con una sintassi più confusa.
Bergi,

4

Andando dall'elenco qui , al momento non esiste alcuna proposta per aggiungere attraversamenti sicuri a Ecmascript. Quindi non solo non c'è un bel modo per farlo, ma non verrà aggiunto nel prossimo futuro.


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

Concatenamento opzionale ?. e coalescente nullo??

Ora puoi utilizzare direttamente ?.inline per testare in sicurezza l'esistenza. Tutti i browser moderni lo supportano.

?? può essere utilizzato per impostare un valore predefinito se non definito o nullo.

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

Se esiste una proprietà, ?.passa al controllo successivo o restituisce il valore valido. Qualsiasi guasto si interromperà immediatamente e ritornerà 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

Per garantire un valore predefinito definito, è possibile utilizzare ??. Se si richiede il primo valore di verità, è possibile utilizzare ||.

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

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

Se non si verifica un caso, la proprietà sul lato sinistro deve esistere. In caso contrario, genererà un'eccezione.

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

?.Supporto browser - 78%, luglio 2020

??Supporto browser - 78%

Documentazione Mozilla

-

Assegnazione nullo logica, soluzione 2020+

I nuovi operatori sono attualmente in fase aggiunti ai browser, ??=, ||=e &&=. Non fanno esattamente quello che stai cercando, ma potrebbero portare allo stesso risultato a seconda dell'obiettivo del tuo codice.

NOTA: Queste non sono comuni nelle versioni pubbliche del browser ancora , ma Babel dovrebbero transpile bene. Si aggiornerà come cambia la disponibilità.

??=controlla se il lato sinistro è indefinito o nullo, corto circuito se già definito. In caso contrario, al lato sinistro viene assegnato il valore del lato destro. ||=e &&=sono simili, ma basati sugli operatori ||e &&.

Esempi di base

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

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

Esempi di oggetti / array

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

Supporto browser luglio 2020 - .03%

Documentazione Mozilla


1
La domanda riguarda anche l'assegnazione condizionale
ᆼ ᆺ ᆼ

Aggiunti alcuni esempi con ?.e ??, e una soluzione imminente dettagliata che potrebbe funzionare per la tua situazione. La migliore soluzione attuale è probabilmente quella di fare semplicementeaThing = possiblyNull?.thing ?? aThing
Gibolt il

0

Un metodo deep get sicuro sembra un adattamento naturale per underscore.js ma il problema è evitare la programmazione di stringhe. Modifica della risposta di @ Felipe per evitare la programmazione di stringhe (o almeno rimanda i caratteri limite al chiamante):

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

Esempio:

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

Per parafrasare Rick: questo suona come una programmazione di stringhe con passaggi aggiuntivi.
tocqueville,

1
lodash ora implementa deep get / set _.get(obj, array_or_dotstring)
prototipo

1
Ma per essere onesti anche il punto Javascript e la notazione di accessor di stringa sono fondamentalmente programmazione di stringhe, obj.a.b.cvsobj['a']['b']['c']
prototipo

1
Da dove viene keys?
Levi Roberts,

-2

So che questa è una domanda JavaScript, ma penso che Ruby lo gestisca in tutti i modi richiesti, quindi penso che sia un punto di riferimento rilevante.

.&, trye && hanno i loro punti di forza e potenziali insidie. Un grande riassunto di queste opzioni qui: http://mitrev.net/ruby/2015/11/13/the-operator-in-ruby/

TLDR; La conclusione di Rubyists è che digè sia più facile per gli occhi sia una garanzia più forte che un valore o nullverrà assegnato.

Ecco una semplice implementazione in 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
}

Può essere utilizzato per qualsiasi profondità di annidamento e gestisce le funzioni.

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

L' tryapproccio è ugualmente piacevole da leggere in JS, come mostrato nelle risposte precedenti. Inoltre non richiede il looping, che è uno svantaggio di questa implementazione.


Ho inserito questa risposta qui come una soluzione divertente / completa / alternativa b / c Mi piace il modo in cui il rubino lo fa con lo zucchero sintattico. Mettilo qui per la community: vota ancora una volta e eliminerò felicemente questo post. Saluti!
theUtherSide

Davvero bello vedere ECMAScript2019 ha una grande proposta per il "Concatenamento opzionale" simile a Ruby: github.com/tc39/proposal-optional-chaining È disponibile oggi tramite Babel Plugin e funziona anche per chiamare metodi su un oggetto: babeljs.io / docs / it / babel-plugin-
request

-5

Ho pensato che questa domanda avesse bisogno di un po 'di aggiornamento per il 2018. Questo può essere fatto bene senza l'uso di alcuna libreria Object.defineProperty()e può essere usato come segue:

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

Lo considero sicuro (ed etico) a causa delle definizioni writeablee enumerableora disponibili per il definePropertymetodo di Object, come documentato in MDN

definizione della funzione di seguito:

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

Ho dimostrato un jsBin con l'output della console per dimostrarlo. Si noti che nella versione jsBin ho anche aggiunto un'eccezione personalizzata per valori vuoti. Questo è facoltativo, quindi l'ho lasciato fuori dalla definizione minima sopra.

I miglioramenti sono i benvenuti


2
Con questo stai di fatto scrivendo il codice in stringhe. È davvero una cattiva idea farlo. Rende impossibile il refactoring del tuo codice e non è IDE-friendly.
tocqueville,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.