Est-il si null cet undefined ?

Si vous faites du JavaScript régulièrement —et il est de plus en plus difficile d'y couper si vous êtes développeur web—, vous vous êtes peut-être déjà posé cette question : « Quelle est la différence entre la valeur null et la valeur undefined ? ». Question qu'on ne se pose pas quand on vient d'un autre langage. Null vs Undefined

Si nous résumons ensemble nous pouvons tomber d'accord sur le fait que :

  • Null et Undefined sont tous les deux des types ne possédant qu'une seule valeur ; les constantes respectives null et undefined.
  • Ils s'utilisent tous les deux pour indiquer « l'absence ».
  • Ils sont tous les deux évalués à false dans un contexte booléen (ex. : dans une structure de contrôle conditionnelle if, avec l'opérateur binaire d'égalité a == b, avec l'opérateur ternaire conditionnel a ? b : c, etc.).

En gros, ils servent donc à la même chose non ? Cette réponse ne me convient pas car, même s'il est vrai qu'à chaque première vu les comportements JavaScript semblent « loufoque », l'expérience m'a toujours montré que « c'était moi » qui n'avait pas compris correctement le JavaScript. Si les deux types existes, il y a probablement une distinction. Voici ce qui en est concrètement dit :

La valeur undefined appartient au type primitif Undefined et est utilisée quand aucune valeur typée n'a été assignée à une variable.

La valeur null appartient au type primitif Null et représente l'absence de valeur, la non-existence d'une référence dans une variable.

La distinction est plus clair ainsi non ? Pas vraiment... On va retrousser nos manches et regarder un peu ce qu'on peut éclaircir. Si vous n'avez pas le temps de suivre l'explication exhaustive (et intéressante je l'espère) qui va suivre, je vous laisse filer à la conclusion qui répond à cette question. Si par contre vous êtes curieux et/ou avez le temps de comprendre les différences, c'est juste en dessous !

Faible mais tout de même dynamique

Pour répondre à notre question, nous allons faire un petit tour d'horizon du typage en JavaScript pour qu'il soit plus clair et simple ensuite d'aborder la question principale.

Typage faible

Les développeurs ayant une grosse dent contre JavaScript l'on souvent car c'est un langage que les moins intéressés qualifieront de non typé et que les plus avertis qualifieront de langage à typage faible (par opposition aux langages à typage fort). Cela signifie que vous ne verrez jamais clairement écrit devant une variable qu'elle est de type int num = 45 ou bool bool = false ou String str = "Hello World", mais seulement qu'elle est var num = 45 ou var bool = false ou var str = "Hello World".

Typage dynamique

En fait en JavaScript, les variables n'ont pas de type : ce sont les opérandes ou les expressions (composition d'opérandes et d'opérateurs) qui sont typées (qui renvoi un type à l'exécution). On peut grossièrement dire qu'en JavaStript num, bool et str sont des conteneurs qui contiennent un élément typé. Cela permet a une variable d'accueillir tout au long de son cycle de vie dans l'application des valeurs d'opérande/d'expression de différent type, c'est ce qu'on appel le typage dynamique. C'est donc le développeur qui décide dans quelle mesure il souhaite afficher clairement le type de contenu qui sera utilisé par ses variables (ex. par leur nom, par leurs commentaires ou par leurs valeurs d'initialisation).

Comment ça marche ?

Nous allons donc voir en quelques exemples que ce qui est porteur du type n'est pas la variable en elle-même, mais l'opérande ou l'expression (groupe d'opérandes et d'opérateurs). Pour bien comprendre nos exemples, nous allons vérifier que nous parlons bien de la même chose quand nous parlons de variables, d'opérandes, d'opérateurs, d'expressions ou de structure de contrôle. Vous trouverez plus de détail à ce propos dans le billet Expression versus structure de contrôle en JavaScript.

Ainsi les instructions suivantes :

var num = 20 + true,
    bool;
str = 'Hello World';
delete str;
if (num === 21) {
    bool = true;
} else {
    bool = false;
    num = 0;
}

se décomposent ainsi :

┌──────────────────────────────────────────────────┬───────────────────────────────┬──────────────────────────────┬───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
│ Instruction                                      │ Instruction                   │ Instruction                  │ Instruction                                                                                                                       │
├──────────────────────────────────────────────────┼───────────────────────────────┼──────────────────────────────┼───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤
│ Structure de contrôle                            │ Structure de contrôle         │ Structure de contrôle        │ Structure de contrôle                                                                                                             │
│ de déclaration de variables                      │ d'évaluation d'expression     │ d'évaluation d'expression    │ conditionnelle de bloc if — else                                                                                                  │
├─────┬───────────────────────────────────────┬────┼──────────────────────────┬────┼─────────────────────────┬────┼──────┬────────────────┬─────┬───────────────────────────┬──────────┬───────────────────────────┬──────────────────────────────┬───┤
│     │ Expression                            │    │ Expression               │    │ Expression              │    │      │ Expression     │     │ Instruction               │          │ Instruction               │ Instruction                  │   │
│     ├─────┬────┬────┬────┬──────┬────┬──────┤    ├─────┬────┬───────────────┤    ├───────────┬─────────────┤    │      ├─────┬─────┬────┤     ├───────────────────────────┤          ├───────────────────────────┼───────────────────────────┬──┤   │
│     │     │    │    │    │      │    │      │    │     │    │               │    │           │             │    │      │     │     │    │     │ Structure de contrôle     │          │ Structure de contrôle     │ Structure de contrôle     │  │   │
│     │     │    │    │    │      │    │      │    │     │    │               │    │           │             │    │      │     │     │    │     │ d'évaluation d'expression │          │ d'évaluation d'expression │ d'évaluation d'expression │  │   │
│     │     │    │    │    │      │    │      │    │     │    │               │    │           │             │    │      │     │     │    │     ├─────────────────┬─────────┤          ├──────────────────┬────────┼────────────────┬──────────┤  │   │
│     │     │    │    │    │      │    │      │    │     │    │               │    │           │             │    │      │     │     │    │     │ Expression      │         │          │ Expression       │        │ Expression     │          │  │   │
│     │     │    │    │    │      │    │      │    │     │    │               │    │           │             │    │      │     │     │    │     ├─────┬────┬──────┤         │          ├──────┬────┬──────┤        ├─────┬────┬─────┤          │  │   │
│     │ var │ op │ od │ op │ od   │ op │ var  │ sc │ prp │ op │ opérande      │ sc │ opérateur │ variable    │ sc │      │ od  │ op  │ od │     │ var │ op │ od   │ sc      │          │ var  │ op │ od   │ sc     │ var │ op │ od  │ sc       │  │   │
│     │     │    │    │    │      │    │      │    │     │    │               │    │           │             │    │      │     │     │    │     │     │    │      │         │          │      │    │      │        │     │    │     │          │  │   │
│ var   num   =    20   +    true   ,    bool   ;    str   =    'Hello World'   ;    delete      str           ;    if (   num   ===   21   ) {   bool  =    true   ;         } else {   bool   =    true   ;        num   =    0     ;             } │
└─────┴─────┴────┴────┴────┴──────┴────┴──────┴────┴─────┴────┴───────────────┴────┴───────────┴─────────────┴────┴──────┴─────┴─────┴────┴─────┴─────────────────┴─────────┴──────────┴──────────────────┴────────┴────────────────┴──────────┴──┴───┘

Légende :

  • od = Opérande
  • op = Opérateur
  • var = Variable
  • prp = Propriété d'objet
  • sc = Point-virgule (Semicolon)
  • ins = Instruction

Nous voyons ici que l'opérande 20 renvoi 20 qui est un opérande de type Number.

>  20
<· 20

Nous voyons également que l'opérande true renvoi true qui est un opérande de type Boolean :

>  true
<· true

Cependant, dès que ces opérandes sont couplés à des opérateurs, l'expression en question ne retourne pas nécessairement le même type. Cela est le cas de l'expression 20 + true ou

  • 20 (type Number) couplé à
  • true (type Boolean) par l'opérateur binaire d'addition +

retourne un opérande 21 de type Number :

>  20 + true
<· 21

Dans ce cas de figure, true a été coercisé (converti implicitement) par le système en Number (valant 1) et additionné à 20. C'est en cela que le typage est dynamique, ce sont les couples d'opérateurs et d'opérandes selon des règles bien précises qui décident du type de l'opérande retourné.

Voyons un autre exemple avec

  • l'opérande de type Number contenu dans la variable num ainsi que
  • l'opérande 21 de type Number

couplé à l'opérateur binaire d'égalité stricte === :

>  num === 21
<· true

On voit que cette expression renvoi un opérande de type Boolean alors que les deux opérandes originaux était de type Number.

Undefined, le retour

Revenons à notre expression 20 + true. En ajoutant un opérateur d'affectation à gauche = à cette expression, nous affectons à num un opérande de type Number valant 21. Consommer num retourne son opérande.

>  num = 20 + true
<· 21

Avec le mot clé var ajouté, notre expression passe de la simple structure de contrôle d'évaluation d'expresion à une structure de contrôle de déclaration de variable. L'expression initiale reste la même mais notre instruction n'est plus utilisable dans les zones accueillant uniquement des expressions (il faudra l'utiliser dans les zone accueillant des déclarations, comme avec for). Dans ce cas de figure, la structure de contrôle ne retourne rien et le moteur remplace cela par une opérande de type Undefined (sous entendu une valeur représentant l'« absence » de type).

>  var num = 20 + true
<· undefined

Cependant la variable num contient bien l'opérande 21 (c'est le rôle de = ça).

>  num
<· 21

Undefined 3, le retour 2

undefined est également une valeur qui peut être attribuée à la variable lors de l'utilisation d'une déclaration de variable. La variable n'ayant pas d'opérateur d'affectation à gauche = se voit attribué un opérande de type Undefined.

>  bool
<· « Uncaught ReferenceError: bool is not defined »
>  var bool;
   bool
<· undefined

Tout ceci pour dire que le type d'opérande de retour d'une déclaration (ou d'une structure de contrôle) est Undefined. Ce type peut également s'obtenir avec l'opérateur void. Ainsi dans l'expression void {opérande de n'importe quel type}, l'opérande de retour sera toujours du type Undefined.

Cela n'est pas le cas du type Null qui est un opérande que l'on peut uniquement déclarer manuellement.

C'est l'histoire de 7 types

La première chose qui diffère entre le JavaScript et d'autres langages, hors mis le fait qu'il n'y ai pas que la valeur null qui représente « l'absence », est que cette valeur est elle même un type à part entier. En d'autres termes, les 3 types primitifs chaîne de caractère, nombre et booléen —respectivement String, Number et Boolean*— ne peuvent pas avoir pour valeur null, à priori seul le type *Null peut valoir null. Et de la même manière, seul le type Undefined peut valoir undefined. Vous pourrez entrer plus en détail dans le typage en JavaScript avec ce billet : Les types en JavaScript : pour tout savoir !.

Nous pouvons donc affirmer dans un premier temps que le JavaScript possède 6 types (7 depuis ES2015 / ES6) dont 5 types primitifs dont 2 types spéciaux que nous pouvons résumé dans le tableau ci-après :

┌────────────────────────────────────────────────────────────────┐
│ Opérandes                                                      │
├────────┬───────────────────────────────────────────────────────┤
│ ES6    │ ES5                                                   │
├────────┴──────────────────────────────────────────────┬────────┤
│ Primitives                                            │        │
├────────┬────────┬────────┬─────────┬──────────────────┤        │
│        │        │        │         │ Spéciaux         │        │
│        │        │        │         ├───────────┬──────┤        │
│ Symbol │ String │ Number │ Boolean │ Undefined │ Null │ Object │
└────────┴────────┴────────┴─────────┴───────────┴──────┴────────┘

Nous n'allons pas aborder le type Symbol dans cet article qui n'aidera pas à comprendre la différence entre null et undefined, et reviendrons au type Object plus loin.

Quelque chose de primitif

Jusque là, nous n'avons pas encore répondu à la question. Nous allons creuser un peu plus en détail le fonctionnement des opérandes avant de s'intéresser à la question principale. Puisque nous allons uniquement nous intéresser dans cette partie aux types primitifs, les opérandes peuvent être appelés ici des primitives.

Comme nous avons pu le voir précédemment, chaque primitive peut en renvoyer une autre avec l'aide d'un opérateur. On peut ainsi dire que n'importe quel type peut se « transformer » en un autre type. Par exemple pour nos 3 primitives vedettes nous avons :

String

qui devient un Number grâce à l'aide de l'opérateur unaire + :

>  +'42'
<· 42
>  +'Hello World'
<· NaN
>  +''
<· 0

Note : NaN (Not A Number) est bien de type *Number (Si, si...), il est généré par des convertions de type qui ne peuvent pas devenir des valeurs numériques usuelles.*

ou qui devient un Boolean grâce à l'aide de l'opérateur unaire ! (x2) :

>  !!''
<· false
>  !!'Hello World'
<· true
>  !!'0'
<· true

Number

qui devient un String grâce à l'aide de l'opérateur binaire + et d'un opérande de type String :

>  42 + ''
<· "42"
>  -0 + ''
<· "0"
>  NaN + ''
<· "NaN"

ou qui devient un Boolean grâce à l'aide de l'opérateur unaire ! (x2) :

>  !!42
<· true
>  !!0
<· false
>  !!NaN
<· false

Boolean

qui devient un String grâce à l'aide de l'opérateur binaire + et d'un opérande de type String :

>  false + ''
<· "false"
>  true + ''
<· "true"

ou qui devient un Number grâce à l'aide de l'opérateur unaire + :

>  +true
<· 1
>  +false
<· 0

« Faux ! »

Ces transformations signifient que n'importe quelle valeur de primitive à forcément un équivalent de type Boolean, et donc une équivalence true ou false. Voyons cela à travers les tableaux suivants :

String

┌──────────────────────────────────────────────────────────────────────────────────────────┐
│ String vers Boolean                                                                      │
├───────┬─────┬─────┬───────────────┬─────┬──────┬────────┬─────────┬────────┬─────────────┤
│ ''    │ ' ' │ 'a' │ 'Hello World' │ '0' │ '42' │ 'true' │ 'false' │ 'null' │ 'undefined' │
├───────┼─────┴─────┴───────────────┴─────┴──────┴────────┴─────────┴────────┴─────────────┤
│ false │ true                                                                             │
└───────┴──────────────────────────────────────────────────────────────────────────────────┘

Number

┌───────────────────────────────────────────────────────────────┐
│ Number vers Boolean                                           │
├───┬─────┬────┬─────┬─────┬─────┬───────┬──────────┬───────────┤
│ 0 │ NaN │ -0 │ 1   │ -1  │ 789 │ -1789 │ Infinity │ -Infinity │
├───┴─────┴────┼─────┴─────┴─────┴───────┴──────────┴───────────┤
│ false        │ true                                           │
└──────────────┴────────────────────────────────────────────────┘

Boolean

┌──────────────────────┐
│ Boolean vers Boolean │
├───────┬──────────────┤
│ false │ true         │
├───────┼──────────────┤
│ false │ true         │
└───────┴──────────────┘

Null

┌───────────────────┐
│ Null vers Boolean │
├───────────────────┤
│ null              │
├───────────────────┤
│ false             │
└───────────────────┘

Undefined

┌────────────────────────┐
│ Undefined vers Boolean │
├────────────────────────┤
│ undefined              │
├────────────────────────┤
│ false                  │
└────────────────────────┘

Ce qui signifie que dans la totalité de tous les types et valeurs possible des opérandes, les 7 uniques valeurs pouvant être false dans une condition sont :

  • false,
  • 0, -0, NaN,
  • '',
  • null et
  • undefined.

Tout le reste est obligatoirement true, et c'est aussi le cas de notre dernier type : le type Object.

Tout n'est qu'Objet

Penchons nous à présent du côté du dernier type. Celui-ci contient la totalité du reste en JavaScript ! Les objets sont de type Object, les functions sont de type Object, les tableaux sont de type Object. Math, RegExp, JSON, etc. sont de type Object.

À ce titre, ils ont donc tous la capacité d'accueillir des propriétés :

>  var obj = new Object();
   obj.test = 'Bruno';
<· "Bruno"
>  var obj = {};
   obj.test = 'Bruno';
<· "Bruno"
>  var fn = new Function();
   fn.test = 'Bruno';
<· "Bruno"
>  var fn = function () {};
   fn.test = 'Bruno';
<· "Bruno"
>  var arr = new Array();
   arr.test = 'Bruno'
<· "Bruno"
>  var arr = [];
   arr.test = 'Bruno';
<· "Bruno"

même s'ils y a une différence entre un objet utilisé uniquement comme agrégateur de valeur et ceux pouvant être exécuté :

>  typeof {}
<· "object"
>  typeof []
<· "object"
>  typeof (function () {})
<· "function"

Quels sont vos références ?

Là ou un opérande de type objet diffère fondamentalement d'un opérande de type primitif c'est qu'il est stocké dans les variables et les propriétés d'autres objets par référence. Cela signifie qu'il ne retourne pas un opérande lorsqu'il est analysé seul mais qu'il retourne une référence vers lui-même.

Cas d'une primitive qui copie son contenu

>  var value = 'a';

   (function (val) {
       val = 'b';
   }(value));

   value;
<· "a"

Cas d'un objet qui partage une référence

>  var ref = { name: 'a' };

   (function (obj) {
       obj.name = 'b';
   }(ref))

   ref;
<· Object {name: 'b'}

Cela induit une chose : un objet, qu'il soit remplit de propriété ou vide, est toujours évalué à true dans un contexte de booléen. Il n'existe donc aucun moyen pour un objet d'être false dans une condition.

Batnull vs. Superundefined

Bien, je pense que nous en savons assez pour dégager l'utilisé de null, undefined et de répondre à la question « Quelle est la différence entre la valeur null et la valeur undefined ? ». Mais ce n'est pas tout, nous allons également pouvoir identifier les différences avec false, '', -0, 0, NaN, null et undefined : 7 valeurs qui au final sont évaluées à false.

C'est bien de rester neutre...

Sachez que même si null et undefined sont censés représenté « l'absence » de quelque chose ; s'ils sont évalués avec des opérateurs réclamant une transformation en String, ils ne représentent pas nécessairement l'élément neutre.

>  var str1,
       str2 = 'Hello';
   str1 + str2
<· "undefinedHello"
>  var str1 = null,
       str2 = 'Hello';
   str1 + str2
<· "nullHello"

Ainsi au lieu d'obtenir "Hello" en sortie nous obtenons "undefinedHello" ou "nullHello"

Également, s'ils sont utilisés avec des opérateurs réclamant une transformation en Number, null et undefined ne sont pas consistant l'un l'autre :

>  var num;
   +num
<· NaN
>  var num = null;
   +num
<· 0

Le plus intéressant va donc être de définir les variables de type String, Number et Boolean en les initialisant avec leur éléments neutre c'est à dire :

  • Celui qui va être évalué à false dans un contexte de condition,
  • Celui qui va être ignoré lors de l'utilisation de l'opérateur + (concaténation), + (addition) ou &&.

Le fait de les définir de cette manière va également permettre aux développeurs de comprendre quels types d'opérande doivent contenir la variable tout au long de son cycle de vie s'ils sont adeptes du typage fort (ou dans un soucis de lisibilité) :

var str = '',
    num = 0,
    bool = false;

Ainsi les opérations suivantes seront consistantes pour :

une chaîne de caractère

>  (str) ? true : false
<· false
>  'Hello World' + str
<· "Hello World"
>  typeof str
<· "string"
>  str = 'Hello World';
   typeof str;
<· "string"

pour un nombre

>  (num) ? true : false
<· false
>  1 + num
<· 1
>  typeof num
<· "number"
>  num = 42;
   typeof num;
<· "number"

pour un Boolean

>  (bool) ? true : false
<· false
>  bool && true
<· false
>  typeof bool
<· "boolean"
>  bool = true;
   typeof bool;
<· "boolean"

Il va donc être intéressant de laisser le système fournir un opérande de type Undefined à une variable uniquement si elle peut changer de type au cours de son cycle de vie et donc le définir en tant que tel.

var anything, // var anything = undefined,
    str = '',
    num = 0,
    bool = false;

...mais c'est null pour un Objet

Et c'est là qu'intervient notre type Null. Puisque le type Object est quoi qu'il arrive évalué à true dans un contexte booléen, il est impossible d'initialiser sa référence à « vide ». Or la valeur null est justement évalué à false et... révélation... retourne object quand on utilise l'opérateur typeof. De plus, il n'y a pas d’ambiguïté possible car seul vous (manuellement) pouvez affecter un opérande null sachant qu'aucune expression ne retourne cette valeur : on fait forcément référence à un objet non encore initialisé.

Ainsi définir

var obj = null;

signifie que obj accueillera un objet :

>  (obj) ? true : false
<· false
>  typeof obj
<· "object"
>  +obj // +''
<· 0    // 0
>  obj = {};
   typeof obj;
<· "object"
>  +obj // +'Hello World'
<· NaN  // NaN

NaN : Null avec Number

Quid de l'utilisation de null comme initialisation des types Number voir même Boolean ? C'est vrai, on a vu que null se transformait en 0 qui est neutre pour le type Number et en false qui est neutre pour le type Boolean. Rien n'est impossible ! Et c'est une éventualité mais gardez à l'esprit que cela fait perdre de la consistance et de la compréhension à votre code.

Effectivement, si pour une raison ou une autre vous ne souhaitez pas utiliser 0 comme élément neutre car il fait tout de même partis des valeurs numériques, vous pouvez le remplacer par NaN qui renvoi bien "number" avec l'opérateur typeof (contrairement à null qui renvoi "object"...).

En ce qui concerne le type Boolean, ça raison d'être est le fait de n'avoir que deux, et uniquement deux états possibles, aucun intérêt donc de ne pas le mettre à false (ou voir à true) plutôt qu'à null.

Et pour finir, puisque les variables n'ont pas de type à proprement parlé, les définir par défaut avec null empêche de ce fait de saisir le type que vous souhaitez y voir placer par la suite...

Undefined n'est pas Not Defined

Il y a encore une joie du JavaScript qui vient complexifier tout ça, une variable peut à première vue contenir an opérande de type Undefined ou Null mais également ne pas contenir d'opérande ce qui lève l'exception ReferenceError. On va essayer de comprendre ça tout de suite avant de conclure.

Utilisons str qui contient un opérande de type String :

>  str
<· "Hello World"

Couplé à l'opérateur delete, cet expression retourne comme opérande un type Boolean pour signifier si l'opération à réussi :

>  delete str
<· true

Opération qui a pour conséquence de vider la propriété str de l'objet global window de son opérande et donc de la laisser sans rien :

>  str
<· « Uncaught ReferenceError: str is not defined »

Il ne faut pas confondre str = 'Hello World' qui affecte à la propriété de l'objet global window.str un opérande de type String et var str = 'Hello World' qui affecte à une variable locale un opérande de type String (voir le billet sur l'objet des variables).

Effectivement, une variable locale ne peut jamais être vidée de son opérande et l'expression :

>  var str = 'Hello World'
<· "undefined"
>  delete str
<· false
>  str
<· "Hello World"

n'efface pas la valeur d'une variable. Ainsi une variable initialisée dans le champ lexical local doit être mise à undefined si vous souhaitez signifier qu'elle n'est plus utilisée.

« Euh... Pourquoi ? »

En fait, quand vous utilisez str = 'Hello World' alors qu'il n'a pas été défini au préalable par l'opérateur var, vous faites exactement la même chose que window.str = 'Hello World' dans un navigateur ou global.str = 'Hello World' en Node.js par exemple. Cela signifie donc que vous n'affectez pas un opérande à une variable, mais vous affectez un opérande à la propriété d'un objet : exactement de la même manière qu'avec cette déclaration { str: 'Hello World'; }. L'opérateur delete sert donc à « retirer » la propriété d'un objet là ou le mettre à undefined signifie simplement que la propriété n'a pas réellement d'opérande primitif ou objet.

Dans cet exemple, la propriété ne disparaît pas :

>  var obj = {
       str: 'Hello World',
       str2: 'Bye!'
   };
   obj.str2 = undefined;
   obj;
<· Object {str: 'Hello World', str2: undefined}

Alors que dans celui-ci, si :

>  var obj = {
       str: 'Hello World',
       str2: 'Bye!'
   };
   delete obj.str2;
   obj;
<· Object {str: "Hello World"}

En d'autre terme, quand vous sollicitez une variable ainsi : myVar,

  • si celle-ci existe en tant que variable local vous obtiendrez au moins : undefined (sauf si elle contient un autre opérande).
  • si elle n'existe pas en local mais existe en tant que propriété de l'objet global vous obtiendrez au moins : undefined (sauf si elle contient un autre opérande).
  • sinon vous obtiendrez « Uncaught ReferenceError: myVar n'est pas définie ».

Il faut bien comprendre que ce message concerne la variable en elle même et non l'opérande qui pour le coup n'existe pas du tout : une sorte de « vrai undefined ».

tl;dr

Voici mon utilisation des 5 types primitifs et du type Object et quel est la différence d'utilisation entre les constantes undefined ou null.

String

Si vous savez qu'une variable sera uniquement de type chaîne de caractère durant son cycle de vie, vous pouvez l'initialiser à '' :

> ('') ? true : false; // false
  typeof ''; // "string";
  ('Hello World') ? true : false; // true
  typeof 'Hello World'; // "string"

  function (str) {
      str = str || "";
  }

Number

Si vous savez qu'une variable sera uniquement de type numérique durant son cycle de vie, vous pouvez l'initialiser à NaN (ou 0 (neutre pour l'addition), ou 1 (neutre pour le produit)) :

> (NaN) ? true : false; // false
  typeof NaN; // "number";
  (42) ? true : false; // true
  typeof 42; // "number"

  function (num) {
      num = num || NaN;
  }

Boolean

Si vous savez qu'une variable sera uniquement de type booléen durant son cycle de vie, vous pouvez l'initialiser à false :

> (false) ? true : false; // false
  typeof false; // "boolean";
  (true) ? true : false; // true
  typeof true; // "boolean"

  function (bool) {
      bool = bool || false;
  }

Object

Si vous savez qu'une variable sera uniquement de type objet, par convention, vous pouvez l'initialiser à null :

> (null) ? true : false; // false
  typeof null; // "object";
  ({}) ? true : false; // true
  typeof {}; // "object"

  function (obj) {
      obj = obj || null;
  }

Note: l'utilisation pratique de null est d'être la version évalué à false d'un type Object car un objet est toujours true, et parce que typeof null retourne object. Cela signifie que typeof myVarObject retourne une valeur consistante pour les deux types Object et Null.

Types multiples

Si vous savez qu'une variable peu être de plusieurs types (tout au long de son cycle de vie), par convention, vous pouvez l'initialiser à undefined.

> (undefined) ? true : false; // false
  typeof undefined; // "undefined";
  function (value) {
      // value = value || undefined;
  }

Lire dans une autre langue