Le matériel, dont nous publions la traduction aujourd'hui, est consacré à l'étude des objets - l'une des essences clés de JavaScript. Il est conçu principalement pour les développeurs débutants qui souhaitent rationaliser leurs connaissances des objets.

Les objets en JavaScript sont des collections dynamiques de propriétés qui, en outre, contiennent une propriété «cachée» qui est un prototype de l'objet. Les propriétés des objets sont caractérisées par des clés et des valeurs. Commençons la conversation sur les objets JS avec des clés.
Clés de propriété d'objet
La clé de propriété d'objet est une chaîne unique. Vous pouvez utiliser deux méthodes pour accéder aux propriétés: y accéder via un point et spécifier la clé d'objet entre crochets. Lors de l'accès aux propriétés via un point, la clé doit être un identifiant JavaScript valide. Prenons un exemple:
let obj = { message : "A message" } obj.message //"A message" obj["message"] //"A message"
Lorsque vous essayez d'accéder à une propriété inexistante d'un objet, un message d'erreur n'apparaîtra pas, mais la valeur
undefined
sera retournée:
obj.otherProperty
Lorsque vous utilisez des crochets pour accéder aux propriétés, vous pouvez utiliser des clés qui ne sont pas des identificateurs JavaScript valides (par exemple, la clé peut être une chaîne contenant des espaces). Ils peuvent avoir n'importe quelle valeur pouvant être convertie en chaîne:
let french = {}; french["merci beaucoup"] = "thank you very much"; french["merci beaucoup"];
Si des valeurs non-chaîne sont utilisées comme clés, elles sont automatiquement converties en chaînes (en utilisant, si possible, la
toString()
):
et obj = {}; //Number obj[1] = "Number 1"; obj[1] === obj["1"]; //true //Object let number1 = { toString : function() { return "1"; } } obj[number1] === obj["1"]; //true
Dans cet exemple, l'objet
number1
est utilisé comme clé. Lorsque vous essayez d'accéder à une propriété, elle est convertie en ligne
1
et le résultat de cette conversion est utilisé comme clé.
Valeurs des propriétés d'objet
Les propriétés d'objet peuvent être des valeurs primitives, des objets ou des fonctions.
▍Objet en tant que valeur de propriété d'objet
Les objets peuvent être placés dans d'autres objets. Prenons
un exemple :
let book = { title : "The Good Parts", author : { firstName : "Douglas", lastName : "Crockford" } } book.author.firstName;
Une approche similaire peut être utilisée pour créer des espaces de noms:
let app = {}; app.authorService = { getAuthors : function() {} }; app.bookService = { getBooks : function() {} };
▍ Fonction en tant que valeur de propriété d'objet
Lorsqu'une fonction est utilisée comme valeur de propriété d'objet, elle devient généralement une méthode d'objet. Dans la méthode, pour accéder à l'objet courant, utilisez le
this
.
Cependant, ce mot clé peut avoir différentes significations, selon la façon dont la fonction a été appelée.
Ici, vous pouvez lire des situations dans lesquelles
this
perd le contexte.
La nature dynamique des objets
Les objets en JavaScript, de par leur nature, sont des entités dynamiques. Vous pouvez leur ajouter des propriétés à tout moment, il en va de même pour la suppression des propriétés:
let obj = {}; obj.message = "This is a message"; // obj.otherMessage = "A new message"; // delete obj.otherMessage; //
Objets en tant que tableaux associatifs
Les objets peuvent être considérés comme des tableaux associatifs. Les clés de tableau associatif sont les noms de propriété de l'objet. Pour accéder à la clé, vous n'avez pas besoin de regarder toutes les propriétés, c'est-à-dire que l'opération d'accès à la clé d'un tableau associatif basé sur un objet s'effectue en temps O (1).
Prototypes d'objets
Les objets ont un lien «caché»,
__proto__
, pointant vers un objet prototype dont l'objet hérite des propriétés.
Par exemple, un objet créé à l'aide d'un littéral d'objet a un lien vers
Object.prototype
:
var obj = {}; obj.__proto__ === Object.prototype;
▍ Objets vides
Comme nous venons de le voir, l'objet "vide",
{}
, n'est en fait pas si vide, car il contient une référence à
Object.prototype
. Pour créer un objet vraiment vide, vous devez utiliser la construction suivante:
Object.create(null)
Grâce à cela, un objet sans prototype sera créé. Ces objets sont généralement utilisés pour créer des tableaux associatifs.
▍ Chaîne prototype
Les objets prototypes peuvent avoir leurs propres prototypes. Si vous essayez d'accéder à une propriété d'un objet qui n'y est pas, JavaScript essaiera de trouver cette propriété dans le prototype de cet objet, et si la propriété souhaitée n'est pas là, une tentative sera faite pour la trouver dans le prototype du prototype. Cela continuera jusqu'à ce que la propriété souhaitée soit trouvée ou jusqu'à ce que la fin de la chaîne de prototype soit atteinte.
Valeurs de type primitif et wrappers d'objets
JavaScript vous permet de travailler avec les valeurs des types primitifs en tant qu'objets, dans le sens où le langage vous permet d'accéder à leurs propriétés et méthodes.
(1.23).toFixed(1); //"1.2" "text".toUpperCase(); //"TEXT" true.toString(); //"true"
De plus, bien sûr, les valeurs des types primitifs ne sont pas des objets.
Pour organiser l'accès aux «propriétés» des valeurs des types primitifs, JavaScript, si nécessaire, crée des objets wrapper qui, une fois devenus inutiles, sont détruits. Le processus de création et de destruction d'objets wrapper est optimisé par le moteur JS.
Les wrappers d'objets ont des valeurs de type numérique, chaîne et logique. Les objets des types correspondants sont représentés par les fonctions constructeur
Number
,
String
et
Boolean
.
Prototypes intégrés
Les objets numériques héritent des propriétés et des méthodes du prototype
Number.prototype
, qui est le descendant de
Object.prototype
:
var no = 1; no.__proto__ === Number.prototype; //true no.__proto__.__proto__ === Object.prototype; //true
Le prototype des objets chaîne est
String.prototype
. Le prototype des objets booléens est
Boolean.prototype
. Le prototype des tableaux (qui sont également des objets) est
Array.prototype
.
Les fonctions en JavaScript sont également des objets qui ont un prototype
Function.prototype
. Les fonctions ont des méthodes comme
bind()
,
apply()
et
call()
.
Tous les objets, fonctions et objets représentant des valeurs de type primitif (à l'exception des valeurs
null
et
undefined
) héritent des propriétés et des méthodes de
Object.prototype
. Cela conduit au fait que, par exemple, ils ont tous une
toString()
.
Extension d'objets incorporés avec des polyfills
JavaScript facilite l'extension des objets intégrés avec de nouvelles fonctionnalités à l'aide de ce que l'on appelle les polyfills. Un polyfill est un morceau de code qui implémente des fonctionnalités qui ne sont prises en charge par aucun navigateur.
▍Utilisation de polyfills
Par exemple, il existe un
polyfill pour la méthode
Object.assign()
. Il vous permet d'ajouter une nouvelle fonction à
Object
si elle n'y est pas disponible.
Il en va de même pour le
polyfill Array.from()
qui, si la méthode
from()
n'est pas dans l'objet
Array
, l'équipe de cette méthode.
▍ Polyfill et prototypes
À l'aide de polyfills, de nouvelles méthodes peuvent être ajoutées aux prototypes d'objets. Par exemple, le
polyfill pour
String.prototype.trim()
vous permet d'équiper tous les objets chaîne avec la méthode
trim()
:
let text = " A text "; text.trim(); //"A text"
Le polyfill pour
Array.prototype.find()
vous permet d'équiper tous les tableaux avec la méthode
find()
. Le
polyfill pour
Array.prototype.findIndex()
fonctionne de la même manière:
let arr = ["A", "B", "C", "D", "E"]; arr.indexOf("C");
Héritage unique
La commande
Object.create()
vous permet de créer de nouveaux objets avec un objet prototype donné. Cette commande est utilisée en JavaScript pour implémenter un mécanisme d'héritage unique. Prenons
un exemple :
let bookPrototype = { getFullTitle : function(){ return this.title + " by " + this.author; } } let book = Object.create(bookPrototype); book.title = "JavaScript: The Good Parts"; book.author = "Douglas Crockford"; book.getFullTitle();
Héritage multiple
La commande
Object.assign()
copie les propriétés d'un ou plusieurs objets vers l'objet cible. Il peut être utilisé pour implémenter plusieurs schémas d'héritage. Voici
un exemple :
let authorDataService = { getAuthors : function() {} }; let bookDataService = { getBooks : function() {} }; let userDataService = { getUsers : function() {} }; let dataService = Object.assign({}, authorDataService, bookDataService, userDataService ); dataService.getAuthors(); dataService.getBooks(); dataService.getUsers();
Objets immuables
La commande
Object.freeze()
vous permet de «figer» un objet. Vous ne pouvez pas ajouter de nouvelles propriétés à un tel objet. Les propriétés ne peuvent pas être supprimées et leurs valeurs ne peuvent pas être modifiées. En utilisant cette commande, un objet devient immuable ou immuable:
"use strict"; let book = Object.freeze({ title : "Functional-Light JavaScript", author : "Kyle Simpson" }); book.title = "Other title";//: Cannot assign to read only property 'title'
La commande
Object.freeze()
effectue la soi-disant «congélation superficielle» des objets. Cela signifie que les objets imbriqués dans un objet «figé» peuvent être modifiés. Pour effectuer un «gel profond» d'un objet, vous devez «geler» récursivement toutes ses propriétés.
Clonage d'objets
Pour créer des clones (copies) d'objets, vous pouvez utiliser la commande
Object.assign()
:
let book = Object.freeze({ title : "JavaScript Allongé", author : "Reginald Braithwaite" }); let clone = Object.assign({}, book);
Cette commande effectue une copie superficielle des objets, c'est-à-dire qu'elle copie uniquement les propriétés de niveau supérieur. Les objets imbriqués s'avèrent courants pour les objets originaux et leurs copies.
Littéral d'objet
Les littéraux d'objets offrent aux développeurs un moyen simple et direct de créer des objets:
let timer = { fn : null, start : function(callback) { this.fn = callback; }, stop : function() {}, }
Cependant, cette méthode de création d'objets présente des inconvénients. En particulier, avec cette approche, toutes les propriétés de l'objet sont accessibles au public, les méthodes de l'objet peuvent être redéfinies, elles ne peuvent pas être utilisées pour créer de nouvelles instances des mêmes objets:
timer.fn;//null timer.start = function() { console.log("New implementation"); }
Object.create (), méthode
Les deux problèmes mentionnés ci-dessus peuvent être résolus grâce à l'utilisation conjointe des méthodes
Object.create()
et
Object.freeze()
.
Nous appliquons cette technique à notre exemple précédent. Tout d'abord, créez un prototype
timerPrototype
qui contient toutes les méthodes nécessaires aux différentes instances de l'objet. Après cela, créez un objet qui succède à
timerPrototype
:
let timerPrototype = Object.freeze({ start : function() {}, stop : function() {} }); let timer = Object.create(timerPrototype); timer.__proto__ === timerPrototype; //true
Si le prototype est protégé contre les modifications, l'objet qui est son héritier ne pourra pas modifier les propriétés définies dans le prototype. Désormais, les méthodes
start()
et
stop()
ne peuvent pas être remplacées:
"use strict"; timer.start = function() { console.log("New implementation"); }
La construction
Object.create(timerPrototype)
peut être utilisée pour créer plusieurs objets avec le même prototype.
Fonction constructeur
JavaScript possède des fonctions dites de constructeur, qui sont du «sucre syntaxique» pour effectuer les étapes ci-dessus pour créer de nouveaux objets. Prenons
un exemple :
function Timer(callback){ this.fn = callback; } Timer.prototype = { start : function() {}, stop : function() {} } function getTodos() {} let timer = new Timer(getTodos);
Vous pouvez utiliser n'importe quelle fonction en tant que constructeur. Le constructeur est appelé à l'aide du
new
mot clé. Un objet créé à l'aide d'une fonction constructeur nommée
FunctionConstructor
recevra un prototype
FunctionConstructor.prototype
:
let timer = new Timer(); timer.__proto__ === Timer.prototype;
Ici, pour empêcher une modification du prototype, encore une fois, vous pouvez geler le prototype:
Timer.prototype = Object.freeze({ start : function() {}, stop : function() {} });
▍ Mot-clé nouveau
Lorsqu'une commande de la forme
new Timer()
est exécutée, les mêmes actions sont effectuées que la fonction
newTimer()
effectue ci-dessous:
function newTimer(){ let newObj = Object.create(Timer.prototype); let returnObj = Timer.call(newObj, arguments); if(returnObj) return returnObj; return newObj; }
Un nouvel objet est créé ici, dont le prototype est
Timer.prototype
. Ensuite, la fonction
Timer
est appelée, définissant les champs du nouvel objet.
Mot-clé de classe
ECMAScript 2015 a introduit une nouvelle façon d'effectuer les actions ci-dessus, qui est un autre lot de «sucre syntaxique». Nous parlons du mot-clé
class
et des constructions associées qui lui sont associées. Prenons
un exemple :
class Timer{ constructor(callback){ this.fn = callback; } start() {} stop() {} } Object.freeze(Timer.prototype);
Un objet créé à l'aide du mot
class
clé
class
basé sur une classe nommée
ClassName
aura le prototype
ClassName.prototype
. Lors de la création d'un objet basé sur une classe, utilisez le
new
mot-clé:
let timer= new Timer(); timer.__proto__ === Timer.prototype;
L'utilisation de classes ne rend pas les prototypes immuables. Si nécessaire, ils devront être «gelés» de la même manière que nous l'avons déjà fait:
Object.freeze(Timer.prototype);
Héritage basé sur un prototype
En JavaScript, les objets héritent des propriétés et des méthodes des autres objets. Les fonctions et classes constructeurs sont des «sucres syntaxiques» pour créer des objets prototypes contenant toutes les méthodes nécessaires. En les utilisant, de nouveaux objets sont créés qui sont les héritiers du prototype, dont les propriétés, spécifiques à une instance particulière, sont définies à l'aide de la fonction constructeur ou à l'aide des mécanismes de classe.
Ce serait bien si les fonctions et les classes constructeurs pouvaient automatiquement rendre les prototypes immuables.
Les atouts de l'héritage de prototype sont les économies de mémoire. Le fait est qu'un prototype n'est créé qu'une seule fois, après quoi tous les objets créés sur sa base l'utilisent.
▍ Le problème du manque de mécanismes d'encapsulation intégrés
Le modèle d'héritage prototype n'utilise pas la séparation des propriétés des objets entre privé et public. Toutes les propriétés des objets sont accessibles au public.
Par exemple, la commande
Object.keys()
renvoie un tableau contenant toutes les clés de propriété de l'objet. Il peut être utilisé pour parcourir toutes les propriétés d'un objet:
function logProperty(name){ console.log(name); // console.log(obj[name]); // } Object.keys(obj).forEach(logProperty);
Il existe un modèle qui imite les propriétés privées, en s'appuyant sur le fait que les développeurs n'accéderont pas aux propriétés dont les noms commencent par un trait de soulignement (
_
):
class Timer{ constructor(callback){ this._fn = callback; this._timerId = 0; } }
Caractéristiques d'usine
Les objets encapsulés en JavaScript peuvent être créés à l'aide des fonctions d'usine. Cela ressemble à ceci:
function TodoStore(callback){ let fn = callback; function start() {}, function stop() {} return Object.freeze({ start, stop }); }
Ici, la variable
fn
est privée. Seules les méthodes
start()
et
stop()
sont accessibles au public. Ces méthodes ne peuvent pas être modifiées en externe. Le mot-clé this n'est pas utilisé ici, par conséquent, lorsque vous utilisez cette méthode de création d'objets, le problème de la perte de
this
contexte n'est pas pertinent.
La commande
return
utilise un littéral objet contenant uniquement des fonctions. De plus, ces fonctions sont déclarées en clôture, elles partagent un état commun. Pour figer une API publique d'un objet, la commande
Object.freeze()
déjà connue est
Object.freeze()
.
Ici, dans les exemples, nous avons utilisé l'objet
Timer
. Dans
ce document, vous pouvez trouver sa mise en œuvre complète.
Résumé
En JavaScript, les valeurs des types primitifs, des objets ordinaires et des fonctions sont traitées comme des objets. Les objets ont un caractère dynamique, ils peuvent être utilisés comme des tableaux associatifs. Les objets sont des héritiers d'autres objets. Les fonctions et classes constructeurs sont des «sucres syntaxiques», elles vous permettent de créer des objets à partir de prototypes. Vous pouvez utiliser la méthode
Object.create()
pour organiser l'héritage unique et
Object.create()
pour organiser l'héritage multiple. Vous pouvez utiliser les fonctions d'usine pour créer des objets encapsulés.
Chers lecteurs! Si vous êtes arrivé à JavaScript à partir d'autres langues, veuillez nous dire ce que vous aimez ou n'aimez pas dans les objets JS, par rapport à la mise en œuvre d'objets dans des langues que vous connaissez déjà.
