Algorithmes fonctionnels en javascript sous la forme de cours et d'exercices. Utilisation de js dans un contexte de liste, de map, list_it. Inspiré de caml. A lire à l'envers...

samedi 24 janvier 2009

Objets et javascript

Comment définit on un objet en javascript ?

Pour continuer, un petit guide de survie sur les objets en javascript était nécessaire. Pour bien comprendre les objets il faut utiliser la console firebug et tester à outrance les morceaux de code. Il faut comprendre, que sur une page web, on est en permanence dans un sous objet qui est le contexte d'exécution: l'objet window. Un peu comme un électron tourne autour d'un atome placé dans un manège qui tourne sur la terre qui tourne autour du soleil.. etc...Il existe beaucoup de dogmes sur la programmation objet, qui passe pour beaucoup de programmeur comme le nirvana à atteindre... Certes, la programmation objet présente de nombreux avantages, elle facilite la modularité et la ré-utilisation de fragments de programmes; pour autant elle ne garantie pas la bonne marche du programme. Et surtout, un conseil, fuyez les dogmes en matière de programmation .. faire comme ci/ faire comme cela, la programmation est un art, pas une série de recettes de cuisine à suivre !

Un objet est une collection de paires 'propriété: valeurs', chacune de ces valeurs peut prendre elle même la forme d'un objet ou d'une fonction ou d'un des types javascript. La définition d'un objet en javascript répond à la notion d'arbre récursif.

Les propriétés de cet objet peuvent être soient numérotées comme pour les tableaux, qui ont pour constructeur Array(), soient nommées et stockées sous la forme d'un "tableau associatif" ( monTableau['mon_element'] ) et donc avoir un constructeur du type Object(). Dans les deux cas, la fonction typeof retourne le type "object".

Lorsqu'on se trouve dans un contexte d'objets les fonctions prennent le nom de "méthodes", nous utiliserons donc indifféremment les mots méthodes et fonctions.

Contrairement aux objets java ou C++ qui sont immuables une fois créés, on peut à tout moment modifier un objet et lui rajouter ou retirer des propriétés (donc des fonctions ou méthodes). Certains évitent de parler de classe en javascript car cette notion est différente de celle des langages procéduraux classiques.

Créations d'objets vides en javascript

Il existe ne nombreuses façons de créer un objet vide en javascript :
  • En écriture directe au standard JSON en écrivant var o ={}
  • Strictement équivalente à var o = new Object()
  • Soit en créant une fonction, puis en créant une instance de l'objet avec le mot clé "new". Dans ce cas, la fonction créatrice est appelée "constructeur".
Chaque objet en javascript possède une propriété cachée: "constructor" qui permet de déceler comment l'objet a été construit.


// Création d'un objet vide:
var monObjet={} ; // monObject.constructor // Object()
var monObjet= new Object(); // identique

// Création à partir d'une fonction anonyme :
var points = function(){} ; // pour le moment points est une fonction anonyme
typeof points ; // "function" ; Classe est une variable avec fonction anonyme.
points.constructor; // Function(), noter le F majuscule.
var p = new point() ; // maintenant c'est un object
typeof p ; //"object"
typeof p.constructor; // "function" .. pas étonnant.
// p.constructor et points sont identiques


// Création à partir d'une fonction nommée:
function Points(){} ; // Points est une fonction nommée.
Points.constructor ; // Function()
var p= new Points() ; // objet
p.constructor ; // Points()




Créations d'objets 'pleins'


Plusieurs méthodes sont utilisées pour créer un objet:
  • La notation JSON, qui créée un objet unique de toute pièce en angais :"object litterals"
  • L'utilisation du préfixe new appliqué à une fonction anonyme ou nommée, qui devient alors le constructeur de l'objet,l'utilisation du mot-clé "this" permet de préciser la portée des variables (publique ou privée). Dans ce cas on a l'habitude d'écrire la fonction constructice avec la première lettre en majuscule.
  • Enfin on peut combiner plusieurs techniques



//La notation JSON :permet de créer un objet unique.
monAuto ={
vehicule: 'voiture',
moteur :{ carburant: 'essence', puissance: 4} ,
couleur: 'rouge',
bruit: function(){ return "beq-beq-beq-beq"}
}

monAuto.constructor; // Object()

// On accède ainsi aux propriétés avec la notation point.
console.log("Mon auto est de couleur "+ monAuto.couleur );

// Création d'un objet avec une fonction et le mot clé this:
// la fonction est un constructeur d'objets .
var Moteurs = function(carburant, puissance){
this.carburant= carburant; // variable publique.
this.puissance= puissance;//variable publique.
var etat= "off"; // variable privee, inaccessible de l'extérieur.
// creation de méthodes publiques à l'aide de "this":
this.demarrer= function(){ etat="on" ;} ;
this.arreter = function(){ etat="off";} ;
this.getEtat = function(){ return etat ;};

}
Moteurs.constructor; // Function()
// le constructeur est une fonction anonyme.

m = new Moteurs("essence", "4") ; // nouvelle instance de Moteurs
m.constructor // function() // avec un f minuscule.
m.demarrer();
n = new Moteurs("essence", "4") ;
n.getEtat(); //"off"
m.getEtat(); //"on"



Ajouts de propriétés à un ou plusieurs objets javascript et utilisation du prototype:


On voit plus haut deux objets définis de façon différente, le premier, est un objet unique. On aurait pu également le définir en ajoutant progressivement des éléments supplémentaires sous forme d'un tableau associatif ou avec la notation point, mais attention, dans ce cas l'objet modifié est unique il est mutant par rapport aux autres objets construits de la même façon.

function station(l){return alert("servez-moi "+ l +" litres d' "+ this.carburant) }
monAuto = new Object() ;
monAuto.vehicule = 'voiture'; // équivalent de monAuto['vehicule']
monAuto.carburant = "essence";
monAuto.demarrage= function(){ return "beq-beq-beq-beq" };
monAuto.station = station ; // on lui assigne la fonction station()

monAuto.station(20) ;// Exécution à l'aide de l'opérateur ()


Le prototype permet d'éviter la mutation individuelle de l'objet et la transformer en mutation de classe. Si on applique à un objet,-- à condition qu'il soit créé par une fonction -- une méthode qui n'est pas définie dans son tableau associatif d'origine, elle le cherchera dans son prototype.
Il est donc facile d'utiliser le prototype pour étendre une classe d'objets déjà instanciés. (Ce qui est ahurissant pour une classe en java par exemple).


var Auto= function(){} ; // c'est une fonction
//Un peu d'introspection:
typeof Auto.constructor ; // "function"
typeof Auto.prototype; // "object" : il existe donc un prototype
// lié à la fonction Auto

Auto.prototype = {
etat: 'off',
demarre: function(){ this.etat='on'; } ,
arreter: function(){ this.etat='off' } ,
getEtat: function(){ return this.etat ; }
};


serge = new Auto() ; // new permet de créer une classe d'objets.
seb = new Auto() ;
serge.demarre();
serge.getEtat(); //'off', il 'a pas démarré.
seb.getEtat(); //'on' il a démarré.
serge.etat ; // 'off' la variable est publique;

Auto.etat ; // "undefined" !
Auto.prototype.etat; // 'off'

// Ainsi la création d'une fonction, a pour conséquence immédiate la création d'un
// objet vide "prototype".
// Cet objet prototype, va servir à créer des objets différents
// créés grâce à l'opérateur new en préfixe de la fonction.
// mais ayant les mêmes méthodes de classes.


Enumération des propriétés d'un objet


Pour inspecter un objet, javascript fournit l'instruction for ... in qui permet d'inspecter certaines propriétés d'un objet, qui n'est autre qu'un tableau associatif d'objets ou de variables.

var objet = new Auto() ;
for(var propriete in objet ){
console.log( "Propriété visible: "+ propriete +": " + objet[propriete] ) ;
}

le prototype

Relations entre l'objet prototype et la fonction constructor:

Il existe des propriétés "cachées" c'est à dire non énumérable lors de l'appel de for...in.
  • Lorsque l'on construit un objet à l'aide de "new", javascript lui adjoint une propriété cachée ['constructor'] du type "function" qui indique le fonction qui l'a créé.
  • Chaque fonction possède elle même une propriété cachée "prototype" de type objet, vide au départ, que l'on peut obtenir sous la forme maFonction['prototype']
  • Mais à son tour, chaque objet prototype possède une propriété cachée constructor, objet.prototype['constructor'] qui pointe vers la fonction qui l'à créée

Le prototype est donc un "objet", vide au départ, qui servira a étendre la classe d'objets créés par la "fonction" constructeur de l'objet.

Il existe ainsi une chaîne de prototypes, que l'on peut découvrir à l'aide du constructeur de l'objet:
Attention: le prototype est une source de confusion ... accrochez vous bien et essayez de suivre pour comprendre le prototype ... :


function Point(x,y){ this.x= x; this.y=y};
// une fonction peut construire un objet :

var p= new Point(3,4) ;// on construit l'objet p avec Point()

p.constructor ; // c'est Point(x,y) du type "function"
//car le mot clé new crée la propriété p['constructor']= Point(x,y)


p.constructor.prototype ;//donc identique à Point.prototype : c'est un objet vide.

Point.prototype.description = function(){
return ("x= "+this.x+", y= " + this.y) ;
}
p.description() ; //"x= 3, y= 4"

// Maintenant, l'objet p est doté de la méthode description
// grâce à p.constructor, car si on fait p.description(), javascript recherchera
// dans p.constructor.prototype puis dans , p.constructor.constructor.prototype
// ... jusqu'à épuisement de la chaine de constructeurs
// La méthode description peut être considérée comme une méthode publique:
// tout élément construit
// à l'aide de Point bénéficie de description.


p.constructor.prototype.constructor; // Point(x,y) ça rend fou non ?
// équivaut à Point.prototype.constructor ;
// logique car si on peut écrire :
// var lePrototype = Point['prototype'] // type objet ,
// lePrototype['constructor'] est une propriété de tout prototype
// ici elle vaut Point(x,y)

p.constructor.constructor ;
// Function() : objet Function
// Equivaut à Point.constructor
// Construit à partir de
p.constructor. constructor. prototype;// function() : constructeur de Function
//Avec d'autre objets:
"hé".constructor ; // String() ;
"hé".constructor.prototype ;// [] c'est un objet array.
// On peut donc créer des nouvelles méthodes pour la
// classe String grâce à son prototype.

On retiendra donc:
  • Le prototype est toujours un objet.
  • Le constructor est toujours une fonction.
  • Tout objet possède une propriété 'constructor'
  • Tout constructeur possède un prototype
  • Tout prototype possède une propriété 'constructor' qui le relie à son constructeur

Methodes d'introspection

Il est donc difficile d'inspecter un objet javascript, voici quelques fonctions d'introspection
  • Nous avons vu, for...in
  • typeof ...
  • Son constructeur: instanceof : monTableau instanceof Array

La fameuse Bibliothèque "Prototype.js" utilise la méthode extends pour simuler un héritage:

Object.prototype.extends(destination, source){
for(property in source){
destination[property]=source[property] ;
}
}
Personnellement, j'ai du mal à modifier les objets de base de javascript...mais c'est juste mon opinion.

Utilisation des clôtures,(en anglais closure) pour limiter la portée des variables


Une troisième méthode pour créer un objet est de créer une fonction qui retourne une autre fonction, dans ce cas les variables définies avant le "return" ne sont accessibles que par la fonction retournée, ces variables sont dans une "cloture" ou "closure":

var moteur = function(puissance){
var etat= "off" ; // notre variable privée dans une cloture (closure).
return{ // on retourne un objet JSON qui fait référence à "etat".
puissance : puissance, // variable publique
demarrer :function(){etat="on" ;},
arreter : function(){ etat="off";} ,
status: function(){ return etat ;}
};
}
m = moteur( "4") ;
m.demarrer();
n = moteur("5") ;
n.status(); //"off"
m.status(); //"on"

//
Ici, on crée une fonction qui initialise d'abord tout ce qui est privé puis renvoie une autre fonction. Tout ce qui a été initialisé est dans une clôture (de l'anglais closure), c'est à dire à portée exclusivement de l'objet renvoyé .
La variable "puissance" est modifiable de l'extérieur, c'est donc une variable publique. Alors que la variable etat, n'est plus accessible. Elle est clôturée dans sa fonction mère.
On a donc ainsi obtenu un nouveau type d'objets aux mêmes caractéristiques et au même comportement. Mais attention, ici pas de mot clé new donc pas de prototype associé à
moteur (Arrêtez moi si je me trompe ).

Reste le problème des variables statiques...


Les variables statiques sont des variables qui maintiennent une valeur constante quelquesoit la classe utilisée, un exemple fréquent est dans une classe qui crée des fenêtres, il existe souvent une variable static qui compte le nombre total de fenêtres. Ce compte est disponible et identique pour tous les objets de cette classe.

Javascript ne comporte pas de déclaration de variable statique, cependant, on peut utiliser le fait que toutes les fonctions sont des objets pour simuler une telle variable. Nous allons créer une variable membre de la fonction, qui sera retenue à chaque appel de cet objet.


function a() {
var closure = "set-me" ;
// noter le var qui sous entend que le "scope" ou la portée
// de cette variable est locale: il s'agit d'une variable en cloture.

// On crée maintenant une variable globale: a.static
// Mais pour la première fois il faut l'initialiser.. sinon on l'initialise à chaque
// appel de a.
if ( typeof a.static == 'undefined' ) {
a.static = 0;
}
a.static++ ; // notre variable sert de compteur.
// On renvoie un objet avec une référence privée: closure et une référence statique: static.
return {
getStatic : function(){ return a.static ;} ,
setClosure : function(n){ return closure= n ;},
getClosure: function(n){ return closure ; }

}
};
a(); // static=1
a(); // static=2
a() ; //static=3
var object1 = a() ; //static = 4
object1.setClosure("hello ") ; // object1.closure vaut "hello "
var object2 = a() ; //static= 5
object2.setClosure("world") ; // object2.closure vaut "world"
object2.getStatic() + " " + object2.getStatic()+ " "+ a.static ;
//Résultat : 5 5 5 : la meme valeur pour tous!
object1.getClosure() + object2.getClosure() ; // "hello world"



On a vu ici quelques éléments de programmation orienté objet. Nous n'avons pas tout abordé en particulier, l'héritage, les fonctions apply et call qui peuvent emprunter des méthodes à d'autres objets, nous n'avons pas vu l'isolement du code source dans une seule variable unique...