version 1.11, dernière mise à jour le 11 décembre 2018.
Il arrive que des variables soient redéfinies entre plusieurs scripts épars dans des fichiers distincts. Une redéfinition malencontreuse d'une variable peut perturber le fonctionnement d'un script, et il faut donc au maximum limiter le risque d'interférences entre deux variables. La toute première précaution à prendre est donc de limiter au maximum la portée d'une variable, en recourant à la déclaration var.
On pourrait penser à utiliser des noms de variables très explicites, voire à les préfixer ou les suffixer, comme par exemple monCodeAMoiRienQuAMoi_maVariable
, mais il faut pour cela trouver un préfixe unique (celui de l'exemple précédent a certes peu de chances d'être repris !), et surtout, par la suite, se garder des fautes de frappe. Qui plus est, cela a comme inconvénient que si par extraordinaire, le préfixe est utilisé dans un autre fichier, l'ensemble des variables et fonctions doit être renommé. Ce n'est donc pas une solution très efficace.
JavaScript
ne permet pas encore comme d'autres langages de définir des classes, et d'instancier ensuite des objets. Mais il est possible de créer directement des objets, grâce au mot-clef Object
.
var objet1 = new Object ;
objet1.chaine="Bonjour!" ;
objet1.affiche=function(){
alert(this.chaine) ;
}
objet1.affiche() ;
La déclaration précédente définit l'objet objet1
, lui attache une propriété (chaine
) et une méthode que l'on appelle ensuite. Ainsi, la méthode est associée à cet objet uniquement, et il n'y a pas de risque que sa définition écrase une autre définition qui serait donnée pour un autre objet.
Il est possible d'aller un peu plus loin dans la création d'un objet en utilisant un « littéral objet », mais cette méthode ne fonctionne pas pour les navigateurs très anciens (Netscape
jusqu'à la version 3 et Internet Explorer
jusqu'à la version 4). La déclaration précédente s'écrit alors...
var
objet1 =
{
chaine:"Bonjour!",
affiche:function()
{
alert(this.chaine) ;
}
}
objet1.affiche() ;
Cela permet de ne pas avoir à répéter le nom de l'objet défini, réduit donc les risques d'erreur et facilite la maintenance et la reprise ultérieure du code. Attention, dans l'exemple précédent, les lignes dans le littéral sont séparées par des virgules et non des points-virgules, et les définitions utilisent des :
et non des signes =
.
On peut aussi supprimer une propriété existante à l'aide de l'opérateur delete
: par exemple, ici, delete objet1.chaine
supprime la propriété d'objet1
. Cela ne supprime pas la propriété du prototype, mais uniquement de l'instanciation spécifiée. En mode strict, tenter cette opération sur une propriété non modifiable (par exemple, définie par défaut dans JavaScript
comme String.length
) lève une exception et déclenche une TypeError
.
La manipulation d'un littéral objet peut vite s'avérer fastidieuse et source d'erreurs. Mais Javascript
offre une manière assez élégante pour aller assez loin dans la définition d'objets. Cette méthode repose sur les propriétés des fonctions. En Javascript
en effet, les fonctions sont des objets de type function
. À ce titre, elles possèdent comme tout objet des propriétés et des méthodes (qui sont donc des fonctions appliquées à des fonctions...). Cela ouvre des perspectives intéressantes.
Commençons par créer une fonction de base, qui va nous servir de « prototype » (nous verrons par la suite que ce terme n'est pas utilisé sans raison) :
function Personne (prenom, genre, dateNaissance, caractere)
{
this.prenom=prenom ;
this.genre=genre ;
this.dateNaissance=dateNaissance ;
this.caractere=caractere ;
}
var anatole = new Personne ("Anatole", "homme", 1990, "frivole") ;
alert(anatole.genre);//renvoie "homme"
var bernadette = new Personne ("Bernadette", "femme", 1991, "très chouette") ;
alert(bernadette.genre);//renvoie "femme"
Cette solution, pour être simple, n'en présente pas moins deux inconvénients :
les propriétés ne sont pas privées, et on peut y accéder (et les modifier) à volonté ;
les arguments doivent être passés dans un certain ordre.
Il est possible d'aller encore un peu plus loin. En effet, il n'est parfois pas souhaitable que certaines propriétés d'un objet soient accessibles de l'« extérieur » du script. En programmation orientée objet, on fait appel, dans une classe, à des membres dits privés et d'autres publics. Il est possible de faire de même en javascript, en utilisant le fait que la portée des variables déclarées dans une fonction y est limitée. Ainsi,
function Personne (prenom, genre, dateNaissance, caractere)
{
var personnePrenom=prenom ;
var personneGenre=genre ;
var personneDateNaissance=dateNaissance ;
var personneCaractere=caractere ;
this.getPrenom = function()
{
return personnePrenom ;
} ;
this.setPrenom = function(prenom)
{
personnePrenom=prenom ;
} ;
}
var anatole = new Personne ("Anatole", "homme", 1990, "frivole") ;
alert(anatole.personnePrenom);//renvoie undefined car personnePrenom est une propriété privée
alert(anatole.getPrenom());//renvoie Anatole
var bernadette = new Personne ("Bernadette", "femme", 1991, "très chouette") ;
bernadette.setPrenom("Noémie") ;
alert(bernadette.getPrenom());//renvoie Noémie
Afin de ne pas être contraint dans l'appel de la fonction dans l'ordre des arguments, on peut lui fournir un objet, et en profiter pour proposer des valeurs par défaut si certaines propriétés sont manquantes (la méthode ES6 ne permet pas de modifier l'ordre de déclaration des paramètres) :
function Personne (fiche)
{
var personnePrenom = typeof fiche.prenom !== 'undefined' ? fiche.prenom : "Nino" ;
var personneGenre = typeof fiche.genre !== 'undefined' ? fiche.genre : "homme" ;
var personneDateNaissance = typeof fiche.dateNaissance !== 'undefined' ? fiche.dateNaissance : "1934" ;
var personneCaractere = typeof fiche.caractere !== 'undefined' ? fiche.caractere : "musicien" ;
this.getPrenom = function()
{
return personnePrenom ;
} ;
this.getDateNaissance = function()
{
return personneDateNaissance ;
} ;
this.setPrenom = function(prenom)
{
personnePrenom=prenom ;
} ;
}
var anatole = new Personne ({prenom:"Anatole", genre:"homme", dateNaissance:1980, caractere:"frivole"}) ;
alert(anatole.getPrenom());//renvoie Anatole
var bernadette = new Personne ( {prenom:"Bernadette", genre:"femme", caractere:"très chouette"}) ;
alert(bernadette.getDateNaissance());//renvoie 1934
Il suffit de déclarer la méthode privée en tant que fonction, à l'intérieur de la fonction de base. Par exemple :
function Personne (fiche)
{
var personnePrenom = typeof fiche.prenom !== 'undefined' ? fiche.prenom : "Nino" ;
var personneGenre = typeof fiche.genre !== 'undefined' ? fiche.genre : "homme" ;
var personneDateNaissance = typeof fiche.dateNaissance !== 'undefined' ? fiche.dateNaissance : "1934" ;
var personneCaractere = typeof fiche.caractere !== 'undefined' ? fiche.caractere : "musicien" ;
function vieillit()
{
personneDateNaissance-- ;
}
this.getPrenom = function()
{
return personnePrenom ;
} ;
this.getDateNaissance = function()
{
return personneDateNaissance ;
} ;
this.setPrenom = function(prenom)
{
personnePrenom=prenom ;
} ;
}
var bernadette = new Personne ( {prenom:"Bernadette", genre:"femme", caractere:"très chouette"}) ;
bernadette.vieillit() ;
alert(bernadette.getDateNaissance());//renvoie 1934
... renvoie un message d'erreur car vieillit
, appliquée à bernadette
, est undefined. Cependant, cela n'empêche pas d'utiliser vieillit
à l'intérieur de Personne
.
Nous avons déjà évoqué la propriété prototype
. Elle s'applique tout aussi facilement à nos objets nouvellement définis, et cela permet de construire un mécanisme en tout point similaire à l'héritage en programmation orientée objet « classique ». Supposons notre prototype Personne
défini comme précédemment. On peut définir un nouveau prototype, qui pourrait être etudiant
, sous la forme :
function Etudiant(fiche, note)
{
Personne.call(fiche, note) ;
var etudiantNote = typeof note !== 'undefined' ? note : 0 ;
this.getNote = function()
{
return etudiantNote ;
} ;
}
Etudiant.prototype = Object.create(Personne.prototype) ;
Etudiant.prototype.constructor = Etudiant ;
On peut alors écrire par exemple...
var marieBerthe = new Etudiant ( {prenom:"Marie-Berthe", genre:"femme", caractere:"experte"},17) ;
alert(marieBerthe.getNote());//renvoie 17
Le mot-clef this
, que nous avons déjà eu l'occasion de rencontrer, a un comportement en apparence déroutant. Il fait en effet référence au contexte dans lequel une fonction est appelée. Considérons l'exemple suivant...
var fratrie = {
prenom1: 'Abel' ,
prenom2: 'Yves' ,
prenom3: 'Hakim' ,
nom: 'Flaille' ,
toutLeMonde: function() {
console.log(this.prenom1 + ', ' + this.prenom2 + ', ' + this.prenom3 + ', ' + this.nom) ;
}
} ;
fratrie.toutLeMonde() ;
var tlm=fratrie.toutLeMonde() ;
tlm() ;
Le premier exemple renvoie "Abel, Yves, Hakim Flaille", le second undefined
. Dans le premier cas en effet, la fonction toutLeMonde
est appelée en tant que méthode de l'objet fratrie
son contexte est donc celui de l'objet appelant, fratrie
et par exemple this.prenom1
renvoie à fratrie.prenom1
. Dans le second cas, la fonction tlm
est appelée au niveau global, donc le contexte est celui de l'objet global, à savoir window
. this.prenom1
renvoie donc à window.prenom1
, qui n'est pas défini...
Un moyen existe pour spécifier l'objet auquel this
doit référer : la méthode bind
de la propriété Function.prototype
, qui est héritée par toute fonction. Il suffit d'écrire ainsi var tlm=fratrie.toutLeMonde.bind(fratrie);
pour que tlm()
renvoie Abel, Yves, Hakim Flaille". On peut aussi expliciter le contexte lors de la définition de la méthode :
var fratrie = {
prenom1: 'Abel' ,
prenom2: 'Yves' ,
prenom3: 'Hakim' ,
nom: 'Flaille' ,
toutLeMonde: function() {
return
this.prenom1 + ', ' + this.prenom2 + ', ' + this.prenom3 + ', ' + this.nom ;
}
} ;
Cette méthode permet de gérer le contexte dans lequel un gestionnaire d'événement est appelé, ou, plus généralement, un callback, c'est-à-dire une fonction passée en paramètre d'une autre fonction. Par exemple...
var fratrie = {
prenom1: 'Abel' ,
prenom2: 'Yves' ,
prenom3: 'Hakim' ,
nom: 'Flaille' ,
toutLeMonde: function() {
document.getElementById("ident").addEventListener("click",function() {
console.log(this.nom) ;
}.bind(this), false) ;
}
} ;
EcmaScript 2015 a introduit une syntaxe de classes dans JavaScript. Cependant, cette syntaxe n'est qu'un vernis sur ce qui reste le fondement de JavaScript, à savoir les prototypes. Partant, un certain nombre de concepts ne sont pas utilisables, et d'autres particularités dérivent du modèle sous-jacent de JavaScript. Par la suite, nous parlerons de classe mais il faut bien garder à l'esprit qu'il ne s'agit pas de classes au même titre qu'en Java, par exemple, mais juste une commodité de notation et de déclaration.
Notamment, toute la définition d'une classe est en mode strict.
Une classe est définie à l'aide du mot-clef class
. Contrairement à la définition de fonctions, une classe doit être définie avant d'être instanciée (contrairement aux définitions de fonction en JavaScript, dont les déclarations peuvent « remonter » —c'est ce qu'on appelle le hoisting. On écrira ainsi…
class maClasse {
constructor(…) {
this.prop1=… ;
}
methode1() {
//Code de la méthode
}
}
let monObjet = new maClasse(…) ;
constructor
est un nom réservé. Par exemple…
class Voiture {
constructor(modele, nombreRoues) {
this.marque=modele ;
this.nombreRoues=nombreRoues ;
}
demarre() {
alert ("Vroum!") ;
}
}
let maPetiteVoiture = new Voiture("Avtoros Shaman", 8) ;
Attention, il n'y a pas de virgule ou de point virgule entre les différents éléments constitutifs de la déclaration. Par exemple, il n'y a pas de virgule entre le constructeur et la définition de la méthode, car il ne s'agit pas d'un littéral objet.
Il n'est actuellement pas possible de définir de propriété, ni publique, ni privée. La mise au point du mécanisme, encore expérimental, dépend encore du navigateur utilisé et on ne peut pas encore l'utiliser en production.
Une méthode publique se définit simplement comme on l'a vu plus haut. Il est cependant possible de définir des méthodes dites « statiques ». Ces méthodes sont propres à la classe et peuvent être utilisées au sein de celle-ci, mais ne peuvent pas être appliquées à une instance en particulier. Par exemple…
class Tab { constructor(tableau){ this.tableau=tableau; } getLongueur () { return this.tableau.length; } item(i) { return this.tableau[i]; } static tabSoustrait(a, b){ var res=new Array; for (let i=0;i<a.getLongueur();res.push(a.item(i)-b.item(i++))); return res; } } var x = new Tab([5,6,7]); var y = new Tab([3,3,12]); console.log(Tab.tabSoustrait(x,y));
En passant, vous pouvez d'ailleurs remarquer dans l'exemple précédent qu'on ne peut pas écrire directement a.length
ou a[i]
dans la méthode statique, puisque a
et b
ne sont pas de « vrais » tableaux. On doit recourir à des définitions de méthodes.
On peut créer une sous-classe d'une classe existante avec l'instruction extends
. Cette instruction permet d'étendre notamment la définition des méthodes de la classe parente, voire de créer de nouvelles méthodes. Par exemple…
class SportCollectif { constructor(nom, nbjoueurs){ this.nom=nom; this.nbjoueurs=nbjoueurs; } get_joueurs(){ return this.nbjoueurs; } } class Sport15 extends SportCollectif { constructor(nom) { super(nom, 15); } get_joueurs(){ var nomJoueurs=""; if (this.nom=="rugby") nomJoueurs+=" rugbymen"; return this.nbjoueurs+nomJoueurs; } } var sport1 = new SportCollectif("football", 11); var sport2 = new Sport15("sport"); var sport3 = new Sport15("rugby"); console.log(sport1.get_joueurs()); // 11 console.log(sport2.get_joueurs()); // 15 console.log(sport3.get_joueurs()); // 15 rugbymen
Cette création est mise à disposition par Gilles Chagnon sous un contrat Creative Commons.