Publié par

Il y a 8 ans -

Temps de lecture 6 minutes

Les propriétés d’objets en Javascript

JavaScript est un langage objet basé sur un modèle de programmation à base de prototype. Cependant, il a manqué jusqu’à très tard de fonctionnalités pour, d’une part, supporter franchement ce type de programmation, et pour, d’autre part, permettre un contrôle fin des propriétés des objets.

Aussi, ECMAScript 5 (le standard derrière JavaScript) a ajouté de nombreuses méthodes à l’objet Object pour faciliter la création et l’extension d’objets. La suite de cet article propose ainsi de voir comment déclarer les propriétés d’un objet JavaScript, la notion de descripteur de propriétés, et enfin comment gérer le cycle de vie de l’objet (extension, mutabilité, etc…).

Créer un objet avec des propriétés

La façon la plus simple de créer un objet en JavaScript reste la notation litérale bien connue :

// batman est un objet "anonyme", contenant 2 propriétés 'firstName', 'lastName'
var batman = {
    firstName: 'Bruce',
    lastName: 'Wayne'
}

JavaScript permet d’ajouter à la volée des propriétés à un objet déjà créé, en utilisant la notation pointée ou la notation avec des crochets. La seconde a l’avantage de permettre l’utilisation de caractères normalement interdits dans le nom de la propriété :

batman.soldeBancaireInCents = 1000000000;
batman['Lieu de vie'] = 'Batcave';
// batman possède maintenant 2 propriétés de plus, 'soldeBancaireInCents' et 'Lieu de vie'

De la même façon, seule la deuxième notation permet d’accéder aux propriétés d’un objet en utilisant une variable :

var propertyName = 'vehicle';
batman[propertyName] = 'Batmobile';

Une autre syntaxe permet de créer un objet en utilisant une fonction constructrice :

var Hero = function(nom, identiteSecrete) {
    this.name = nom;
    this.secretIdentity = identiteSecrete;
};

var robin = new Hero('Robin', 'Dick Grayson'); 
// robin est un objet avec 2 propriétés, "name" et "secretIdentity" 

C’est souvent cette syntaxe qui est utilisée pour simuler un comportement de classe en JavaScript.

Enfin, une dernière syntaxe permet de créer un objet à partir d’un autre objet :

var nightwing = Object.create(robin);

Dans le cas ci-dessus, nightwing est un nouvel objet dont le prototype est robin. Ce type d’approche permet d’avoir un système d’héritage en JavaScript (cela était possible avant l’apparition de cette syntaxe, mais au prix d’une syntaxe plus verbeuse).

Cependant, les approches énumérées ci-dessus ne permettent au final que de déclarer des propriétés publiques, mutables et énumérables. ECMAScript 5 apporte une solution au problème sous la forme de descripteurs de propriété.

Le descripteur de propriété

Le descripteur de propriété est un objet JavaScript décrivant les propriétés d’une propriété. On parle donc de méta-propriété. Le descripteur a la structure suivante :

var descriptor =  {
   value: 'la valeur de ma propriété',
   get: function() { return this.value; }, // getter
   set: function(value) { this.value = value;}, // setter
   writable: true, // la valeur peut-elle être modifiée ?
   enumerable: true, // la propriété apparait-elle dans les for-in et Object.keys ?
   configurable: true // la propriété peut-elle être retirée ?
};

La façon la plus simple d’attacher un descripteur à une propriété est d’utiliser Object.defineProperty :

Object.defineProperty(batman, 'powerOrigin', {
   value: 'meurtre des parents',
   writable: false, // la valeur peut-elle être modifiée ?
   enumerable: true, // la propriété apparait-elle dans les for-in et Object.keys ?
   configurable: false // la propriété peut-elle être retirée ?
});

delete batman['powerOrigin']; // renvoie false et la propriété reste présente
batman.powerOrigin = "morsure d'araignée"; // ne  change rien
console.log(batman.powerOrigin); // affiche toujours "meurtre des parents"

On peut définir plusieurs propriétés avec la méthode Object.defineProperties, et plus intéressant, on peut utiliser la méthode Object.create pour créer un objet à partir d’un prototype et de propriétés :

var batmanBeyond = Object.create(batman, {
    firstname: {
        value:'Terry',
        configurable:false,
        writable:false
    },
    lastname: {
        value:'Mc Ginnis',
        configurable:false,
        writable:false
    },
});

Getter / Setter

Le descripteur de propriété permet de définir des getter et des setter, utilisables de façon transparente sur l’objet.

Object.defineProperty(batman, 'soldeBancaireInDollars', {
         get:function() {
             return batman.soldeBancaireInCents / 100;
         },
         set:function(newValue) {
             batman.soldeBancaireInCents = newValue * 100;
         }
});

console.log(batman.soldeBancaireInCents) // Display 200000
console.log(batman.soldeBancaireInDollars) // Display 2000
batman.soldeBancaireInDollars = 42;
console.log(batman.soldeBancaireInCents) // Display 4200
console.log(batman.soldeBancaireInDollars) // Display 42 

Parcours de propriétés

Un objet JavaScript peut donc voir ses propriétés évoluer dynamiquement, à tel point que l’on utilise souvent les objets comme tableaux associatifs. Il est donc parfois nécessaire de parcourir toutes les propriétés d’un objet, et cela peut être fait avec la structure for-in :

var batSuitsByTypes = {'night':'black suit', 'underwater':'diving suit', 'carnival':'rainbow suit'};

for(var parameter in batSuitsByTypes) {
   console.log(parameter+' => '+batSuitsByTypes[parameter]);
}
// La boucle ci-dessus va afficher :
// night => black suit
// underwater => diving suit
// carnival => rainbow suit

Mais Batman n’assume pas d’avoir un costume arc-en-ciel, on va donc créer un deuxième tableau, qui ne révélera pas ce costume :

var batSuitsByTypes2 = Object.create({'night':'black suit', 'underwater':'diving suit'}, {
   carnival: {
      value: 'rainbow suit',
      enumerable:false
   }
});

console.log(batSuitsByTypes2.carnival); // affiche "rainbow suit"
for(var parameter in batSuitsByTypes2) {
   console.log(parameter+' => '+batSuitsByTypes2[parameter]);
}
// La boucle ci-dessus va afficher :
// night => black suit
// underwater => diving suit

On peut récupérer tous les paramètres énumerables grâce à la méthode Object.keys, et tous les paramètres de l’objet (sans ceux du prototype) avec la méthode Object.getOwnPropertyNames

Object.keys(batSuitsByTypes2) // renvoit ['night','underwater']
Object.getOwnPropertyNames(batSuitsByTypes2) // renvoit ['carnival'] car night et underwater font partie du prototype

Mutabilité et extensibilité des objets

Ecmascript 5 met aussi à disposition des méthodes permettant de gérer le "cycle de vie" des objets, c’est-à-dire l’ajout, la suppression ou la modification de propriétés existantes. La méthode preventExtensions de Object permet ainsi d’empêcher l’ajout de nouvelle propriété à l’objet. Attention cependant, il n’existe pas de méthode pour faire l’opération inverse, c’est-à-dire rendre à nouveau extensible un objet que l’on aurait bloqué.

var batBelt = {batarang:6, batlasso:2, batSmokeGrenage:6};
// Plus de place dans la ceinture, on bloque l'extension :
Object.preventExtensions(batBelt)
batBelt.batMakeup = 1; // l'ajout de propriété ne se fait pas
console.log(batBelt.batMakeup); // affiche undefined

Pour aller plus loin, la méthode seal permet d’empêcher l’ajout de nouvelles propriétés, mais aussi d’empêcher la suppression de propriétés existantes. Attention, cette opération n’est pas non plus réversible.

var batMobile = {wheel:4, motor:1, rearViewMirror:3}
// Batman n'aime pas le tuning, et desire que sa voiture ne puisse pas être modifiée :
Object.seal(batMobile);
batMobile.spoiler = 2; // ne fait rien
delete batMobile.rearViewMirror; // ne fait rien
console.log(batMobile.spoiler); // affiche undefined
console.log(batMobile.rearViewMirror); // affiche 3

Enfin, pour verrouiller complètement un objet, et le rendre immutable, il est possible d’utiliser la méthode freeze, empêchant l’ajout, la suppression et la modification de propriétés. Cette méthode non plus n’est pas réversible.

var joker = {hair:"vert", face:"white"};
Object.freeze(joker);
delete joker.hair; // ne fait rien
joker.lips = "rouge"; // ne fait rien
joker.hair = "blue"; // ne fait rien

console.log(joker.hair); // affiche vert
console.log(joker.lips); // affiche undefined 

En mode strict, toutes les modifications qui ne "font rien" lancent des TypeError.

Conclusion

La relative complexité de ces fonctionnalités, et leur absence d’implémentation dans les navigateurs trop anciens (Internet Explorer 8 et antérieur, par exemple) font qu’elles ne sont actuellement que peu utilisées dans le cadre du développement quotidien. Cependant de nombreux frameworks s’appuient ou vont s’appuyer dessus, gagnant ainsi en lisibilité et en performance.

Publié par

Publié par Benoît Lemoine

Développeur et fier de l'être, Benoit s'intéresse de près à tout ce qui peut permettre de créer une application web, du HTML aux sources de données, en passant par le javascript et les framework haute productivité. twitter : @benoit_lemoine

Commentaire

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Nous recrutons

Être un Sapient, c'est faire partie d'un groupe de passionnés ; C'est l'opportunité de travailler et de partager avec des pairs parmi les plus talentueux.