Programmation JavaScript/Variables

Les variables permettent d'instancier un objet et de manipuler cette instance à l'aide d'un identificateur (le nom de la variable). Tous les identificateurs en JavaScript sont des références à un objet. Javascript étant un langage au typage dynamique, le type des objets n'est pas déclaré explicitement et il peut changer automatiquement.

Typage dynamique

modifier

Il est donc possible de déclarer une variable sans lui définir de type. Ou plutôt, une variable déclarée de telle manière sera du type undefined.
Tout au long de son utilisation, cette variable pourra changer de type sans que l'interpréteur n'y trouve rien à redire.

var ma_versatile; // est undefined
ma_versatile = "Je deviens une chaîne"; // devient string
ma_versatile = 5; // devient number
ma_versatile = new Array(); // devient object

Il n'est en fait pas nécessaire de déclarer une variable (avec le mot-clé var). La différence est expliquée ci-après.

Identificateur

modifier

Les règles d'élaboration des identificateurs sont décrites à ce lien. Pour résumer, les identificateurs comportent des caractères alphanumériques et le tiret soulignant (_). Ils ne peuvent commencer par un chiffre, et les majuscules comptent.

Portée

modifier

Propriétés

modifier

L'instanciation directe (sans mot-clé var) d'un objet avec un identificateur donne souvent l'impression de créer une variable globale. En réalité, la notion de "variable globale" n'existe pas en JavaScript. Toutes les variables sont, sauf quand on les déclare avec var, des propriétés d'un objet. Par défaut, cet objet est implicitement l'objet window (et non, window n'est pas une variable globale qui ferait exception à la règle car il est une de ses propres propriétés, c'est-à-dire que window est une propriété de window).

Comme window reste l'objet implicite la plupart du temps, une variable créée ainsi pourra être employée comme si elle était globale :

ma_variable = "une valeur";

function maFonction()
{
  mon_autre_variable = "une autre valeur"
}

maFonction()
alert(mon_autre_variable) // affiche "une autre valeur"

La variable mon_autre_variable est certes déclarée dans une fonction, mais il faut lire "window.mon_autre_variable" donc c'est une propriété de window et elle ne sera pas détruite automatiquement à la fin de la fonction.

Sous-entendre un objet

modifier

Pour économiser du code, il peut être intéressant de sous-entendre un autre objet que window. Cela se fait explicitement avec un bloc with. Cette déclaration ne fonctionne pas aussi simplement qu'en Basic, par exemple. L'objet est en fait placé en haut de la scope chain dans le bloc d'instruction, presque comme si ses propriétés étaient des variables locales d'une fonction (voir Programmation JavaScript/Scope chain). Lorsqu'on utilise un identificateur dans un bloc with, la résolution se fait aussitôt qu'une des conditions suivantes est remplie :

  1. l'identificateur est une propriété existante de l'objet sous-entendu
  2. l'identificateur existe ailleurs dans la scope chain, en partant du haut (par exemple, une variable locale)
  3. l'identificateur est une propriété existante de window

Si aucune condition n'est remplie et que l'expression affecte une valeur à la variable, cette variable est créée comme propriété de l'objet global. Si l'expression ne fait qu'utiliser la valeur, elle retournera simplement undefined.

c=true
d="propriété de window"

monObjet={a: 0, d: "propriété de monObjet"}

with(monObjet)
{
  alert(a)    // affiche la valeur de monObjet.a : "0"
  a=2    // monObjet.a existe et va être utilisé
  // si on veut utiliser window.a, il faut le faire explicitement
  window.a=5

  b="texte"    // b n'est encore défini nulle part donc window.b est créé
  // si on veut créer monObjet.b, il faut le faire explicitement
  monObjet.b="autre chose"

  delete c   // monObjet.c n'existe pas, mais window.c va être utilisé
  
  alert(d)   // affiche "propriété de monObjet"
  // si on veut l'autre, il faut être explicite
  alert(window.d)    // affiche "propriété de window"
}

Variables locales

modifier

À l'aide de la déclaration var, il est possible de définir une variable locale, qui n'est pas une propriété d'un autre objet. Cette déclaration peut avoir lieu n'importe où dans le corps d'une fonction, pas nécessairement au début, mais seules les utilisation ultérieures de l'identificateur feront référence à la variable locale puisqu'elle n'existait pas avant.

Comme toute variable locale, elle est automatiquement détruite à la fin de la fonction, bien que sa valeur puisse être sauvegardée dans certains cas (voir Fermeture). Par contre, elle ne peut pas être détruite par l'opérateur delete.

function foo()
{
	var loc_1 = 2                 // n'a pas d'existence en dehors de foo()
	for (i = 0 ; i < loc_1; i++)
	{
		var loc_2 = 2 * loc_1;   // loc_2 est disponible pour toute la fonction foo() 
		alert(loc_2);
	}
	alert(loc_2);                 // S'affiche sans problème
}
alert(loc_2); // Provoque une erreur

Si la déclaration var est utilisée en-dehors de toute fonction, la variable sera utilisable dans tout le script.

Hoisting

modifier

L'exemple précédent montre l'effet hositing (levage) des déclarations avec le mot-clé var. Les déclarations sont en effet remontées au niveau du premier bloc de code de la fonction. Cela permet dans l'exemple précédent de continuer à accéder à la variable loc_2 après la boucle for.

Par contre, la partie initialisation n'est pas rehaussée et la variable a donc une valeur indéfinie (undefined). L'exemple précédent équivaut au code suivant :

function foo()
{
	var loc_1;
	var loc_2;  // Déclaration rehaussée
	var i;      // Déclaration implicite
	loc_1 = 2;
	for (i = 0 ; i < loc_1; i++)
	{
		loc_2 = 2 * loc_1;   // loc_2 est disponible pour toute la fonction foo() 
		alert(loc_2);
	}
	alert(loc_2);             // S'affiche sans problème, vaudrait undefined si loc_1 <= 0
}

Il en va de même pour les fonctions déclarées dans des fonctions :

function add(n)
{
	var t = addn(5);

	function addn(a)   // ^^ déclaration rehaussée implicitement
	{
		return a+n;
	}

	return t;
}

Cela équivaut au code suivant :

function add(n)
{
	var t;
	function addn(a)
	{
		return a+n;
	}

	t = addn(5);
	return t;
}

Depuis Javascript 5, le mot-clé let permet de déclarer des variables locales à un bloc, sans effet de rehaussement.

function foo()
{
	let loc_1 = 2;
	for (i = 0 ; i < loc_1; i++)
	{
		let loc_2 = 2 * loc_1;
		console.log(loc_2);
	}
	console.log(loc_2); // --> Uncaught ReferenceError: loc_2 is not defined
}

Javascript est faiblement typé, mais typé quand même. Voici une revue des types que peut prendre une variable. Il est possible à tout moment de vérifier le type avec l'opérateur typeof.

undefined

modifier

Une variable est du type undefined dans trois cas:

  • Après déclaration var sans affectation
var maNouvelleVar; // Déclarée mais pas affectée
alert(typeof maNouvelleVar); // Affiche undefined
  • Si on lui affecte explicitement la valeur undefined, ou qu'on lui affecte le résultat d'une expression qui retourne cette valeur.
var jeSaisPas = undefined
var caNExistePas = "impossible".enFrancais
alert (typeof jeSaisPas) // Affiche undefined
alert (typeof caNExistePas) // Affiche undefined
  • Si elle n'a pas été déclarée ni utilisée, ou bien qu'elle a été effacée par l'opérateur delete.
alert(typeof varPasDeclaree); // Affiche undefined (si elle n'a effectivement pas été affectée)
monEphemere = "exister";
delete monEphemere; // la vie est courte
alert (typeof monEphemere); // Affiche undefined

alert(varPasDeclaree) // erreur !!

À noter : dans les deux premiers cas, la variable existe et possède une valeur (undefined). On peut lire cette valeur sans provoquer d'erreur. Dans le troisième cas cependant, la variable n'existe pas : l'opérateur typeof retourne "undefined", mais lire directement la valeur de la variable provoque une erreur.

JavaScript réunit en un seul type les entiers, petits et grands, les décimaux, et les réels, flottants ou pas.

    r = 1/3;
    alert(r);         // affiche 0.333333333333
    alert(typeof r);  // affiche number

    n = 4/2;
    alert(n);         // affiche 2
    alert(typeof n);  // affiche number
    
    3.6e5      // 360 000 en notation scientifique
    0x40       // 64 en notation hexadécimale
    0100       // 64 en notation octale

Concrètement, les nombres sont stockés sur 64 bits, avec une mantisse de 53 bits. Cela permet des valeurs entières jusqu'à 9 007 199 254 740 991, à partir duquel on commence à perdre de la précision jusqu'à 2^1024-1 qui est la valeur flottante la plus élevée représentable en JavaScript.

En fait, JavaScript inclut également dans le type number deux valeurs spéciales. Infinity, un nombre signé, est renvoyé dès que le résultat dépasse la capacité de stockage de number, mais aussi dans des cas limites simples. Dans les cas indécidables, ou bien lorsqu'une opération qui retourne un nombre ne peut pas le faire, c'est la valeur NaN (Not A Number) qui est renvoyée (voir les opérateurs arithmétiques, les méthodes de Number et les méthodes de Math pour les cas qui produisent ces deux valeurs).

1/0 == Infinity
-2 / Infinity == 0
0/0 == NaN
Number("texte") == NaN

Le type string stocke une chaîne de caractères. C'est aussi le type le plus "faible" du langage : n'importe quelle valeur peut être convertie en chaîne, et en cas de doute avec l'opérateur +, c'est une concaténation qui aura lieu, pas une addition.

s = "Une chaîne de caractères";
alert(typeof s); // Affiche "string"

Tous les objets possèdent une méthode générique toString() "magique" qui est appelée lorsqu'il faut convertir leur valeur en chaîne. Elle peut bien sûr être écrasée par une méthode plus spécifique.

Les chaînes de caractères en JavaScript sont encadrées par des guillemets droits (double quotes, ") ou des single quotes ('), au choix. Il n'y a pas de distinction entre des chaînes définies avec l'un ou l'autre, mais si on commence une chaîne par un type de guillemet il faut la finir par le même type. À l'intérieur d'une chaîne, les séquences d'échappement connues fonctionnent comme partout :

  • \n : line feed, saut de ligne Unix
  • \r : carriage return, saut de ligne Mac OS classique
  • \t : tabulation
  • \\ : backslash
  • \' ou \" : un guillemet si la chaîne commence par ce caractère

etc.

Enfin, JavaScript utilise des chaînes de caractères Unicode multi-octets. Quel que soit l'encodage du fichier où est écrit le script, il est toujours possible d'inclure n'importe quel caractère dans une chaîne, soit en récupérant une chaîne d'un autre fichier, soit en créant une chaîne avec la méthode statique String.fromCharCode(), soit en utilisant la séquence d'échappement :

  • \xHH : HH est le code hexadécimal du caractère dans l'encodage courant
  • \uHHHH : HHHH est le code hexadécimal du caractère dans le tableau Unicode. Il ne peut y avoir moins de 4 chiffres après la séquence \u.

boolean

modifier

Une variable de type boolean accepte deux valeurs : vrai et faux.

b = true;
alert(typeof b); // Affiche "boolean"
c = (5 == 3); // est faux, mais l'expression est booléenne.
alert(typeof c); // Affiche "boolean"

Dans une comparaison, les valeurs suivantes sont équivalentes (opérateur ==) à false :

  • 0 : le nombre zéro et ses variations, du style 0.000, 0x0, 0e12, etc.
  • "" : la chaîne vide
  • "0" : la chaîne contenant le nombre zéro, et toutes ses variations, comme pour le nombre
  • [] ou new Array() : un tableau vide, et en fait tout objet dont la conversion en chaîne est l'une des valeurs ci-dessus

Par ailleurs, les valeurs suivantes donnent false si on les convertit (explicitement ou pas) en booléen, bien qu'elle ne soient pas toutes équivalentes par comparaison :

  • 0 et ses variations
  • ""
  • null
  • NaN
  • undefined

Comme ces listes le montrent, une valeur peut être équivalente à false sans être considérée comme fausse elle-même ! Et inversement, une valeur considérée comme fausse n'est pas forcément équivalente au booléen false.

Cela peut sembler surprenant et illogique à première vue (et source de quelques bugs) mais il faut comprendre l'origine de ces différences :

  • les valeurs null, NaN et undefined refusent simplement d'être équivalentes à n'importe quoi sauf elles-mêmes. Donc l'opérateur == retournera systématiquement false si une des opérandes est null, NaN ou undefined et que l'autre n'est pas strictement identique. Cependant, à choisir entre vrai ou faux (lors d'une conversion en Boolean, il n'y a pas d'autre choix), ces valeurs sont plutôt false.
  • les chaînes "0" et ses variantes sont... des chaînes, et false est un booléen. JavaScript ne peut pas comparer directement deux types différents. Il pourrait convertir la chaîne en booléen, mais préfère convertir les deux en number (le type number est plus "fort"). Le résultat est 0 == 0 la comparaison est donc vraie. Cependant, "0" reste une chaîne non-vide, et si on la convertir en booléen, on obtient true.
  • la chaîne "false" est aussi une chaîne, mais ne peut pas être convertie en number. JavaScript, en dernier recours, convertit alors l'autre opérande en string et se retrouve à comparer "false"=="false", ce qui est vrai. Par contre, comme pour "0", la chaîne n'est pas vide et sa conversion en booléen donne true.
  • le tableau vide [] est un tableau, donc JavaScript le convertit en chaîne pour le comparer à false. Or, la méthode toString() d'un tableau retourne la fusion de tous les éléments, séparés par des virgules. Comme il n'y a aucun élément dans le tableau, le résultat est la chaîne vide "", qui est équivalente à false. Par contre, un tableau est de type objet et sa conversion en booléen donne true.

Une valeur qui n'est pas équivalente à false ne sera pas pour autant équivalente à true par comparaison, à commencer par null, NaN et undefined mais aussi toute valeur qui n'est pas équivalente à l'une des conversions possibles de true (la chaîne "true", la chaîne ou le nombre 1). Par contre, toutes les valeurs qui ne donnent pas false par conversion en boolean donnent true, sans exception.

Attention, les valeurs de type object, y compris les instances des classes String, Number et Boolean créées avec le constructeur new sont toujours converties en true, même lorsque leur valeur est "", 0 ou false (la seule exception est null qui est de type object et dont la conversion en booléen est false). Cependant la comparaison avec false fonctionne de la même façon qu'avec des valeurs littérales.

Une variable de type object assume pleinement sa nature. Les autres types de variables, dits "scalaires", se comportent comme s'ils étaient eux-mêmes une valeur. Ainsi, lorsqu'on passe une variable scalaire à une expression, sa valeur est clonée. En revanche, passer une variable de type object ne fait que copier la référence, et la valeur n'est pas clonée.

L'existence de "classes" en JavaScript est discutée. Le langage ne possède pas de déclaration de classe en tant que telle. Pour cette raison, l'utilisation de ce terme sera mise entre guillemets.

Il existe trois manières de créer une variable de type object.

  • implicitement par l'expression littérale d'une valeur non-scalaire (valable uniquement quand une expression littérale existe pour la "classe" voulue)
  • explicitement avec new et le constructeur (valable pour toutes les "classes" sauf les objet du DOM)
  • indirectement pour les objets du DOM (et on ne peut pas utiliser new avec eux)

L'instanciation explicite d'un objet grâce à un constructeur s'effectue avec l'opérateur new.

t = new Array();
alert(typeof t) // Affiche "object"

Les expressions littérales de chaînes, nombres et booléens créent des variables scalaires alors que le constructeur de ces types crée un objet (explicitement de type object et donc strictement différents d'une valeur scalaire équivalente). Mais les expressions littérales de fonctions, tableaux et expressions rationnelles créent des objets, car ce ne sont pas des types scalaires. Ces objets, instanciés sans utilisation de new, sont néanmoins strictement identiques à leur équivalent produit par l'appel d'un constructeur. Par exemple :

// valeur scalaire vs. objet
"chaîne" !== new String("chaîne")
2.34 !== new Number(2.34)

// objet vs. objet... c'est pareil !
["a", "b", "c"] === new Array("a", "b", "c")
/(\w+) ?\(/i === new RegExp("(\w+) ?\(", "i")

On peut créer un objet générique (qui sera simplement une instance de la "classe" Object) avec la notation littérale. Cela consiste à écrire, entre accolades, une liste de couples clé : valeur séparés par des virgules, la clé pouvant être un identifiant ou bien une chaîne, et la valeur pouvant être n'importe quelle valeur. La clé et la valeur sont séparés par deux points (':').

objetLitteral = {
  nom : "Mac OS X",
  version : 10.5,
  "nom de code" : "Leopard"
}

Attention, dans l'exemple ci-dessus, on ne pourra accéder à la dernière propriété ("nom de code") de l'objet qu'avec la notation tableau (avec des crochets), pas la notation objet (avec des points) car ce n'est pas un identifiant valide (il y a des espaces dedans).

Le DOM (aussi bien W3C que IE) définit certaines "classes" : HTMLElement, HTMLTextNode, HTMLDocument, CSSRuleSet, CSSRule, etc. On doit utiliser les méthodes du DOM (comme document.createElement()) pour instancier ces objets (le "DOM 0" fait exception à cette règle et à bien d'autres).

Les autres "classes" d'objets ne peuvent être créés que par un constructeur. Il y a des constructeurs natifs tels que Date, et on peut définir un constructeur soi-même. Pour plus de détails, voir le chapitre consacré aux objets.

function

modifier

La différence essentielle entre une variable de type function et une variable de type object, est qu'on peut appeler une fonction. Dans les deux sens du terme :

  • une fonction peut être nommée, par un identifiant autre que le nom de la variable qui la référence
  • une fonction peut être invoquée

Attention : ne pas confondre le type de variable ("object", "function") avec les constructeurs homonymes (Object, Function). Une variable de type "object" peut avoir n'importe quel constructeur sauf Function et bien des comportements différents, tous différents du comportement d'une fonction. Mais cette section est uniquement consacrée au type "function".

Contrairement aux autres types, le type "function" n'a pas de valeur spéciale (telle que NaN pour "number" ou null pour "object"). Il n'y a aucun moyen de convertir directement un autre type en fonction.

Une fonction est créée par l'expression littérale (exemple ci-dessous) ou bien par le constructeur Function.

function fibonacci( n )
{
    if(isNaN(n) || n<0)
        return undefined

    n=Math.floor(n)
    var u = 1, v = 0

    while(n--)
        u = v + (v = u)

    return v
}

Cette déclaration crée une variable "fibonacci" qui référence la fonction, mais c'est aussi une opération qui retourne la référence de la fonction, que l'on peut ainsi affecter à une autre variable. Il est d'ailleurs possible de déclarer une fonction anonyme. De plus, toute nouvelle fonction reçoit automatiquement une propriété prototype identique au prototype de la fonction Object.

L'appel se fait en ouvrant et fermant une paire de parenthèses juste après une expression dont la valeur est de type "function". Cette expression n'est pas obligatoirement le nom d'une fonction, ni même un identifiant. Cette signification des parenthèses est prioritaire sur la signification générale (forcer une expression à être évaluée avant celles qui l'entourent).

f = fibonacci
delete fibonacci // efface seulement la variable, pas la fonction, qui est toujours référencée par f
x = f(10); // Constitue l'appel de la fonction fibonacci, en lui passant un argument
alert(f) // affiche le code de la fonction, y compris le nom donné dans sa déclaration, "fibonacci"

Les opérateurs et méthodes qui acceptent des objets comme opérande ou argument fonctionnent pareil avec des fonctions, notamment le point pour accéder aux propriétés de la fonction. L'opérateur new n'accepte que des fonctions comme opérande.

Par défaut, la conversion d'une fonction en chaîne retourne le code source de la fonction, préalablement reformaté par l'interpréteur JavaScript : les point-virgules, sauts de ligne et indentations seront normalisés.