La classe `Object` en JavaScript
Cet article explique la classe Object en JavaScript.
Cet article explique la classe Object en JavaScript, y compris des exemples pratiques.
YouTube Video
La classe Object en JavaScript
Object est un objet natif qui sert de base à tous les objets JavaScript. De nombreuses fonctionnalités fondamentales du langage, telles que la gestion des propriétés, l’héritage (chaîne de prototypes), l’énumération, la duplication et le gel, sont fournies par le comportement de Object.
Object.create
Il existe plusieurs façons de créer des objets, et il est important de les utiliser en fonction de vos objectifs.
Littéral d’objet (le plus courant)
Le code suivant montre la façon la plus simple et la plus lisible de créer un objet.
1// Create an object using object literal
2const user = {
3 name: "Alice",
4 age: 30,
5 greet() {
6 return `Hello, I'm ${this.name}`;
7 }
8};
9
10console.log(user.greet()); // "Hello, I'm Alice"
- Dans cet exemple, les propriétés et les méthodes sont définies à l’aide de littéraux. C’est simple et cela offre généralement des performances supérieures.
Constructeur new Object()
Le constructeur Object est rarement utilisé, mais il est utile de comprendre son comportement.
1// Create an object using the Object constructor
2const objFromCtor = new Object();
3objFromCtor.x = 10;
4objFromCtor.y = 20;
5
6console.log(objFromCtor); // { x: 10, y: 20 }
new Object()retourne un objet vide, mais le littéral{}est plus court et plus courant.
Spécifier le prototype avec Object.create
Object.create est utilisé pour créer un objet avec un prototype spécifié.
1// Create an object with a specified prototype
2const proto = { hello() { return "hi"; } };
3const obj = Object.create(proto);
4obj.name = "Bob";
5
6console.log(obj.hello()); // "hi"
7console.log(Object.getPrototypeOf(obj) === proto); // true
Object.createest idéal pour un design d’objet basé sur l’héritage, permettant de contrôler précisément la chaîne de prototypes.
Attributs et descripteurs de propriété
Les propriétés possèdent des attributs comme « value », « writable », « enumerable » et « configurable », qui peuvent être contrôlés en détail avec Object.defineProperty.
Exemple de base d’utilisation de defineProperty
Voici un exemple de définition de propriétés non énumérables et en lecture seule à l’aide de defineProperty.
1// Define a non-enumerable read-only property
2const person = { name: "Carol" };
3
4Object.defineProperty(person, "id", {
5 value: 12345,
6 writable: false,
7 enumerable: false,
8 configurable: false
9});
10
11console.log(person.id); // 12345
12console.log(Object.keys(person)); // ["name"] — "id" is non-enumerable
13person.id = 999; // silently fails or throws in strict mode
14console.log(person.id); // still 12345
- L’utilisation de
definePropertypermet de contrôler précisément le comportement des propriétés, comme l’énumération, la réécriture et la suppression.
Propriétés d’accesseurs (getter / setter)
Avec les accesseurs, il est possible d’injecter de la logique lors de la lecture ou de l’écriture de propriétés.
1// Use getter and setter to manage internal state
2const data = {
3 _value: 1,
4 get value() {
5 return this._value;
6 },
7 set value(v) {
8 if (typeof v === "number" && v > 0) {
9 this._value = v;
10 } else {
11 throw new Error("value must be a positive number");
12 }
13 }
14};
15
16console.log(data.value); // 1
17data.value = 5;
18console.log(data.value); // 5
19// data.value = -1; // would throw
- Avec
getteretsetter, l’accès à la propriété peut être traité comme une API externe et permettre l’ajout de validations ou d’effets secondaires.
Prototype et héritage (prototype / __proto__ / Object.getPrototypeOf)
L’héritage en JavaScript repose sur la chaîne de prototypes, et non sur les classes. Les objets peuvent référencer d’autres objets comme prototypes.
Object.getPrototypeOf et Object.setPrototypeOf
L’exemple suivant montre comment inspecter et définir les prototypes.
1// Inspect and change prototype
2const base = { speak() { return "base"; } };
3const derived = Object.create(base);
4console.log(Object.getPrototypeOf(derived) === base); // true
5
6const other = { speak() { return "other"; } };
7Object.setPrototypeOf(derived, other);
8console.log(derived.speak()); // "other"
Object.getPrototypeOfrécupère le prototype d’un objet.Object.setPrototypeOfmodifie le prototype d’un objet existant, mais il convient de l’utiliser avec prudence car cela peut nuire aux performances.
Méthodes intégrées importantes
Nous expliquerons clairement les méthodes les plus couramment utilisées et importantes sélectionnées parmi celles fournies par Object.prototype, ainsi que les méthodes statiques appartenant à Object.
hasOwnProperty, isPrototypeOf, toString, valueOf
hasOwnProperty, isPrototypeOf, toString et valueOf définissent le comportement de base des objets.
1// Demonstrate prototype methods
2const base = { greet() { return "hello"; } };
3const child = Object.create(base);
4const a = { x: 1 };
5
6console.log(a.hasOwnProperty("x")); // true
7console.log(a.hasOwnProperty("toString")); // false — toString is inherited
8
9console.log(a.toString()); // "[object Object]" by default
10
11console.log(base.isPrototypeOf(child)); // true
12console.log(Object.prototype.isPrototypeOf(child)); // true
hasOwnPropertyest une méthode essentielle pour vérifier si une propriété appartient directement à l’objet.isPrototypeOfvérifie si l’objet cible possède cet objet comme prototype.
Object.keys, Object.values, Object.entries
Object.keys, Object.values et Object.entries renvoient les listes des propriétés énumérables propres à un objet. Elles sont utiles pour l’itération et la transformation.
1// Keys, values and entries
2const item = { id: 1, name: "Widget", price: 9.99 };
3
4// ["id", "name", "price"]
5console.log(Object.keys(item));
6
7// [1, "Widget", 9.99]
8console.log(Object.values(item));
9
10// [["id",1], ["name","Widget"], ["price",9.99]]
11console.log(Object.entries(item));- Celles-ci sont fréquemment utilisées pour itérer et transformer des objets.
Object.assign
Object.assign est utilisé pour la copie superficielle et la fusion. Notez que les prototypes et les propriétés d’accesseur ne sont pas copiés.
1// Shallow copy / merge using Object.assign
2const target = { a: 1 };
3const source = { b: 2 };
4const result = Object.assign(target, source);
5
6console.log(result); // { a: 1, b: 2 }
7console.log(target === result); // true (merged into target)
- Pour les objets imbriqués, seules les références sont copiées, donc une autre implémentation est nécessaire pour un clonage profond.
Object.freeze, Object.seal, Object.preventExtensions
Object.freeze, Object.seal et Object.preventExtensions contrôlent la mutabilité des objets.
1// Freeze vs seal vs preventExtensions
2const obj = { a: 1 };
3Object.freeze(obj);
4obj.a = 2; // fails silently or throws in strict mode
5delete obj.a; // fails
6
7const obj2 = { b: 2 };
8Object.seal(obj2);
9obj2.b = 3; // allowed
10// delete obj2.b; // fails
11
12const obj3 = { c: 3 };
13Object.preventExtensions(obj3);
14obj3.d = 4; // fails
freezeest la méthode la plus stricte ; elle empêche toute modification des propriétés de l’objet.sealempêche l’ajout ou la suppression de propriétés, mais permet de modifier les valeurs des propriétés existantes.preventExtensionsempêche seulement l’ajout de nouvelles propriétés ; les propriétés existantes peuvent toujours être modifiées ou supprimées.
Énumérabilité, ordre et for...in / for...of pour les objets
for...in énumère les noms de propriétés énumérables, mais inclut également celles de la chaîne de prototypes ; il est donc souvent utilisé avec hasOwnProperty. Combiner Object.keys() avec for...of est plus sûr et rend vos intentions plus claires.
1// Safe enumeration
2const obj = Object.create({ inherited: true });
3obj.own = 1;
4
5for (const key in obj) {
6 if (obj.hasOwnProperty(key)) {
7 console.log("own prop:", key);
8 } else {
9 console.log("inherited prop:", key);
10 }
11}
12
13for (const key of Object.keys(obj)) {
14 console.log("key via Object.keys:", key);
15}- Les règles d’énumération des propriétés sont définies dans la spécification ECMAScript, avec des cas où l’ordre est garanti et d’autres non. En règle générale, les clés interprétées comme des nombres sont ordonnées par ordre croissant, tandis que les autres clés suivent l’ordre d’insertion.
Clonage et copie profonde
Il existe deux types de copie d'objet : la copie superficielle en utilisant Object.assign ou la syntaxe de l'opérateur de propagation (spread), et la copie profonde en utilisant des méthodes récursives. Il est important de les utiliser de manière appropriée selon la situation.
Copie superficielle (syntaxe spread / Object.assign)
1// Shallow copy with spread operator
2const original = { a: 1, nested: { x: 10 } };
3const shallow = { ...original };
4shallow.nested.x = 99;
5console.log(original.nested.x); // 99 — nested object is shared
- Avec une copie superficielle, les objets imbriqués conservent leurs références, de sorte que les modifications de l’objet original peuvent affecter la copie.
Copie profonde simple (avec réserves)
Utiliser l’astuce JSON est un moyen rapide de créer une copie profonde, mais cela présente des inconvénients, comme la perte des fonctions, des objets Date, des références circulaires et des valeurs undefined. Pour un véritable clonage profond, il est nécessaire d’utiliser une bibliothèque dédiée.
1// Deep clone using JSON methods — limited use-cases only
2const source = { a: 1, d: new Date(), nested: { x: 2 } };
3const cloned = JSON.parse(JSON.stringify(source));
4console.log(cloned); // ok for plain data, but Date becomes string, functions lost
- Les méthodes basées sur JSON sont pratiques pour traiter rapidement des données simples, mais leur comportement peut s’avérer problématique dans des cas courants.
Mixins et composition d’objets
Plutôt que l’héritage multiple, on utilise souvent le pattern de composition des comportements avec des mixins.
1// Simple mixin function
2const canEat = {
3 eat() { return `${this.name} eats`; }
4};
5const canWalk = {
6 walk() { return `${this.name} walks`; }
7};
8
9function createPerson(name) {
10 const person = { name };
11 return Object.assign(person, canEat, canWalk);
12}
13
14const p = createPerson("Dana");
15console.log(p.eat()); // "Dana eats"
16console.log(p.walk()); // "Dana walks"
- Les mixins sont flexibles, mais il faut faire attention aux collisions de noms de propriétés et à la testabilité.
Pièges courants et bonnes pratiques
Voici quelques pièges courants et bonnes pratiques.
-
Mutabilité Les objets sont mutables par défaut. Dans les applications avec gestion d’état, envisagez des structures de données immuables en utilisant
Object.freezeou une bibliothèque immuable. -
Pollution de prototype Fusionner directement des données externes dans un objet avec
Object.assignou des boucles peut provoquer des effets de bord inattendus sur des propriétés spéciales comme__proto__ouconstructor, ce qui crée des risques de sécurité. Filtrez les données utilisateur avant de les fusionner directement. -
Pièges de
for...infor...inénumère également les propriétés du prototype ; vérifiez donc avechasOwnProperty. UtiliserObject.keysest plus clair. -
Mauvaise utilisation de la copie superficielle Demandez-vous si une copie profonde est nécessaire pour éviter que des modifications dans les objets imbriqués n’affectent l’objet original.
Exemple pratique : Modèle de mise à jour immuable d’objet
Les modèles qui retournent un nouvel objet sans modifier directement l’état sont couramment utilisés dans React et des bibliothèques similaires.
1// Immutable update example
2const state = { todos: [{ id: 1, text: "Buy milk", done: false }] };
3
4// Toggle todo done immutably
5function toggleTodo(state, todoId) {
6 return {
7 ...state,
8 todos: state.todos.map(t => t.id === todoId ? { ...t, done: !t.done } : t)
9 };
10}
11
12const newState = toggleTodo(state, 1);
13console.log(state.todos[0].done); // false
14console.log(newState.todos[0].done); // true
- Ce code est un exemple de création d’un nouvel objet d’état sans modifier directement l’objet
stated’origine. La fonctiontoggleTodocopie le tableautodoset retourne un nouvel objet avec seulement l’élément ciblé modifié, de sorte que l’objetstated’origine reste inchangé. - Les mises à jour immuables réduisent les effets de bord et facilitent la gestion de l’état.
Exemple pratique : Fusion sécurisée (attention à la pollution de prototype)
Lors de la fusion de JSON externe, ignorez __proto__ pour éviter la pollution de prototype.
1// Safe merge ignoring __proto__ keys
2function safeMerge(target, source) {
3 for (const key of Object.keys(source)) {
4 if (key === "__proto__" || key === "constructor") continue;
5 target[key] = source[key];
6 }
7 return target;
8}
9
10const target = {};
11const source = JSON.parse('{"a":1,"__proto__":{"polluted":true}}');
12safeMerge(target, source);
13console.log(target.polluted); // undefined — safe
14console.log({}.polluted); // undefined — prototype not polluted
- Ce genre de protection est également important dans les bibliothèques et frameworks.
Considérations de performance
En ce qui concerne les performances, gardez à l’esprit les points suivants :.
- Évitez de modifier fréquemment le prototype (
Object.setPrototypeOf) ou d’ajouter/supprimer dynamiquement des propriétés, car cela nuit aux optimisations du moteur JavaScript. - Lors de la génération de nombreux petits objets, l'optimisation devient plus efficace si vous utilisez des objets avec une structure uniforme (le même ensemble de propriétés).
- La copie profonde est coûteuse. Réduisez leur utilisation ou envisagez des mises à jour basées sur les différences (diff).
Résumé
Object est central en JavaScript, offrant des fonctionnalités telles que la création d’objets, le contrôle des propriétés, l’héritage, la copie et la gestion de la mutabilité. Il est important de comprendre les API telles que Object.defineProperty, Object.assign et Object.freeze, et de concevoir avec soin pour éviter des écueils comme la pollution de prototype et la copie superficielle.
Vous pouvez suivre l'article ci-dessus avec Visual Studio Code sur notre chaîne YouTube. Veuillez également consulter la chaîne YouTube.