7 conseils pour gérer undefined en JavaScript

La plupart des langages modernes comme Ruby, Python ou Java ont une seule valeur nulle (nil ou null), ce qui semble une approche raisonnable.

Mais JavaScript est différent.

null, mais aussi undefined, représentent en JavaScript des valeurs vides. Alors, quelle est la différence exacte entre eux?

La réponse courte est que linterpréteur JavaScript renvoie undefined lors de laccès à une variable ou une propriété dobjet qui nest pas encore initialisée. Par exemple:

De lautre côté, null représente un objet manquant référence. JavaScript ninitialise pas les variables ou les propriétés dobjet avec null.

Certaines méthodes natives comme String.prototype.match() peuvent renvoyer null pour désigner un objet manquant. Jetez un œil à lexemple:

Parce que JavaScript est permissif, les développeurs sont tentés daccéder à des valeurs non initialisées. Je suis également coupable d’une telle mauvaise pratique.

Ces actions risquées génèrent souvent des undefined erreurs liées:

  • TypeError: "undefined" is not a function
  • TypeError: Cannot read property "<prop-name>" of undefined
  • et les erreurs de type similaires.

Les développeurs JavaScript peuvent comprendre lironie de cette blague :

Pour réduire ces erreurs, vous devez comprendre les cas où undefined est généré. Explorons undefined et son effet sur la sécurité du code.

1. Ce qui nest pas défini

JavaScript a 6 types primitifs:

Et un type dobjet séparé: {name: "Dmitri"} , .

De 6 types primitifs undefined est une valeur spéciale avec son propre type Indéfini. Selon la spécification ECMAScript:

La valeur primitive de valeur non définie est utilisée lorsquune valeur na pas été affectée à une variable.

La norme définit clairement que vous recevrez undefined lors de laccès à des variables non initialisées, des propriétés dobjet non existantes, éléments de tableau non existants, et similaires.

Quelques exemples:

Lexemple ci-dessus montre que laccès à:

  • une variable non initialisée number
  • une propriété dobjet non existante movie.year
  • ou un élément de tableau inexistant movies

sont évalués à undefined.

La spécification ECMAScript définit le type de undefined valeur:

Le type non défini est un type dont la seule valeur est la valeur undefined.

En ce sens, typeof Lopérateur renvoie une chaîne "undefined" pour une valeur undefined:

Bien sûr, typeof fonctionne bien pour vérifier si une variable contient une valeur undefined:

2. Scénarios qui créent undefined

2.1 Variable non initialisée

Une variable déclarée mais non encore affectée dune valeur (non initialisée) est par défaut undefined.

Clair et simple:

myVariable est déclaré et pas encore affecté avec une valeur. Laccès à la variable équivaut à undefined.

Une approche efficace pour résoudre les problèmes de variables non initialisées consiste à attribuer chaque fois que possible une valeur initiale. Moins la variable existe dans un état non initialisé, mieux cest.

Idéalement, vous attribueriez une valeur immédiatement après la déclaration const myVariable = "Initial value". Mais ce n’est pas toujours possible.

Astuce 1: Favorisez const, sinon utilisez let, mais dites au revoir à var

À mon avis, lune des meilleures fonctionnalités dECMAScript 2015 est la nouvelle façon de déclarer des variables en utilisant const et let. Cest un grand pas en avant.

const et let ont une portée de bloc (contrairement à lancienne fonction de portée var) et existent dans une zone morte temporelle jusquà la ligne de déclaration.

Je recommande la variable const lorsque sa valeur ne va pas changer. Cela crée une liaison immuable.

Une des fonctionnalités intéressantes de const est que vous devez attribuer une valeur initiale à la variable const myVariable = "initial". La variable nest pas exposée à létat non initialisé et laccès à undefined est impossible.

Vérifions la fonction qui vérifie si un mot est un palindrome:

length et half reçoivent une valeur une fois. Il semble raisonnable de les déclarer comme const car ces variables ne vont pas changer.

Utilisez la déclaration let pour les variables dont la valeur peut changer. Chaque fois que possible, attribuez immédiatement une valeur initiale, par ex. let index = 0.

Et la vieille école var? Ma suggestion est darrêter de lutiliser.

var problème de déclaration est la variable soulevée dans la portée de la fonction. Vous pouvez déclarer une variable var quelque part à la fin de la portée de la fonction, mais vous pouvez toujours y accéder avant la déclaration: et vous obtiendrez un undefined.

myVariable est accessible et contient undefined avant même la ligne de déclaration: var myVariable = "Initial value".

Au contraire, une variable const ou let nest pas accessible avant la ligne de déclaration – la variable se trouve dans une zone morte temporelle avant la déclaration. Et cest bien car vous avez moins de chance daccéder à un undefined.

Lexemple ci-dessus mis à jour avec let (à la place de var) lance un ReferenceError car la variable dans la zone morte temporelle nest pas accessible.

Encourager lutilisation de const pour les liaisons immuables ou let garantit autrement une pratique qui réduit lapparence des non initialisés variable.

Astuce 2: Augmenter la cohésion

La cohésion caractérise le degré dappartenance des éléments dun module (espace de nom, classe, méthode, bloc de code). La cohésion peut être élevée ou faible.

Un module à haute cohésion est préférable car les éléments dun tel module se concentrent uniquement sur une seule tâche. Cela rend le module:

  • Focalisé et compréhensible: plus facile à comprendre ce que fait le module
  • Maintenable et plus facile à refactoriser: le changement dans le module affecte moins de modules
  • Réutilisable: étant concentré sur une seule tâche, il facilite la réutilisation du module
  • Testable: vous testeriez plus facilement un module axé sur une seule tâche

Une cohésion élevée accompagnée dun couplage lâche est la caractéristique dun système bien conçu.

Un bloc de code peut être considéré comme un petit module. Pour profiter des avantages dune cohésion élevée, gardez les variables aussi proches que possible du bloc de code qui les utilise.

Par exemple, si une variable existe uniquement pour former la logique de la portée du bloc, alors déclarez et rendez la variable active uniquement dans ce bloc (en utilisant const ou let déclarations). Nexposez pas cette variable à la portée du bloc externe, car le bloc externe ne devrait pas se soucier de cette variable.

Un exemple classique de la durée de vie inutilement prolongée des variables est lutilisation de for cycle à lintérieur dune fonction:

index, item et length les variables sont déclarées au début du corps de la fonction. Cependant, ils ne sont utilisés que vers la fin. Quel est le problème avec cette approche?

Entre la déclaration en haut et lutilisation dans linstruction for les variables index, item ne sont pas initialisés et exposés à undefined. Ils ont un cycle de vie déraisonnablement long dans toute la portée de la fonction.

Une meilleure approche consiste à déplacer ces variables aussi près que possible de leur lieu dutilisation:

index et existent uniquement dans la portée de bloc de linstruction for. Ils nont aucune signification en dehors de for.
length la variable est également déclarée proche de la source de son utilisation.

Pourquoi la version modifiée est-elle meilleure que la version initiale? Voyons voir:

  • Les variables ne sont pas exposées à létat non initialisé, vous navez donc aucun risque daccéder à undefined
  • Déplacer le des variables aussi proches que possible de leur lieu dutilisation augmente la lisibilité du code
  • Les morceaux de code hautement cohésifs sont plus faciles à refactoriser et à extraire dans des fonctions séparées, si nécessaire

2.2 Accès une propriété inexistante

Lors de laccès à une propriété dobjet inexistante, JavaScript renvoie undefined.

Démontrons cela dans un exemple:

favoriteMovie est un objet avec une seule propriété title. Laccès à une propriété inexistante actors à laide dun accesseur de propriété favoriteMovie.actors équivaut à undefined.

Laccès à une propriété non existante ne génère pas derreur. Le problème apparaît lorsque vous essayez dobtenir des données de la propriété inexistante, qui est le piège le plus courant undefined, reflété dans le message derreur bien connu TypeError: Cannot read property <prop> of undefined.

Modifions légèrement lextrait de code précédent pour illustrer un TypeError lancer:

favoriteMovie na pas la propriété actors, donc favoriteMovie.actors donne la valeur undefined.

Par conséquent, accéder au premier élément dune valeur undefined à laide de lexpression favoriteMovie.actors génère un TypeError.

Le caractère permissif de JavaScript qui permet daccéder à des propriétés non existantes est une source de non-déterminisme: la propriété peut être définie ou non. Le bon moyen de contourner ce problème est de restreindre lobjet à avoir toujours défini les propriétés quil détient.

Malheureusement, vous navez souvent pas de contrôle sur les objets. Ces objets peuvent avoir un ensemble de propriétés différent dans divers scénarios. Vous devez donc gérer tous ces scénarios manuellement.

Implémentons une fonction append(array, toAppend) qui ajoute au début et / ou à la fin dun tableau de nouveaux éléments. Le paramètre toAppend accepte un objet avec les propriétés:

  • first: élément inséré au début de array
  • last: élément inséré à la fin de array.

La fonction renvoie une nouvelle instance de tableau, sans altérer le tableau dorigine.

La première version de append(), un peu naïve, peut ressembler à ceci:

Parce que toAppend peut omettre les propriétés first ou last, il est obligatoire de vérifier si ces propriétés existent dans toAppend.

Un accesseur de propriété évalue à undefined si la propriété nexiste pas. La première tentation de vérifier si les propriétés first ou last sont présentes est de les vérifier par rapport à undefined. Ceci est effectué dans les conditions if(toAppend.first){} et if(toAppend.last){}

Pas si vite. Cette approche présente un inconvénient. undefined, ainsi que false, null, 0, NaN et "" sont des valeurs fausses.

Dans limplémentation actuelle de append(), la fonction ne permet pas dinsérer des éléments faux:

Les conseils qui suivent expliquent comment vérifier correctement lexistence de la propriété.

Astuce 3: Vérifier lexistence de la propriété

Heureusement, JavaScript propose un tas de façons de déterminer si lobjet a une propriété spécifique:

  • obj.prop !== undefined: comparer avec undefined directement
  • typeof obj.prop !== "undefined": vérifiez le type de valeur de la propriété
  • obj.hasOwnProperty("prop"): vérifiez si le lobjet a sa propre propriété
  • "prop" in obj: vérifier si lobjet a une propriété propre ou héritée

Ma recommandation est de utilisez lopérateur in. Il a une syntaxe courte et douce. La présence de lopérateur in suggère une intention claire de vérifier si un objet a une propriété spécifique, sans accéder à la valeur de propriété réelle.

obj.hasOwnProperty("prop") est également une bonne solution. Il est légèrement plus long que lopérateur in et ne vérifie que dans les propres propriétés de lobjet.

Améliorons la fonction append(array, toAppend) en utilisant lopérateur in:

"first" in toAppend (et "last" in toAppend) est true si la propriété correspondante existe, false sinon.

in résout le problème dinsertion déléments faux 0 et false. Maintenant, lajout de ces éléments au début et à la fin de produit le résultat attendu .

Astuce 4: Destructuration pour accéder aux propriétés dun objet

Lors de laccès à une propriété dobjet, il est parfois nécessaire de définir une valeur par défaut si la propriété nexiste pas.

Vous pouvez utiliser in accompagné dun opérateur ternaire pour accomplir cela:

La syntaxe des opérateurs ternaires devient décourageante lorsque le nombre de propriétés à vérifier augmente. Pour chaque propriété, vous devez créer une nouvelle ligne de code pour gérer les valeurs par défaut, augmentant ainsi un affreux mur dopérateurs ternaires dapparence similaire.

Pour utiliser une approche plus élégante, familiarisons-nous avec une excellente fonctionnalité ES2015 appelée déstructuration dobjets.

La déstructuration dobjet permet lextraction en ligne des valeurs de propriété dobjet directement dans des variables et la définition dune valeur par défaut si la propriété nexiste pas. Une syntaxe pratique pour éviter de traiter directement avec undefined.

En effet, lextraction de propriété est désormais précise:

Pour voir les choses en action, définissons une fonction utile qui enveloppe une chaîne entre guillemets.

quote(subject, config) accepte le premier argument comme chaîne à encapsuler. Le deuxième argument config est un objet avec les propriétés:

En appliquant les avantages de la déstructuration dobjets, implémentons quote():

const { char = """, skipIfQuoted = true } = config laffectation de déstructuration en une ligne extrait les propriétés char et skipIfQuoted de lobjet config.
Si certaines propriétés manquent dans lobjet config, laffectation de déstructuration définit les valeurs par défaut : """ pour char et false pour skipIfQuoted.

Heureusement, la fonction peut encore être améliorée.

Déplaçons laffectation de déstructuration dans la section des paramètres. Et définissez une valeur par défaut (un objet vide { }) pour le paramètre config, pour ignorer le deuxième argument lorsque les paramètres par défaut sont suffisants.

Laffectation de déstructuration remplace le paramètre config dans la signature de la fonction. Jaime ça: quote() devient une ligne plus courte.

= {} sur le côté droit de laffectation de déstructuration garantit quun objet vide est utilisé si le deuxième argument nest pas du tout spécifié quote("Sunny day").

La déstructuration dobjets est une fonctionnalité puissante qui gère efficacement lextraction des propriétés des objets. Jaime la possibilité de spécifier une valeur par défaut à renvoyer lorsque la propriété accédée nexiste pas. En conséquence, vous évitez undefined et les tracas qui lentourent.

Astuce 5: Remplissez lobjet avec les propriétés par défaut

Sil nest pas nécessaire de créer des variables pour chaque propriété, comme le fait laffectation de déstructuration, lobjet qui manque certaines propriétés peut être rempli avec des valeurs par défaut.

LES2015 Object.assign(target, source1, source2, ...) copie les valeurs de toutes les propriétés propres énumérables dun ou plusieurs objets source dans lobjet cible. La fonction renvoie lobjet cible.

Par exemple, vous devez accéder aux propriétés de lobjet unsafeOptions qui ne contient pas toujours son ensemble complet de propriétés.

Pour éviter undefined lors de laccès à une propriété inexistante depuis unsafeOptions, faisons quelques ajustements:

  • Définissez un objet defaults contenant les valeurs de propriété par défaut
  • Appelez Object.assign({ }, defaults, unsafeOptions) pour créer un nouvel objet options. Le nouvel objet reçoit toutes les propriétés de unsafeOptions, mais celles manquantes sont extraites de defaults.

unsafeOptions contient uniquement la propriété fontSize. Lobjet defaults définit les valeurs par défaut des propriétés fontSize et color.

Object.assign() prend le premier argument comme objet cible {}. Lobjet cible reçoit la valeur de la propriété fontSize de lobjet source unsafeOptions. Et la valeur de la propriété color de defaults objet source, car unsafeOptions ne contient pas color.

Lordre dans lequel les objets source sont énumérés importe: les propriétés des objets source ultérieurs écrasent les précédentes.

Vous pouvez désormais accéder en toute sécurité à toute propriété de lobjet options, y compris options.color qui nétait pas disponible dans unsafeOptions initialement.

Heureusement, il existe une alternative plus simple pour remplir lobjet avec les propriétés par défaut.Je recommande dutiliser les propriétés de propagation dans les initialiseurs dobjets.

Au lieu de linvocation Object.assign(), utilisez la syntaxe de propagation dobjet pour copier dans lobjet cible toutes les propriétés propres et énumérables des objets source:

Le linitialiseur dobjet répartit les propriétés des objets source defaults et unsafeOptions. Lordre dans lequel les objets source sont spécifiés est important: les propriétés dobjet source ultérieures écrasent les précédentes.

Remplir un objet incomplet avec des valeurs de propriété par défaut est une stratégie efficace pour rendre votre code sûr et durable. Quelle que soit la situation, lobjet contient toujours lensemble complet des propriétés: et undefined ne peut pas être généré.

Astuce bonus: coalescence nullish

Lopérateur nullish coalescing prend une valeur par défaut lorsque son opérande est undefined ou null:

Lopérateur de fusion nul est pratique pour accéder à une propriété dobjet tout en ayant une valeur par défaut lorsque cette propriété est undefined ou null:

styles lobjet na pas la propriété color, donc styles.color Laccesseur de propriété est undefined. styles.color ?? "black" prend la valeur par défaut "black".

styles.fontSize est 18, donc lopérateur de fusion nullish évalue à la valeur de propriété 18.

2.3 Paramètres de fonction

Les paramètres de fonction implicitement par défaut sont undefined.

En général, une fonction définie avec un nombre spécifique de paramètres doit être appelée avec le même nombre darguments. Cest à ce moment que les paramètres obtiennent les valeurs attendues:

Quand multiply(5, 3), les paramètres a et b reçoivent 5 et respectivement 3 valeurs. La multiplication est calculée comme prévu: 5 * 3 = 15.

Que se passe-t-il lorsque vous omettez un argument lors de linvocation? Le paramètre correspondant à lintérieur de la fonction devient undefined.

Modifions légèrement lexemple précédent en appelant la fonction avec un seul argument:

Lappel multiply(5) est effectué avec un seul argument: comme résultat, le paramètre a est 5, mais le paramètre Le paramètre b est undefined.

Astuce 6: Utilisez la valeur de paramètre par défaut

Parfois, une fonction ne nécessite pas lensemble complet darguments lors de linvocation. Vous pouvez définir des valeurs par défaut pour les paramètres qui n’ont pas de valeur.

En rappelant lexemple précédent, apportons une amélioration. Si le paramètre b est undefined, laissez-le par défaut sur 2:

La fonction est appelée avec un seul argument multiply(5). Initialement, le paramètre a est 2 et b est undefined.
Linstruction conditionnelle vérifie si b est undefined. Si cela se produit, laffectation b = 2 définit une valeur par défaut.

Bien que la manière fournie dattribuer des valeurs par défaut fonctionne, je ne recommande pas de comparer directement avec undefined. Il est verbeux et ressemble à un hack.

Une meilleure approche consiste à utiliser la fonctionnalité de paramètres par défaut de lES2015. Cest court, expressif et pas de comparaison directe avec undefined.

Lajout dune valeur par défaut au paramètre b = 2 semble mieux:

b = 2 dans la signature de la fonction sassure que si b est undefined, le paramètre par défaut est 2.

La fonctionnalité des paramètres par défaut de lES2015 est intuitive et expressive. Utilisez-le toujours pour définir les valeurs par défaut des paramètres facultatifs.

2.4 Valeur de retour de la fonction

Implicitement, sans instruction return, une fonction JavaScript renvoie undefined.

Une fonction qui na pas return renvoie implicitement undefined:

square() ne renvoie aucun résultat de calcul. Le résultat de lappel de fonction est undefined.

La même situation se produit lorsque linstruction return est présente, mais sans expression à proximité:

return; est exécutée, mais elle ne renvoie aucune expression. Le résultat de lappel est également undefined.

Bien sûr, indiquer à proximité de return lexpression à renvoyer fonctionne comme prévu:

Linvocation de la fonction est maintenant évaluée à 4, qui est 2 au carré.

Astuce 7: Ne faites pas confiance à linsertion automatique de points-virgules

La liste dinstructions JavaScript suivante doit se terminer par des points-virgules (;) :

  • instruction vide
  • let, const, var, import, export déclarations
  • déclaration dexpression
  • debugger instruction
  • continue instruction, break instruction
  • throw instruction
  • return instruction

Si vous utilisez lune des instructions ci-dessus, assurez-vous dindiquer un point-virgule à la fin:

À la fin des deux let déclaration et return déclaration un point-virgule obligatoire est écrit.

Que se passe-t-il lorsque vous ne voulez pas les indiquer des points-virgules? Dans une telle situation, ECMAScript fournit un mécanisme dinsertion automatique de point-virgule (ASI), qui insère pour vous les points-virgules manquants.

Avec laide dASI, vous pouvez supprimer les points-virgules de lexemple précédent:

Le texte ci-dessus est un code JavaScript valide. Les points-virgules manquants sont automatiquement insérés pour vous.

À première vue, cela semble assez prometteur. Le mécanisme ASI vous permet de sauter les points-virgules inutiles. Vous pouvez rendre le code JavaScript plus petit et plus facile à lire.

Il existe un petit mais ennuyeux piège créé par ASI. Lorsquune nouvelle ligne se situe entre return et lexpression renvoyée return \n expression, ASI insère automatiquement un point-virgule avant la nouvelle ligne return; \n expression.

Quest-ce que cela signifie dans une fonction davoir une instruction return;? La fonction renvoie undefined. Si vous ne connaissez pas en détail le mécanisme d’ASI, le undefined renvoyé de manière inattendue est trompeur.

Par exemple, étudions la valeur renvoyée de linvocation getPrimeNumbers():

Entre linstruction return et lexpression littérale du tableau existe une nouvelle ligne. JavaScript insère automatiquement un point-virgule après return, interprétant le code comme suit:

Linstruction return; fait que la fonction getPrimeNumbers() renvoie undefined au lieu du tableau attendu.

Le problème est résolu en supprimant la nouvelle ligne entre return et le littéral du tableau:

Ma recommandation est détudier comment exactement linsertion automatique de points-virgules fonctionne pour éviter de telles situations.

Bien sûr, ne mettez jamais de nouvelle ligne entre return et lexpression renvoyée.

Opérateur 2.5 void

void <expression> évalue lexpression et renvoie undefined quel que soit le résultat de l’évaluation.

Un cas dutilisation de lopérateur void est de supprimer lévaluation de lexpression sur undefined, en se basant sur certains effets secondaires de lévaluation.

3. non défini dans les tableaux

Vous obtenez undefined lors de laccès à un élément de tableau avec un index hors limites.

colors tableau a 3 éléments, donc les index valides sont 0, 1 et 2.

Comme il ny a pas déléments de tableau aux index 5 et -1, les accesseurs colors et colors sont undefined.

En JavaScript, vous pouvez rencontrer des tableaux dits clairsemés. Les thèses sont des tableaux qui ont des lacunes, cest-à-dire quà certains index, aucun élément nest défini.

Lorsquun espace (aka espace vide) est accédé à lintérieur dun tableau épars, vous obtenez également un undefined.

Lexemple suivant génère des tableaux épars et tente daccéder à leurs emplacements vides:

sparse1 est créé en appelant un Array constructeur avec un premier argument numérique.Il a 3 emplacements vides.

sparse2 est créé avec un littéral de tableau avec le deuxième élément manquant.

Dans nimporte lequel de ces tableaux épars, accéder à un emplacement vide équivaut à undefined.

Lorsque vous travaillez avec des tableaux, pour éviter undefined, assurez-vous dutiliser des index de tableau valides et empêchez la création de tableaux épars.

4. Différence entre undefined et null

Quelle est la principale différence entre undefined et null? Les deux valeurs spéciales impliquent un état vide.

undefined représente la valeur dune variable qui na pas encore été initialisée, tandis que null représente une absence intentionnelle dun objet.

Explorons la différence dans quelques exemples.

La variable number est définie , cependant, na pas de valeur initiale:

number variable est undefined, ce qui indique une variable non initialisée.

Le même concept non initialisé se produit lors de laccès à une propriété dobjet non existante:

Parce que lastName la propriété nexiste pas dans obj, JavaScript évalue obj.lastName à undefined.

De lautre côté, vous savez quune variable attend un objet. Mais pour une raison quelconque, vous ne pouvez pas instancier l’objet. Dans ce cas, null est un indicateur significatif dun objet manquant.

Par exemple, clone() est une fonction qui clone un objet JavaScript simple. La fonction devrait renvoyer un objet:

Cependant, clone() peut être invoqué avec un argument non-objet: 15 ou null. Dans un tel cas, la fonction ne peut pas créer de clone, elle renvoie donc null – lindicateur dun objet manquant.

typeof fait la distinction entre undefined et null:

De plus, lopérateur de qualité stricte === différencie correctement undefined de null:

5. Conclusion

undefined lexistence est une conséquence de la nature permissive de JavaScript qui permet lutilisation de:

  • variables non initialisées
  • propriétés ou méthodes dobjet non existantes
  • index hors limites pour accéder aux éléments du tableau
  • le résultat de lappel dune fonction qui ne renvoie rien

Comparer directement avec undefined nest pas sûr car vous vous fiez à une pratique autorisée mais découragée mentionnée ci-dessus.

Une stratégie efficace consiste à réduire au minimum lapparence du mot clé undefined dans votre code en appliquant de bonnes habitudes telles que:

  • réduire lutilisation de variables non initialisées
  • rendre le cycle de vie des variables court et proche de la source de leur utilisation
  • chaque fois que possible attribuer des valeurs initiales aux variables
  • favor const, sinon utilisez let
  • utilisez les valeurs par défaut pour les paramètres de fonction insignifiants
  • vérifiez les propriétés ou remplir les objets non sécurisés avec les propriétés par défaut
  • éviter l’utilisation de tableaux épars

Est-il bon que JavaScript ait les deux undefined et null pour représenter des valeurs vides?

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *