Espaces de noms (ou namespace) en JavaScript

Nourdine FALOLA

Cet article s'adresse à une population de développeurs déjà familière avec la problématique du développement Web et le langage JavaScript. Dans cet article, vous apprendrez comment packager vos bibliothèques en simulant en JavaScript ce que l'on appelle un espace de noms (« namespace » en anglais). Vous pouvez participer à la discussion relative à cet article sur le site communautaire www.developpez.com. Commentez ♪

- Titre : Espaces de noms (ou namespace) en JavaScript

- Auteur : Nourdine FALOLA

- Parution : 26 mars 2009

- Mise à jour : 10 juin 2009

- Licence : Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright ® 2009 Nourdine FALOLA. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

Sommaire

- I. Qu'est-ce qu'un espace de noms

- I-A. Problématique

- I-B. Comment s'en sortir

- I-C. Comment choisir de mettre telle source dans un espace et telle autre dans un autre

- II. Création d'un espace de noms en JavaScript

- II-A. Les bases

- II-B. Notation JSON

- II-C. Existence et unicité d'un espace de noms

- II-D. Les sous-espaces de noms

- II-E. Les limites des espaces de noms en JavaScript

- III. Exemples

- III-A. Votre bibliothèque : myOwnJSLibrary.js

- III-B. myOwnJSLibrary.Math.js

- III-C. myOwnJSLibrary.Math.Statistic.js

- III-D. myOwnJSLibrary.String.js

- III-E. Utilisation dans un code client : test.html

- IV. Annexe

- IV-A. Le mot-clé « this »

- IV-B. Les fonctions anonymes

- IV-C. Importation de symboles

- V. Remerciements

- VI. Toutes les publications

- VI-A. Espaces de noms (ou namespace) en JavaScript

- VI-B. Conception de contrôles Web avancés : Créer une table HTML éditable - HtmlEditTable v1.0

- VI-C. Conception de contrôles Web avancés : Créer une table HTML éditable - HtmlEditTable v2.0

- VI-D. Bibliothèque d'effets JavaScript : Smile FX

I - Qu'est-ce qu'un espace de noms

Faisons un bref rappel de ce qu'est un espace de noms (ou une courte introduction pour ceux qui ne les connaissent pas).

I-A - Problématique

Lorsque vous développez, seul ou en équipe, il peut arriver que vous vous retrouviez dans plusieurs codes sources avec des variables, des fonctions, des classes, etc. portant le même nom, mais effectuant des tâches différentes, car prévues pour des contextes différents. Si vous devez faire collaborer ces codes au sein d'un même programme, il y aura alors conflit de noms. Avec un langage compilé, le compilateur vous préviendra de ce conflit, mais avec un langage interprété comme le langage JavaScript vous passerez à côté et ce que vous manipulerez sera la dernière définition de vos variables, fonctions, etc. qui aura écrasé les précédentes.

I-B - Comment s'en sortir

Sauvez-nous docteur! Et bien voilà toute l'utilité des espaces de noms. Cela sert à regrouper dans un même espace vos sources et à les qualifier de manière unique (si vous ne vous amusez pas à créer deux espaces de noms similaires évidemment).

I-C - Comment choisir de mettre telle source dans un espace et telle autre dans un autre

Il est préférable de regrouper sous un même espace de noms les fonctionnalités qui agissent de conserve dans un même contexte. Par exemple, il n'est pas pertinent de mettre dans le même espace de noms des fonctionnalités pour des traitements mathématiques et pour des conteneurs. Dans ce cas, il est plus judicieux de créer 2 espaces de noms : un pour ce qui se rapporte aux nombres (« Math » par exemple) et aux chaînes de caractères (« String » par exemple). Dans ces deux espaces de noms, vous pouvez avoir une fonction « Add ». Dans le cas de « Math » elle peut additionner deux nombres, dans le cas de « String » elle peut concaténer le contenu d'une chaîne à une autre (addition de chaînes), deux actions complètement différentes qui pourtant portent assez logiquement le même nom.

J'espère que cela vous a convaincu de l'utilité des espaces de noms. Voyons maintenant comment les mettre en œuvre en JavaScript.

II - Création d'un espace de noms en JavaScript

Vais-je vous choquer et vous faire vous demander quel est le but de cet article si je vous dis que les espaces de noms n'existent pas en JavaScript : Et non, ça n'existe pas en tant que tel ! Mais il est possible de les simuler.

En JavaScript, vous avez la possibilité de créer des objets et de leur adjoindre des attributs (ou propriétés) comme bon vous semble, aussi que sera un espace en JavaScript ? Un objet dont les propriétés seront les fonctionnalités que vous cherchez à regrouper dans un même domaine.

II-A - Les bases

Créons donc un espace de noms, i.e. un objet, myNamespace :

Création d'un espace de noms

var myNamespace = {} ;

Ajoutons-lui quelques fonctionnalités :

Ajout de fonctionnalité à un espace de noms

myNamespace.var1 = "NF";
myNamespace.var2 = 28;
myNamespace.displayVar1 = function() { alert(this.var1) ; } ;
myNamespace.displayVar2 = function() { alert(this.var2) ; } ;
myNamespace.createObject = function(){
return new function()
{
this.name = "no name";
this.what = "object created by myNamespace.createObject";
this.version = "v1.0";
this.display = function() {
alert(this.name+ " ("+this.what+") - "+ this.version);
}
}
};

Vous trouverez en annexe un bref rappel sur l'utilisation de .

Et testons notre création :

Utilisation des fonctionnalités d'un espace de noms

alert(myNamespace.var1);   // NF
myNamespace.var1 = "NF is the author";
myNamespace.displayVar1(); // NF is the author

alert(myNamespace.var2);   // 28
myNamespace.var2++;
myNamespace.displayVar2(); // 29

var o = myNamespace.createObject();
o.version = "v2.0";
o.display();
// no name (bject created by myNamespace.createObject) - v2.0

Quelques explications s'imposent.

Tout d'abord nous avons créé un objet appelé myNamespace dont nous supposerons que le nom est unique (à vous de faire en sorte que ce soit le cas).

Puis nous lui avons adjoint quelques propriétés qui constituent les fonctionnalités de notre bibliothèque :

- une variable var1 contenant une chaîne de caractères ;

- une variable var2 contenant un nombre ;

- une fonction qui affiche var1 ;

- une fonction qui affiche var2 ;

- une fonction qui crée un objet possédant 4 propriétés (3 variables et une fonction d'affichage de ses variables).

Enfin nous utilisons les fonctionnalités de notre bibliothèque :

- affiche var1 ;

- modifie la valeur de var1 ;

- affiche var1 (nouvelle valeur) ;

- affiche var2 ;

- modifie la valeur de var2 ;

- affiche var2 (nouvelle valeur) ;

- crée un objet o ;

- modifie la valeur de o.version ;

- affiche les valeurs des attributs de o.

À ce stade, vous avez packagé vos fonctionnalités (i.e. votre bibliothèque), vous avez résolu d'éventuels conflits de noms en regroupant ces fonctionnalités dans un même espace (ou domaine) et si le choix de l'espace de noms est pertinent, on reconnaît tout de suite à quelle utilisation se destine votre bibliothèque.

II-B - Notation JSON

Peut-être que le code précédent n'est pas lisible pour vous, pas assez intuitif. Peut-être êtes-vous habitué à une autre syntaxe du fait de votre expérience passée et vous abordez celle-ci avec difficulté. Sachez qu'il ne s'agit pas là de l'unique syntaxe possible.

Vous pouvez par exemple utiliser la notation JSON :

Notation JSON

var myNamespace = {
var1 : "NF",
var2 : 28,
displayVar1 : function() { alert(this.var1) ; },
displayVar2 : function() { alert(this.var2) ; },
createObject : function(){
return {
name : "no name",
what : "objet créé par myNamespace.createObject",
version : "v1.0",
display : function() {
alert(this.name+ " ("+this.what+") - "+ this.version);
}
}
}
};

Cette notation est strictement équivalente à la précédente, plus élégante, car plus lisible et montrant tout de suite que les fonctionnalités de votre bibliothèque appartiennent à un même ensemble myNamespace.

Cependant, rien ne vous empêche de mixer les 2 notations. Par exemple, si vous avez implémenté votre bibliothèque dans un fichier et que dans un second vous souhaitez en ajouter de nouvelles. Dans votre premier fichier, écrire le code précédent et dans le second :

Extension d'un namespace

myNamespace.newFunction = function() {
alert("new function added") ;
};

II-C - Existence et unicité d'un espace de noms

Si vous êtes amené à séparer dans plusieurs fichiers l'implémentation de votre espace de noms comme dans la section précédente, je vous conseille de tester son existence au début de chaque fichier d'extension, car rien ne vous garantit que les utilisateurs de votre bibliothèque incluront tous les scripts nécessaires dans leurs pages web, ou qu'ils les déclarent dans le bon ordre.

Test de l'existence du nom de la variable

if (typeof myNamespace == "undefined")
{
alert("myNamespace is not defined. Include myNamespace.js before this script");
}
else
{
myNamespace.newFunction = function() {
alert("new function added") ;
};
}

Depuis le début de cet article, je vous dis que les espaces de noms nous permettent d'éviter les conflits de noms. C'est bien joli me direz-vous, mais si l'on package notre code au sein d'espaces de noms qu'est-ce qui empêche :

- qu'on se retrouve avec plusieurs espaces de noms portant le même nom ;

- qu'un objet ou une variable ait le même nom que notre espace de noms ?

Réponse : rien, retour à la case départ.

En fait pas tout à fait, car déjà nous avons évité qu'au sein de vos propres développements vous ayez des conflits de noms et en travail collaboratif nous avons limité la possibilité de conflits aux noms des espaces. C'est déjà pas mal. Comment atteindre l'étape ultime et éviter tout conflit entre votre espace et un objet ou une variable ? Impossible. Vous ne pouvez garantir que ceux qui utiliseront votre espace de noms feront attention à ne pas créer autre chose portant le même nom.

Cependant vous pouvez mettre en place un système pour détecter si à la fin du chargement d'une page web utilisant votre espace celui-ci n'a pas été écrasé par une autre variable de même nom. Vous pouvez aussi étoffer le test du listing précédent pour non seulement tester l'existence du nom de votre espace, mais aussi vérifier qu'il s'agit toujours de votre espace.

Pour cela, ajoutons une propriété author à notre espace dans laquelle vous mettez votre nom. Cette propriété nous sert à identifier l'espace de noms :

Identifier l'espace de noms

var myNamespace = {
author : "Nourdine FALOLA",
var1 : "NF",
var2 : 28,
displayVar1 : function() { alert(this.var1) ; },
displayVar2 : function() { alert(this.var2) ; },
createObject : function(){
return {
name : "no name",
what : "objet créé par myNamespace.createObject",
version : "v1.0",
display : function() {
alert(this.name+ " ("+this.what+") - "+ this.version);
}
}
}
};

Et structurons chaque fichier d'extension de notre espace de la façon suivante :

Test de l'existence et de l'unicité de l'espace de noms

(function()
{
var f = function()
{
if (typeof myNamespace == "undefined")
{
alert("the namespace myNamespace was deleted.");
return false;
}
else if (typeof myNamespace.author == "undefined" ||
myNamespace.author != "Nourdine FALOLA")
{
alert("the namespace myNamespace was altered.");
return false;
}

return true;
}

if (window.addEventListener) // non-IE browser
{
window.addEventListener("load",f,false);
}
else if (window.attachEvent) // IE browser
{
window.attachEvent("onload",f);
}

if (!f())
return;

// put your extension code here

})(); // run the anonymous function

Vous trouverez en annexe un rappel sur l'utilisation des fonctions anonymes

Que réalise la fonction dont la définition représente tout notre fichier d'extension et qui est exécutée dans la foulée :

- elle définit une fonction f vérifiant l'existence de la variable portant le nom de notre espace, l'existence d'une propriété author contenant le nom de l'auteur de la bibliothèque ;

- elle attache un événement à la fin du chargement de la page appelant ce code (exécution de la fonction f) ;

- elle teste l'exécution de f renvoie bien true et stoppe le script si false ;

- elle étend l'espace de noms par ajout de nouvelles propriétés.

Détaillons chaque étape.

D'abord tout le code de votre fichier d'extension est encapsulé dans une exécutée dès la fin de sa définition grâce à la syntaxe suivante :

Syntaxe de l'exécution d'une fonction anonyme

(function() {
// code
})() ;

Une fonction f est définie. Cette fonction réalise plusieurs tests pour vérifier que la variable représentant l'espace de nom existe et qu'il s'agit bien de celle que vous avez créée dans le fichier initial (par vérification de l'existence de la propriété author et de sa valeur), si c'est le cas elle renvoie true, sinon false.

Cette fonction est ensuite attachée à l'événement onload de la page, c'est-à-dire qu'elle sera exécutée dès la fin du chargement de la page. Pourquoi à la fin du chargement de la page ? Parce que si un utilisateur écrase votre espace, il y a de fortes chances que ce soit avant le chargement complet de la page.

Puis le retour de cette fonction est testé afin de voir tout de suite si on manipule bien la bonne variable myNamespace. Si oui, on passe à la définition des nouvelles propriétés qui vont étendre notre espace de noms, sinon, la fonction s'arrête là et retourne.

Comprenez bien que si la variable n'est pas celle que vous croyez, un message d'alerte vous le dira :).

II-D - Les sous-espaces de noms

Maintenant vous savez regrouper vos sources dans un domaine et créer différents domaines pour des contextes spécifiques. C'est pas mal, mais vous pouvez encore améliorer la donne en ajoutant plusieurs niveaux de nommage. Pourquoi plusieurs niveaux de nommage ?

Tout d'abord si vous avez créé différents espaces de noms, c'est pour éviter les conflits de noms et regrouper vos fonctionnalités suivant des contextes particuliers. Mais de la même façon qu'il pouvait être pertinent d'avoir deux fonctionnalités portant le même nom, il en est de même pour les espaces de noms (et là vous commencez à avoir mal à la tête).

Ensuite, il serait bien pratique de n'avoir qu'une seule entrée pour identifier les fonctionnalités provenant de votre (vos) bibliothèque(s) et structurer suivant une hiérarchie pertinente vos fonctionnalités. En plus, n'avoir qu'une entrée vous permet de n'avoir à vous soucier que d'une possibilité de conflit de nom entre votre espace de noms global et une variable lambda.

Avec ce que vous avez appris précédemment, vous avez tous les outils pour agrémenter votre espace de noms principal de sous-espaces. Comme vous l'avez vu, un espace de noms en JavaScript est un objet dont les propriétés sont les fonctionnalités de votre bibliothèque. De la même façon, vous pouvez créer une propriété qui aura elle-même comme propriétés d'autres fonctionnalités, il s'agit alors d'un sous-espace de noms, un espace dans l'espace.

Ajoutons à notre espace de nom précédent deux sous-espaces et un sous-espace à l'un d'entre eux. Nous aurons alors 3 niveaux de profondeur.

Ajouter des sous-espaces de noms

myNamespace.subNamespace1 = {
displayMsg : function(){
alert("you're in subNamespace1");
}
};

myNamespace.subNamespace2 = {
displayMsg : function(){
alert("you're in subNamespace2");
}
};

myNamespace.subNamespace1.subSubNamespace = {
displayMsg : function(){
alert("you're in subSubNamespace");
}
};

Testons les extensions de notre espace de noms global :

Utiliser les sous-espaces de noms

myNamespace.subNamespace1.displayMsg();
myNamespace.subNamespace2.displayMsg();
myNamespace.subNamespace1.subSubNamespace.displayMsg();

// you're in subNamespace1
// you're in subNamespace2
// you're in subSubNamespace

Vous constatez qu'au premier niveau vous avez les fonctionnalités de l'espace de noms myNamespace (var1, var2, displayVar1, displayVar2, createObject), au second niveau les fonctionnalités de subNamespace1 et subNamespace2 et au troisième niveau les fonctionnalités de subSubNamespace !

II-E - Les limites des espaces de noms en JavaScript

Jusque là nous avons été super optimistes sur notre conception d'un espace de noms en JavaScript. Pourtant, j'ai dit au début de cet article que la notion d'espace de noms n'existe pas dans ce langage et que ce que nous pouvions faire, au mieux, c'est l'émuler. Tout ceci implique certaines limitations.

Le JavaScript est un langage interprété. Aucune vérification n'est faite à la compilation puisqu'il n'y a pas de compilation. Donc si un problème survient c'est à l'exécution, ce qui est très embêtant pour l'utilisateur (et pour vous en tant que concepteur). C'est pourquoi nous avons fait ce que nous pouvions pour détecter en amont les problèmes possibles et prévenir par une alerte le cas échéant et en stoppant les traitements voués à l'échec.

Pourtant nous ne sommes pas capables de prévenir tous les problèmes. La principale limitation est que nous ne pouvons pas nous assurer de la bonne utilisation de notre bibliothèque par des tiers. Entre autres ils peuvent :

- écraser tout ou partie des fonctionnalités de notre bibliothèque en les redéfinissant (vil gredin !).

Si cette fonction était utilisée quelque part en interne dans votre bibliothèque autant dire que ça ne marche plus.

Écrasement d'une fonctionnalité dans le code client

// Script utilisateur
myNamespace.displayVar1 = null;

- écraser nos tests de validité sur le chargement de la page en affectant un callback sur l'événement onload façon old school (traditional binding).

Écrasement du test de validité de l'espace de noms dans le code client

// Script utilisateur après l'inclusion du Listing 3 8
window.onload = function(){ alert("gotcha !!") } ;

- écraser notre espace de noms APRÈS que l'on ait testé sa validité en ajoutant un autre callback à la suite du nôtre (avec addEventListener ou attachEvent suivant le navigateur). Auquel cas il n'y a pas d'alerte pour le prévenir du méfait.

Contournement du test de validité de l'espace de noms dans le code client

// Script utilisateur après l'inclusion du Listing 3 8
var f = function()
{
myNamespace = null;
alert("gotcha again !!");
}

if (window.addEventListener) // non-IE browser
{
window.addEventListener("load",f,false);
}
else if (window.attachEvent) // IE browser
{
window.attachEvent("onload",f);
}

- utiliser des raccourcis pour ne pas avoir à écrire le nom de l'espace ou du sous-espace de noms, de la classe, de la variable, de la fonction en entier. De ce fait nous ne pouvons garantir que sa nouvelle variable ne sera pas écrasée par une autre qu'il utilisera ailleurs.

Ce problème s'apparente à celui que l'on peut rencontrer dans des langages compilés comme le C++ où l'on utilise l'instruction using pour ne pas avoir à préfixer les fonctionnalités par leur espace de noms, ce qui peut induire des ambiguïtés. Donc à utiliser avec d'extrêmes précautions, voire à proscrire.

Utilisation de raccourcis d'écriture dans le code client

// Script utilisateur
var raccourci = myNamespace.displayVar1;
raccourci() ; // affiche la valeur de myNamespace.var1
[...]
raccourci = myNamespace.subNamespace1.displayMsg;
raccourci() ; // affiche "you're in subNamespace1"
raccourci = myNamespace.var1;
[...]
raccourci(); // ERREUR raccourci is not a function

Mais tout ça n'est pas vraiment de notre ressort. Nous avons fait le maximum pour qu'il n'y ait pas de problème (on peut aussi mettre à disposition une documentation indiquant aux utilisateurs ce qu'il faut faire ou ne pas faire pour que ça marche au mieux). Au-delà de la conception de nos bibliothèques, nous ne pouvons garantir aux utilisateurs le bon fonctionnement de leurs programmes s'ils n'utilisent pas correctement nos outils.

- Raccourcis d'utilisation (var toto = namespace.ns1.ns2 ;) équivalent de using namespace std ; en C++

III - Exemples

Et voilà, nous en sommes à présent à mettre en application tout ce que vous avez appris dans cet article. Je vous propose de mettre en œuvre une mini bibliothèque illustrative et un cas d'utilisation, ce qui vous permettra d'appréhender dans sa globalité la notion d'espace de noms en JavaScript.

III-A - Votre bibliothèque : myOwnJSLibrary.js

myOwnJSLibrary.js

var myOwnJSLibrary = {
author : "Nourdine FALOLA",

addEventListener : function(o,typeEvent,callback){
if (o.addEventListener){
o.addEventListener(typeEvent,callback,false);
}
else if (o.attachEvent){
o.attachEvent("on" + typeEvent,callback);
}
},

displayException : function(){
if (arguments.length < 1){
// pas de paramètres passés à la fonction
return;
}

var exc = arguments[0];
if (!(exc instanceof Object) || exc.message === undefined){
// le paramètre n'est pas une exception
return;
}

// on affiche toutes les propriétés de l'exception
// (elles diffèrent suivant le navigateur)
var msg = "";
for (var prop in exc){
msg += exc[prop] + "\n";
}
alert(msg);
}

};
>

III-B - myOwnJSLibrary.Math.js

myOwnJSLibrary.Math.js

(function(){
var f = function(){
if (typeof myOwnJSLibrary == "undefined"){
alert("the namespace myOwnJSLibrary was deleted.");
return false;
}
else if (typeof myOwnJSLibrary.author == "undefined" || myOwnJSLibrary.author != "Nourdine FALOLA"){
alert("the namespace myOwnJSLibrary was altered.");
return false;
}

return true;
}

if (window.addEventListener){ // non-IE browser
window.addEventListener("load",f,false);
}
else if (window.attachEvent){ // IE browser
window.attachEvent("onload",f);
}

if (!f())
return;

// extends myOwnJSLibrary

myOwnJSLibrary.Math = {

PI : Math.PI,

add : function(){
try{
switch(arguments.length){
case 0:
return undefined;
case 1:
if (typeof arguments[0] != "number"){
return undefined;
}
return arguments[0];
default:
var ret = 0;
for (var i=0; i<arguments.length; i++){
if (typeof arguments[i] != "number"){
return undefined;
}
ret += arguments[i];
}
return ret;
}
}
catch(exc){
myOwnJSLibrary.displayException(exc);
}
}
};

})(); // run the anonymous function

III-C - myOwnJSLibrary.Math.Statistic.js

myOwnJSLibrary.Math.Statistic.js

(function(){
var f = function(){
if (typeof myOwnJSLibrary == "undefined"){
alert("the namespace myOwnJSLibrary was deleted.");
return false;
}
else if (typeof myOwnJSLibrary.author == "undefined" || myOwnJSLibrary.author != "Nourdine FALOLA"){
alert("the namespace myOwnJSLibrary was altered.");
return false;
}
else if (typeof myOwnJSLibrary.Math == "undefined"){
alert("the namespace myOwnJSLibrary.Math was deleted.");
return false;
}

return true;
}

if (window.addEventListener){ // non-IE browser
window.addEventListener("load",f,false);
}
else if (window.attachEvent){ // IE browser
window.attachEvent("onload",f);
}

if (!f())
return;

// extends myOwnJSLibrary.Math

myOwnJSLibrary.Math.Statistic = {

fact : function(n){
try{
if (n>0){
return n*this.fact(n-1);
}
else if (n==0){
return 1;
}

return undefined;
}
catch(exc){
myOwnJSLibrary.displayException(exc);
}
},

combi : function(n,k){
if (k>n){
return undefined;
}

return this.fact(n)/(this.fact(k)*this.fact(n-k));
}

};

})(); // run the anonymous function

III-D - myOwnJSLibrary.String.js

myOwnJSLibrary.String.js

(function(){
var f = function(){
if (typeof myOwnJSLibrary == "undefined"){
alert("the namespace myOwnJSLibrary was deleted.");
return false;
}
else if (typeof myOwnJSLibrary.author == "undefined" || myOwnJSLibrary.author != "Nourdine FALOLA"){
alert("the namespace myOwnJSLibrary was altered.");
return false;
}

return true;
}

if (window.addEventListener){ // non-IE browser
window.addEventListener("load",f,false);
}
else if (window.attachEvent){ // IE browser
window.attachEvent("onload",f);
}

if (!f())
return;

// extends myOwnJSLibrary

myOwnJSLibrary.String = {

add : function(){
try{
switch(arguments.length){
case 0:
return undefined;
case 1:
return "" + arguments[0];
default:
var ret = "";
for (var i=0; i<arguments.length; i++){
ret += arguments[i];
}
return ret;
}
}
catch(exc){
myOwnJSLibrary.displayException(exc);
}
},

format : function(){
switch(arguments.length){
case 0:
return undefined;
case 1:
return "" + arguments[0];
default:
var ret = arguments[0];
var expr = "\\{n\\}";

for (var i=1; i<arguments.length; i++){
var motif = new RegExp(expr.replace("n",i-1),"g");
ret = ret.replace(motif,"" + arguments[i]);
}
return ret;
}
}

};

})(); // run the anonymous function

III-E - Utilisation dans un code client : test.html

Code client utilisant la bibliothèque myOwnJSLibrary

<html>
<head>
<script type="text/javascript" src="myOwnJSLibrary.js"></script>
<script type="text/javascript" src="myOwnJSLibrary.Math.js"></script>
<script type="text/javascript" src="myOwnJSLibrary.Math.Statistic.js"></script>
<script type="text/javascript" src="myOwnJSLibrary.String.js"></script>
<script type="text/javascript">
myOwnJSLibrary.addEventListener(window, "load", function(){

try{
alert(myOwnJSLibrary.Math.PI);
alert(myOwnJSLibrary.Math.add(5,6,7,8,9));
alert(myOwnJSLibrary.Math.add(1,7,"8",9));
alert(myOwnJSLibrary.Math.Statistic.combi(4,2));
alert(myOwnJSLibrary.String.add(5,6,7,"8",9));
alert(myOwnJSLibrary.String.add(null,6,7,"8",{ name : "un objet" }, "viva españa"));
alert(myOwnJSLibrary.String.format("{0}, {1}",{},123));
}
catch(exc){
myOwnJSLibrary.displayException(exc);
}

});
</script>
</head>
<body>
</body>
</html>

IV - Annexe

IV-A - Le mot-clé « this »

Dans des langages objets comme Java ou C++, this désigne au sein d'une classe l'instance de la classe sur laquelle on agit. Ce n'est pas le cas en JavaScript. En JavaScript, « this » désigne l'objet qui appelle la méthode dans laquelle il se trouve (oui il y a une subtile différence !). Prenons un exemple :

this en JavaScript

function who(){
alert(this.name);
}

var o1 = { name : "objet 1"  };
var o2 = { name : "objet 2"  };

o1.who1 = who;
o2.who2 = who;

o1.who1(); // affiche "objet 1"
o2.who2(); // affiche "objet 2"

S'il s'agit d'une méthode au sein d'une classe, vous utilisez le this de la même manière qu'en C++.

Utiliser this comme en C++

function MyClass(){
this.name = "instance of MyClass";
this.who = function(){
alert(this.name);
};
}

var obj = new MyClass();
obj.who(); // "instance of MyClass"
obj.name = "new instance of MyClass";
obj.who(); // "new instance of MyClass"

Cependant il s'agit toujours en réalité de la règle de l'objet appelant la méthode. Si vous affectez la méthode who à un nouvel objet qui n'est pas une instance de MyClass, this fait maintenant référence à cet objet.

this relatif à l'objet appelant

var weird = {};
weird.name = "weird object";
weird.who = obj.who;
weird.who(); // "weird object"

IV-B - Les fonctions anonymes

Vous avez vu dans cet article qu'on utilisait la syntaxe suivante :

Syntaxe d'une fonction anonyme

(function(){
// votre code ici
})();

Cela est strictement équivalent à :

Écriture équivalente à une fonction anonyme

function myFunction(){
// votre code ici
}
myFunction();

C'est-à-dire qu'il y a une définition de fonction suivie de l'exécution de cette dernière. La différence entre les deux notations est que dans la première la fonction ne porte pas de nom, c'est pourquoi on désigne ce genre de fonction comme étant une fonction anonyme.

L'intérêt des fonctions anonymes est de pouvoir exécuter du code à l'issu de la définition de celui-ci ou encore d'affecter une fonction à un gestionnaire d'événement (ou autre) sans avoir besoin de nommer la fonction en la déclarant dans un premier temps, puis en l'utilisant ensuite. En ce qui concerne la première utilisation, on peut obtenir la même chose sans encapsuler le code dans une fonction. Cependant en encapsulant votre code dans une fonction anonyme, vous réduisez la portée des variables qui y sont déclarées. Vous travaillez dans un contexte local à la fonction.

Contexte local à une fonction anonyme

/* contexte local à la fonction anonyme */
var n = 10;
var foo = function(){
alert("global foo");
};

alert(n); // 10
foo(); // global foo

(function(){
var n = 50;
var foo = function (){
alert("anonymous foo");
};

alert(n); // 50
foo(); // anonymous foo
})();

alert(n); // 10
foo(); // global foo

Affectation d'une fonction anonyme à une propriété d'un objet

/* fonction anonyme et gestionnaire d'événement */
objet.onclick = function (event){
// votre code ici
}

Vous pouvez exploiter cet état de fait pour simuler ce qu'on appelle des variables et méthodes privées dans d'autres langages objets. En effet les variables et méthodes définies dans une fonction anonymes ne sont accessibles que dans cette méthode. C'est une façon d'encapsuler les fonctionnalités.

Au passage, remarquez qu'on utilise les fonctions anonymes (et le mot-clé this) avec la notation JSON :

Notation JSON, fonction anonyme et this

var obj = {
var1 : "variable 1",
var2 : "variable 2",
getVars : function(){
alert(this.var1 + " et " +  this.var2);
}
};

IV-C - Importation de symboles

À la section 3.5, je vous énumérais les limites des espaces de noms en JavaScript. En particulier je vous montrais avec le Listing 3 15 qu'un utilisateur de votre espace de noms pouvait utiliser des raccourcis d'écriture et qu'une utilisation maladroite pouvait causer des problèmes.

Cependant, à l'intérieur de votre librairie, vous pouvez vous-même utiliser ce genre de raccourcis d'écriture si la notation complète commence à devenir fastidieuse avec de multiples niveaux de noms. Si votre code est encapsulé dans une fonction anonyme, les raccourcis que vous emploierez seront définis localement à cette fonction et n'interfèrerons pas avec le code utilisateur. Par contre, à votre charge de ne pas utiliser les mêmes noms dans une même portée (et même dans des portées différentes au risque de semer la confusion).

Cette technique porte le nom d'importation de symboles.

Importation de symboles

myLibrary.sub1.sub2.sub3.Event = {
namespace :  "myLibrary.sub1.sub2.sub3.Event"
}
[...]
var EVENT = myLibrary.sub1.sub2.sub3.Event;
alert(EVENT.namespace); // "myLibrary.sub1.sub2.sub3.Event"

V - Remerciements

Je remercie Didier Mouronval (aka Bovino) pour sa relecture attentive, Pierre Schwartz (aka Khayyam90) pour ses conseils et Grégory Dumas (aka freegreg) pour son aide à la génération de cet article. Je remercie toute l'équipe Web pour l'intérêt et les conseils manifestés pour cet article.

VI - Toutes les publications

VI-A - Espaces de noms (ou namespace) en JavaScript

Cet article s'adresse à une population de développeurs déjà familière avec la problématique du développement Web et le langage JavaScript. Dans cet article, vous apprendrez comment packager vos bibliothèques en simulant en JavaScript ce que l'on appelle un espace de noms (« namespace » en anglais). Vous pouvez participer à la discussion relative à cet article sur le site communautaire www.developpez.com. Commentez

lire l'article

VI-B - Conception de contrôles Web avancés : Créer une table HTML éditable - HtmlEditTable v1.0

Ce tutoriel est le premier d'une série consacrée au développement de contrôles Web avancés en JavaScript. Vous allez apprendre au cours de votre lecture à penser et concevoir des outils plus complexes au travers d'une étude de cas : la conception d'une table éditable. Vous pouvez participer à la discussion relative à cet article sur le site communautaire www.developpez.com. Commentez

lire l'article

VI-C - Conception de contrôles Web avancés : Créer une table HTML éditable - HtmlEditTable v2.0

Dans un précédent tutoriel, nous avons conçu une table HTML éditable en JavaScript : HtmlEditTable v1.0. Pour la v2.0, nous allons réviser un peu le code et ajouter les fonctionnalités suivantes :

- création d'un HtmlEditTable à partir d'une table HTML existante ;

- navigation au clavier.

Vous pouvez participer à la discussion relative à cet article sur le site communautaire www.developpez.com. Commentez

lire l'article

VI-D - Bibliothèque d'effets JavaScript : Smile FX

En cours…

voir la démo