Il y a 10 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.
Commentaire