Nous allons créer le constructeur d'un objet que nous appellerons "Rectangle".

En voici le texte :

  function Rectangle (Longueur,Largeur){
    this.Longueur = Longueur;
    this.Largeur = Largeur; 
  }
  var MonRect = new Rectangle();

Ce constructeur reçoit en paramètre les tailles du rectangle et les affecte aux propriétés Longueur et Largeur.

A noter que ...

  • le fait de donner le même nom aux paramètres du constructeur et aux propriétés n'est pas une obligation, mais cela a l'avantage d'être clair.
  • le terme this. Au moment où le constructeur est appelé, l'objet créé (ici, MonRect) est vide. Ce terme permet de lui faire référence afin de lui donner les propriétés désirées.
  • de ce fait, la fonction constructeur n'a pas de valeur de retour. On peut toutefois avoir un objet en retour. Dans ce cas c'est celui-ci qui est affecté à l'expression new et la valeur de l'objet this est abandonnée.
  • même si le constructeur dispose de paramètres d'initialisation, il peut être appelé sans paramètre effectif. Les propriétés seront créées, mais non affectées.

A ce niveau, essayons d'afficher les propriétés de MonRect...

Vous allez à présent donner des valeurs aux propriétés de MonRect et voir le résultat....

Nous allons maintenant définir trois méthodes pour cet objet Rectangle : une qui calcule la surface, une autre, le périmètre et la dernière, la diagonale

function CalcAire(){
  return this.Longueur*this.Largeur ;
}
function CalcPeri(){
  return 2*this.Longueur + 2*this.Largeur;
}
function CalcDiago(){ 
  with (Math){
    return round(100*sqrt(pow(this.Longueur,2)
                 +pow(this.Largeur,2)))/100;
  }
}

Ces trois méthodes (qui ne sont autres que des fonctions) qui calculent respectivement la surface, le périmètre et la diagonale d'un rectangle sont définies dans un script au niveau global (afin qu'elles puissent être atteintes dans toute la page). On remarque qu'elles calculent les résultats sur des opérandes référencés par this, mais il n'y a pour l'instant aucune association avec le constructeur Rectangle !! On pourrait même imaginer que certaines de ces mêmes fonctions vont être utilisées par d'autres constructeurs (toutes pour un carre , CalcPeri pour un losange,...).

Pour opérer ce lien avec le constructeur Rectangle, , il convient de définir dans celui-ci trois nouvelles propriétés, par exemple, Surface, Perimetre et Diagonale, et d'affecter celles-ci respectivement à Calcaire, CalcPeri et Diago par :

this.Surface = Calcaire;
this.Perimetre = CalcPeri;
this.Diagonale = CalcDiago;



Attention :

Le fait d'avoir écrit : 2*this.Longueur+2*this.Largeur
au lieu de : 2*(this.Longueur+ this.Largeur)n'est pas innocent...
En effet, dans le second cas, l'opérateur + étant commun aux expressions arithmétiques et aux chaînes de caractères, l'interpréteur aurait commencé par concaténer les deux valeurs (en tant que chaînes) avant de transformer le résultat en valeur numérique pour le multiplier par 2. Ainsi, pour une longueur de 12 et une largeur de 6, le périmètre calculé aurait été : 126 * 2, soit 252 au lieu de 36 !!!!

On a annoncé dans le document maître que dans certains contextes, on pouvait réduire l'écriture des objets. Effectivement, nous voyons dans la fonction CalcDiago apparaître le mot with... Ici, les fonctions round (arrondi), sqrt (racine carrée), pow (puissance) sont des méthodes de l'objet Math, si bien qu'il aurait fallu écrire :

     Math.round(100*Math.sqrt(Math.pow(this.Longueur,2)
                            + Math.pow(this.Largeur,2)))/100
L'instruction with permet de préfixer en partie commune toutes les propriétés ou méthodes qui le nécessitent.

Voyons ce que notre objet MonRect est à présent devenu.........

On constate que les valeurs associées à Surface, Perimetre et Diagonale ne sont autres que les fonctions définies plus haut, ce qui prouve bien que les propriétés peuvent contenir n'importe quel objet !!!

Finalement, nous pouvons afficher les différentes valeurs accessibles de MonRect....

Nous allons à présent créer le constructeur d'un objet que nous appellerons "Carre" issu du précédent.

Voyons à présent comment élargir le constructeur Rectangle et créer un constructeur Carre à partir de celui-ci. La propriété du carré est d'avoir deux côtés consécutifs égaux, de sorte que nous voudrions disposer d'un constructeur Carre auquel on ait à donner en paramètre seulement la taille du côté. En créant un carré à partir du constructeur Rectangle, il héritera de ce fait de toutes les propriétés définies pour celui-ci. La fonction servant de constructeur s'écrira de la façon suivante :

 
function Carre(cote){
  var Obj = new Rectangle(cote,cote);
  Obj.cote = cote;
  return Obj;
}

On constate que dans ce constructeur, on passe par l'intermédiaire d'une variable Obj. En effet, la création effective du carré s'opère par l'expression new Rectangle(cote,cote). Si l'on avait voulu utiliser this, il nous aurait fallu écrire this = new Rectangle(cote,cote). Or cette syntaxe est interdite. On construit donc un objet affecté à la variable Obj, variable dont la référence est renvoyée par la fonction (chose qui n'était pas le cas dans le constructeur Rectangle) afin que le Carre créé s'identifie à cet objet.
A noter que l'on a doté cet objet d'une nouvelle propriété : cote.

Voyons à présent ce que cela donne si l'on désire créer un objet MonCarre en fournissant au constructeur la valeur du côté (10 cm).........

Parfait ! tout a l'air de bien fonctionner... Essayons à présent d'affecter après la création du carré une valeur pour le côté...........

Et bien oui ! Cela ne marche pas ! Pourquoi ? Parce que vous demandant de donner une valeur à cote, l'objet MonCarre a été crée sans paramètre afin qu'ensuite on puisse affecter la valeur saisie a cote. MAIS RIEN N'A ÉTÉ AFFECTÉ aux propriétés Longueur et Largeur sur lesquelles fonctionnent les trois méthodes de calcul. Si le constructeur Carre avait été appelé avec un paramètre affecté, tout aurait bien fonctionné, mais il faut que vous puissiez voir tous les situations !!

Pour pallier à ce dysfonctionnement, deux solutions sont possibles. On peut modifier toutes les méthodes de calcul de Rectangle afin qu'elle prennent en compte la propriété cote. Ceci n'est pas conseillé, voire même irréalisable si vous utilisez, à la place de Rectangle, un constructeur prédéfini de JavaScript pour hériter de ses fonctionnalités dans la définition d'un nouveau constructeur (on verra plus loin dans le cours que cette affirmation mérite un bémol). La deuxième solution, la seule envisageable, consiste à forcer la prise en compte dans Largeur et Longueur de la valeur de cote. Il faudrait donc que dans le constructeur de Carre, on identifie Largeur et Longueur à cote. Identifier, ce n'est pas simplement affecter la valeur. D'ailleurs cela serait inutile puisqu'on vient de voir que l'affectation s'opère convenablement dans le constructeur. Ici, il s'agit de ne faire qu'une de ces trois variables de façon à ce que en affectant l'une d'elle, les deux autres le soient simultanément à la même valeur.

Pour que cela puisse se faire, il faudrait que ces variables soient de type référence. Or nous avons vu précédemment dans le cours, que les nombres étaient de type primitif. En conséquence, il n'est pas possible d'opérer des affectations par référence comme nous le voudrions. On doit donc se résoudre à doter le constructeur Carre d'une méthode spécifique qui sera appelée pour affecter la variable cote. Cette méthode, bien sûr, prendra soin, au passage, d'affecter aussi Longueur et Largeur.

function Carre(cote){
  function Affect_Cote(cote){
    this.cote = cote;
    this.Longueur = this.Largeur = cote;
  }
var Obj = new Rectangle(cote,cote); Obj.cote = cote; Obj.Longueur = Obj.Largeur = cote; Obj.Cote = Affect_Cote; return Obj; }

Nous voyons ici la méthode qui devra être appelée pour affecter une valeur au côté du carré. On constate que l'affectation aux deux autres variables est effectué par la même occasion. De la même façon, ces deux variables sont aussi affectées à la même valeur que le côté dans le corps du constructeur. Cela n'est en aucune façon redondant !!! En effet, on peut ainsi affecter une valeur au côté au moment de la création et par ailleurs modifier cette valeur ensuite.

Dans le corps du constructeur, l'affectation Obj.cote = init a non seulement pour fonction de prendre en compte la valeur de l'éventuel paramètre, mais aussi de définir une nouvelle propriété : cote. En ce sens, elle est incontournable !
Par contre les affectations de la longueur et de la largeur auraient pu être conditionnées au fait que cote a bien une valeur.

 

L'affectation à posteriori du côté de MonCarre s'écrira donc : MonCarre.Cote(12);

Finalement, espérons que cela va suffire.........

Ouf ! on y est arrivé... Mais néanmoins quelque chose vous laisse insatisfait, n'est ce pas ? Vous avez bien raison !! ...

Le fait d'être obligé de faire une affectation de façon non naturelle est gênant sur deux plans : d'abord, d'un point de vue purement esthétique et ergonomique, il n'est guère élégant de procéder à l'affectation de cote par MonCarre.Cote(...) plutôt que MonCarre.cote = ... ; ensuite, comment peut-on être assuré qu'à un moment ou un autre, par inadvertance, on n'utilisera pas l'affectation normale, avec les risques qui encourent ? Syntaxiquement correcte, cette affectation fatale ne sera pas mise en défaut par l'analyseur et pourra donc déboucher sur les conséquences fâcheuses que l'on a dénoncées.

A moins que, connaissant les méthodes que les objets de type Carre vont utiliser, on opère une redéfinition de celles-ci permettant, pour parer à toute éventualité, d'affecter les propriétés Longueur et Largeur.

Le constructeur final pourrait donc avoir l'allure suivante où l'on peut voir par la même occasion l'utilisation de littéraux fonctions pour la redéfinition des méthodes.


function Carre(cote){
  var Obj = new Rectangle(cote,cote);
   //En même temps, on affecte  Longueur et Largeur si le constructeur est appelé avec une valeur pour cote.

  Obj.cote = cote;
  with(Obj){
    Obj.Surf=Surface; Obj.Peri=Perimetre; Obj.Diag=Diagonale; //Sauvegarde des fonctions d'objet Rectangle

    // Modification des fonctions dédiées à un objet de type Carre (sur appel de ces fonctions, Largeur et Longueur sont affectées)

    Surface = function(){this.Longueur = this.Largeur = this.cote; return this.Surf()};
    Perimetre = function(){this.Longueur = this.Largeur = this.cote; return this.Peri()};
    Diagonale = function(){this.Longueur = this.Largeur = this.cote; return this.Diag()};
  }
  return Obj;
}

RÉSULTAT FINAL

APPLICATION À UN OBJET PRÉCIS

On pourrait trouver d'autres techniques pour arriver à nos fins... En particulier, nous verrons plus loin dans ce même chapitre du cours, les techniques de prototypage qui permettent de définir de nouvelles propriétés pour tous les objets d'une classe ou de surcharger les propriétés existantes... Cela nous évitera de créer trois nouvelles propriétés relais (Surf, Peri et Diag) et donc d'économiser de la place. Lorsque vous les aurez comprises, réfléchissez à nouveau à notre problème et déterminez si une nouvelle solution plus économique peut être trouvée...