Accès aux propriétés de sécurité nulle (et affectation conditionnelle) dans ES6 / 2015

150

Existe-t-il un nullopérateur d'accès aux propriétés -safe (propagation / existence nulles) dans ES6 (ES2015 / JavaScript.next / Harmony) comme ?.dans CoffeeScript par exemple? Ou est-ce prévu pour ES7?

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

Ce sera à peu près comme:

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

Idéalement, la solution ne doit pas attribuer (même undefined) à aThingif possiblyNullisnull

ᆼ ᆺ ᆼ
la source
3
@naomik Ce type de vérification de null peut être très utile pour les instructions if où vous recherchez une propriété profondément imbriquée, par exemple if( obj?.nested?.property?.value )au lieu deif( obj && obj.nested && obj.nested.property && obj.nested.property.value )
Sean Walsh
@SeanWalsh si vos objets sont si profondément imbriqués ou si vos fonctions creusent aussi profondément dans vos objets, il y a probablement plusieurs autres problèmes avec votre application.
Merci
1
comparer var appConfig = loadConfig(config, process.env); connect(appConfig.database);à connect(config). Vous pouvez passer un objet beaucoup plus simple à la connectplace de passer l'ensemble configobjet, vous pouvez utiliser conf.username, au conf.passwordlieu d'essayer quelque chose comme config[process.env]?.database?.username, config[process.env]?.database?.password. Référence: Law of Demeter .
Merci
De plus, si vous faites quelque chose comme définir les valeurs par défaut ou assainir les propriétés (cela pourrait être fait dans loadConfigl'exemple ci-dessus), vous pouvez faire des hypothèses sur l'existence de propriétés et ignorer la vérification des valeurs nulles dans d'innombrables zones de votre application.
Merci
4
@naomik Tant que le langage prend en charge l'imbrication d'objets, c'est toujours une fonctionnalité utile - indépendamment de ce que vous ou moi pensez de l'architecture de l'application elle-même. En passant, les graphiques d'objets complexes comme celui-ci sont très courants dans les ORM qui modélisent un modèle de données complexe.
Sean Walsh

Réponses:

100

Mise à jour (2020-01-31): Il semble que les gens trouvent toujours ceci, voici l'histoire actuelle:

Mise à jour (01/08/2017): Si vous souhaitez utiliser un plugin officiel, vous pouvez essayer la version alpha de Babel 7 avec la nouvelle transformation. Votre kilométrage peut varier

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

Original :

Une fonctionnalité qui accomplit qui est actuellement à l'étape 1: Chaînage facultatif.

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

Si vous souhaitez l'utiliser aujourd'hui, il existe un plugin Babel qui accomplit cela.

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

jours de base
la source
Dois-je bien comprendre que cela n'inclut pas l'attribution conditionnelle? street = user.address?.streetserait mis streeten tout cas?
ᆼ ᆺ ᆼ
1
En fait, malheureusement, je pense que vous avez raison. Street Je pense qu'il serait attribué undefined. Mais au moins, cela ne reviendrait pas à essayer d'accéder aux propriétés sur undefined.
jours de base
2
Il semble également que vous puissiez utiliser l'opérateur de la main gauche, et s'il évalue quelque chose à indéfini, la main droite n'est pas évaluée. Le plugin babel peut varier un peu, je ne l'ai pas encore beaucoup testé moi-même.
jours de base du
1
En ce qui concerne l'affectation conditionnelle sur le côté gauche du =, il semble que cela ne soit pas pris en charge dans la spécification officielle actuellement. github.com/tc39/proposal-optional-chaining#not-supported
basicdays
1
En mai 2020, il semble que les navigateurs actuels et Typescript l'ont implémenté!
Josh Diehl
66

Ce n'est pas aussi beau que le?. opérateur, mais pour obtenir un résultat similaire, vous pouvez faire:

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

Puisque nullet undefinedsont tous les deux des valeurs fausses ( voir cette référence ), la propriété après l' &&opérateur n'est accessible que si le précédent n'est pas nul ou indéfini.

Vous pouvez également écrire une fonction comme celle-ci:

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

Usage:

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

Ou, avec une valeur de repli:

_try(() => user.address.postcode, "none") // return postcode or a custom string
tocqueville
la source
Je devrais probablement clarifier la question, la principale chose que je recherchais était l'affectation conditionnelle
ᆼ ᆺ ᆼ
juste un corollaire à cela; pour trouver l'inverse en toute sécurité, vous pouvez utiliser !(user && user.address && user.address.postcode) :)
rob2d
foo && foo.bar && foo.bar.quux ...dans une grande base de code, c'est moche et ajoute beaucoup de complexité qu'il vaut mieux éviter.
Skylar Saveland
1
C'est la solution la plus propre que j'ai trouvée sur Internet. J'utilise ceci avec dactylographié:_get<T>(func: () => T, fallbackValue?: T) :T
tomwassing
3
Si user.address.postcoden'est pas défini, _try(() => user.address.postcode, "")retournera à la undefinedplace de "". Le code _try(() => user.address.postcode, "").lengthlèvera donc une exception.
Alexander Chen
33

Non. Vous pouvez utiliser lodash # get ou quelque chose comme ça pour cela en JavaScript.

Girafa
la source
4
Y a-t-il des propositions pour l'ajouter à ES7?
ᆼ ᆺ ᆼ
1
Je n'en ai rencontré aucun.
Girafa
3
@PeterVarga: Beaucoup. Trop de discussions. La grammaire n'est pas une chose facile pour cette fonctionnalité
Bergi
1
lodash.getest légèrement différent en ce qu'il ne peut évidemment pas faire d'assignation conditionnelle.
ᆼ ᆺ ᆼ
17

Alternative à la vanille pour un accès sécurisé à la propriété

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

L'affectation conditionnelle la plus concise serait probablement celle-ci

try { b = a.b.c.d.e } catch(e) {}
yagger
la source
Alors, comment écririez-vous le devoir dans la question en utilisant ce mécanisme? N'avez-vous pas encore besoin d'un conditionnel pour ne pas attribuer au cas où le possiblyNulln'est pas défini / null?
ᆼ ᆺ ᆼ
Bien sûr, vous devez toujours vérifier si vous souhaitez qu'une mission ait lieu ou non. Si vous utilisez l'opérateur '=', quelque chose sera inévitablement assigné, que ce soit une donnée, un indéfini ou nul. Donc, ce qui précède n'est qu'un accès sécurisé à la propriété. L'opérateur d'affectation conditionnelle existe-t-il même, dans n'importe quelle langue?
yagger
Bien sûr, par exemple CoffeeScript , il a également||=
ᆼ ᆺ ᆼ
+1 Même ma première pensée fut (((a.b || {}).c || {}).d || {}).e. Mais plus précis ce serait((((a || {}).b || {}).c || {}).d || {}).e
IsmailS
6

Non, il n'y a pas d'opérateur de propagation nul dans ES6. Vous devrez suivre l'un des modèles connus .

Vous pouvez cependant utiliser la déstructuration:

({thing: aThing} = possiblyNull);

Il y a beaucoup de discussions (par exemple ceci ) pour ajouter un tel opérateur dans ES7, mais aucune n'a vraiment décollé.

Bergi
la source
Cela semblait prometteur, mais au moins ce que Babel en fait n'est pas différent de justeaThing = possiblyNull.thing
ᆼ ᆺ ᆼ
1
@PeterVarga: Oups, vous avez raison, la déstructuration fonctionne lorsque la propriété n'existe pas, mais pas lorsque l'objet existe null. Vous devrez fournir une valeur par défaut, à peu près comme ce modèle mais avec une syntaxe plus déroutante.
Bergi
4

En parcourant la liste ici , il n'y a actuellement aucune proposition pour ajouter une traversée sûre à Ecmascript. Donc, non seulement il n'y a pas de bon moyen de le faire, mais cela ne sera pas ajouté dans un avenir prévisible.

Antimoine
la source
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);
Gary Fu
la source
1

Chaînage ?.et coalescence nul en option??

Vous pouvez maintenant utiliser directement en ?.ligne pour tester en toute sécurité l'existence. Tous les navigateurs modernes le prennent en charge.

?? peut être utilisé pour définir une valeur par défaut si non définie ou nulle.

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

Si une propriété existe, ?.passe à la vérification suivante ou renvoie la valeur valide. Toute panne sera immédiatement court-circuitée et retournée 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

Pour garantir une valeur définie par défaut, vous pouvez utiliser ??. Si vous avez besoin de la première valeur de vérité, vous pouvez utiliser ||.

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

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

Si vous ne cochez pas de cas, la propriété de gauche doit exister. Sinon, il lèvera une exception.

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

?.Support du navigateur - 78%, juillet 2020

??Prise en charge du navigateur - 78%

Documentation Mozilla

-

Affectation logique nulle, solution 2020+

De nouveaux opérateurs sont actuellement ajoutés aux navigateurs ??=, ||=et &&=. Ils ne font pas tout à fait ce que vous recherchez, mais pourraient conduire au même résultat en fonction de l'objectif de votre code.

NOTE: Ce ne sont pas communs dans les versions de navigateur publics encore , mais Babel devraient transpile bien. Sera mis à jour à mesure que la disponibilité change.

??=vérifie si le côté gauche est indéfini ou nul, court-circuit s'il est déjà défini. Sinon, le côté gauche reçoit la valeur du côté droit. ||=et &&=sont similaires, mais basés sur les opérateurs ||et &&.

Exemples de base

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

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

Exemples d'objets / de tableaux

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

Browser Support Juillet 2020 - .03%

Documentation Mozilla

Gibolt
la source
1
La question concerne également l'assignation conditionnelle
ᆼ ᆺ ᆼ
Ajout de quelques exemples avec ?.et ??, et d'une solution à venir détaillée qui peut fonctionner pour votre situation. La meilleure solution actuelle est probablement de simplement faireaThing = possiblyNull?.thing ?? aThing
Gibolt
0

Une méthode sécurisée de deep get semble être un ajustement naturel pour underscore.js mais là, le problème est d'éviter la programmation de chaînes. Modifier la réponse de @ Felipe pour éviter la programmation de chaînes (ou au moins repousser les cas de bord vers l'appelant):

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

Exemple:

var test = { 
  a: { 
    b: 'b property value',
    c: { }
  } 
}
safeGet(test, ['a', 'b']) 
safeGet(test, "a.b".split('.'))  
prototype
la source
Pour paraphraser Rick: cela ressemble à une programmation de cordes avec des étapes supplémentaires.
tocqueville
1
lodash implémente maintenant deep get / set _.get(obj, array_or_dotstring)
prototype
1
Mais pour être juste, même la notation des accesseurs de points et de chaînes Javascript est essentiellement une programmation de chaînes, obj.a.b.cvsobj['a']['b']['c']
prototype
1
D'où keysvient-il?
Levi Roberts
-2

Je sais que c'est une question JavaScript, mais je pense que Ruby gère cela de toutes les manières demandées, donc je pense que c'est un point de référence pertinent.

.&, tryEt && ont leurs points forts et les pièges potentiels. Un bon aperçu de ces options ici: http://mitrev.net/ruby/2015/11/13/the-operator-in-ruby/

TLDR; La conclusion des rubisistes digest à la fois plus facile pour les yeux et une garantie plus forte qu'une valeur ou nullsera attribuée.

Voici une implémentation simple dans 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
}

Cela peut être utilisé pour n'importe quelle profondeur d'imbrication et gère les fonctions.

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

L' tryapproche est également agréable à lire dans JS, comme indiqué dans les réponses précédentes. Il ne nécessite pas non plus de bouclage, ce qui est un inconvénient de cette implémentation.

theUtherSide
la source
Je mets cette réponse ici comme une solution amusante / complète / alternative b / c J'aime la façon dont ruby ​​le fait avec le sucre syntaxique. Juste mettre ceci là-bas pour la communauté - Votez une fois de plus et je serai heureux de supprimer ce message. À votre santé!
theUtherSide
Vraiment cool de voir ECMAScript2019 a une excellente proposition pour "Chaînage facultatif" de type Ruby: github.com/tc39/proposal-optional-chaining Il est disponible aujourd'hui via Babel Plugin, et il fonctionne également pour appeler des méthodes sur un objet: babeljs.io / docs / fr / babel-plugin-proposal-optional-chaining
theUtherSide
-5

Je pensais que cette question avait besoin d'un peu de rafraîchissement pour 2018. Cela peut être fait bien sans aucune bibliothèque Object.defineProperty()et peut être utilisé comme suit:

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

Je considère cela comme sûr (et js-éthique) en raison des définitions writeableet des enumerabledéfinitions maintenant disponibles pour la definePropertyméthode de Object, comme documenté dans MDN

définition de fonction ci-dessous:

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

J'ai mis en place un jsBin avec une sortie de console pour le démontrer. Notez que dans la version jsBin, j'ai également ajouté une exception personnalisée pour les valeurs vides. Ceci est facultatif, et je l'ai donc laissé en dehors de la définition minimale ci-dessus.

Les améliorations sont les bienvenues

Felipe
la source
2
Avec cela, vous écrivez de facto du code en chaînes. C'est vraiment une mauvaise idée de faire ça. Cela rend votre code impossible à refactoriser et non compatible avec l'IDE.
tocqueville