En JavaScript, les objets permettent de structurer des données dynamiques. Bien qu'il soit courant de créer et de modifier des propriétés librement, le moteur JavaScript offre un contrôle granulaire sur la mutabilité de ces objets et de leurs membres via des attributs internes.
Les Attrbiuts des Propriétés de Données
Chaque propriété d'un objet possède des caractéristiques spécifiques définies par ce qu'on appelle un descripteur :
- value : La valeur effective stockée par la propriété.
- writable : Détermine si la valeur peut être modifiée par une simple assignation.
- enumerable : Indique si la propriété est visible lors d'itérations (ex:
for...inouObject.keys()). - configurable : Autorise ou non la suppression de la propriété ainsi que la modification de ses autres attributs (à l'exception de
writablepassant detrueàfalse).
Pour inspecter ces métadonnées, l'API JavaScript met à disposition Object.getOwnPropertyDescriptor(). Inversement, Object.defineProperty() permet de les redéfinir.
const userConfig = { id: 1, role: "admin" };
// Inspection des attributs par défaut
console.log(Object.getOwnPropertyDescriptor(userConfig, 'id'));
// { value: 1, writable: true, enumerable: true, configurable: true }
// Modification de la valeur via defineProperty
Object.defineProperty(userConfig, 'id', { value: 99 });
console.log(userConfig.id); // 99
Restreindre la Mutabilité : Writable et Enumerable
En passant writable à false, la propriété devient immuable par assignation directe. Cependant, si la propriété reste configurable, il est toujours possible de contourner cette restriction en modifiant l'attribut value directement via Object.defineProperty().
const settings = { port: 8080 };
Object.defineProperty(settings, 'port', { writable: false });
settings.port = 3000; // Échec silencieux (ou TypeError en mode strict)
console.log(settings.port); // 8080
// Contournement via defineProperty
Object.defineProperty(settings, 'port', { value: 3000 });
console.log(settings.port); // 3000
L'attribut enumerable permet de masquer une propriété des boucles d'itération, un comportement similaire aux propriétés héritées du prototype qui sont non-énumérables par défaut.
const hiddenData = { token: "xyz", visible: true };
Object.defineProperty(hiddenData, 'token', { enumerable: false });
for (const key in hiddenData) {
console.log(key); // N'affiche que "visible"
}
Le Verrouillage avec Configurable
L'attribut configurable est fondamental car son verrouillage est irréversible. Une fois à false, il est impossible de repasser configurable à true, de modifier enumerable, ou de supprimer la propriété. Néanmoins, si la propriété est initialement writable: true, il reste possible de modifier sa value et de la rendre writable: false. Si la propriété est déjà writable: false, aucune modification de value ou de writable n'est permise.
const lockedObj = { counter: 0 };
Object.defineProperty(lockedObj, 'counter', { configurable: false });
// Impossible de la supprimer
delete lockedObj.counter;
console.log(lockedObj.counter); // 0
// Si on tente de la reconfigurer, cela lève une exception
try {
Object.defineProperty(lockedObj, 'counter', { configurable: true });
} catch (e) {
console.log(e.message); // Cannot redefine property: counter
}
// Transition writable true -> false autorisée
Object.defineProperty(lockedObj, 'counter', { writable: false });
// Tentative de modification de valeur sur une propriété non-configurable et non-writable
try {
Object.defineProperty(lockedObj, 'counter', { value: 100 });
} catch (e) {
console.log(e.message); // Cannot redefine property: counter
}
États Globaux de l'Objet
Au-delà des propriétés individuelles, l'objet lui-même possède un statut global contrôlant son extensibilité (la capacité à ajouter de nouvelles propriétés) :
- Non-extensible (
Object.preventExtensions()) : Empêche l'ajout de nouvelles propriétés ou la modification du prototype. - Scellé (
Object.seal()) : L'objet devient non-extensible et toutes ses propriétés propres passent enconfigurable: false. - Gelé (
Object.freeze()) : L'objet est scellé et toutes ses propriétés propres deviennentwritable: false.
const systemState = { status: "online" };
// Gel de l'objet
Object.freeze(systemState);
console.log(Object.isFrozen(systemState)); // true
console.log(Object.isSealed(systemState)); // true (un objet gelé est implicitement scellé)
console.log(Object.isExtensible(systemState)); // false
systemState.status = "offline"; // Échec
console.log(systemState.status); // online
Propriétés d'Accès (Getters et Setters)
Les objets ne se limitent pas aux propriétés de données. Il existe des propriétés d'accès définies par des fonctions get et set. Celles-ci ne possèdent pas d'attributs value ni writable, mais retiennent enumerable et configurable. Il est possible de convertir une propriété de données en propriété d'accès, et inversement, à condition que la propriété soit configurable: true.
const account = {
balance: 100,
get currency() {
return "EUR";
}
};
// Conversion d'une propriété d'accès en propriété de données
Object.defineProperty(account, 'currency', { value: "USD", writable: true });
console.log(Object.getOwnPropertyDescriptor(account, 'currency'));
// { value: 'USD', writable: true, enumerable: true, configurable: true }
// Conversion d'une propriété de données en propriété d'accès en lecture seule
Object.defineProperty(account, 'balance', {
get() { return 999; },
enumerable: true,
configurable: true
});
console.log(account.balance); // 999
account.balance = 0; // Échec (pas de setter défini)
console.log(account.balance); // 999