JavaScript: explorer des objets

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 //undefined 

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"]; //"thank you very much" 

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; //"Douglas" 

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; //true 

▍ 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"); //2 

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();//JavaScript: The Good Parts by Douglas Crockford 

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"); } //: Cannot assign to read only property 'start' of object 

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à.

Source: https://habr.com/ru/post/fr420615/


All Articles