JavaScript Create Object - Comment définir des objets dans JS

Les objets sont la principale unité d'encapsulation dans la programmation orientée objet. Dans cet article, je décrirai plusieurs façons de créer des objets en JavaScript. Elles sont:

  • Objet littéral
  • Object.create ()
  • Des classes
  • Fonctions d'usine

Objet littéral

Tout d'abord, nous devons faire une distinction entre les structures de données et les objets orientés objet. Les structures de données ont des données publiques et aucun comportement. Cela signifie qu'ils n'ont aucune méthode.

Nous pouvons facilement créer de tels objets en utilisant la syntaxe littérale d'objet. Cela ressemble à ceci:

const product = { name: 'apple', category: 'fruits', price: 1.99 } console.log(product);

Les objets en JavaScript sont des collections dynamiques de paires clé-valeur. La clé est toujours une chaîne et doit être unique dans la collection. La valeur peut être une primitive, un objet ou même une fonction.

Nous pouvons accéder à une propriété en utilisant le point ou la notation carrée.

console.log(product.name); //"apple" console.log(product["name"]); //"apple"

Voici un exemple où la valeur est un autre objet.

const product = { name: 'apple', category: 'fruits', price: 1.99, nutrients : { carbs: 0.95, fats: 0.3, protein: 0.2 } }

La valeur de la carbspropriété est un nouvel objet. Voici comment nous pouvons accéder à la carbspropriété.

console.log(product.nutrients.carbs); //0.95

Noms de propriété abrégés

Prenons le cas où nous avons les valeurs de nos propriétés stockées dans des variables.

const name = 'apple'; const category = 'fruits'; const price = 1.99; const product = { name: name, category: category, price: price }

JavaScript prend en charge ce que l'on appelle les noms de propriété abrégés. Cela nous permet de créer un objet en utilisant uniquement le nom de la variable. Cela créera une propriété avec le même nom. Le littéral objet suivant est équivalent au précédent.

const name = 'apple'; const category = 'fruits'; const price = 1.99; const product = { name, category, price }

Object.create

Ensuite, regardons comment implémenter des objets avec un comportement, des objets orientés objet.

JavaScript a ce qu'on appelle le système prototype qui permet de partager le comportement entre les objets. L'idée principale est de créer un objet appelé prototype avec un comportement commun, puis de l'utiliser lors de la création de nouveaux objets.

Le système prototype nous permet de créer des objets qui héritent du comportement d'autres objets.

Créons un objet prototype qui nous permet d'ajouter des produits et d'obtenir le prix total à partir d'un panier.

const cartPrototype = { addProduct: function(product){ if(!this.products){ this.products = [product] } else { this.products.push(product); } }, getTotalPrice: function(){ return this.products.reduce((total, p) => total + p.price, 0); } }

Notez que cette fois, la valeur de la propriété addProductest une fonction. Nous pouvons également écrire l'objet précédent en utilisant une forme plus courte appelée syntaxe de la méthode abrégée.

const cartPrototype = { addProduct(product){/*code*/}, getTotalPrice(){/*code*/} }

Le cartPrototypeest l'objet prototype qui maintient le comportement commun représenté par deux méthodes, addProductet getTotalPrice. Il peut être utilisé pour créer d'autres objets héritant de ce comportement.

const cart = Object.create(cartPrototype); cart.addProduct({name: 'orange', price: 1.25}); cart.addProduct({name: 'lemon', price: 1.75}); console.log(cart.getTotalPrice()); //3

L' cartobjet a cartPrototypepour prototype. Il en hérite le comportement. carta une propriété masquée qui pointe vers l'objet prototype.

Lorsque nous utilisons une méthode sur un objet, cette méthode est d'abord recherchée sur l'objet lui-même plutôt que sur son prototype.

ce

Notez que nous utilisons un mot-clé spécial appelé thispour accéder et modifier les données sur l'objet.

N'oubliez pas que les fonctions sont des unités de comportement indépendantes en JavaScript. Ils ne font pas nécessairement partie d'un objet. Quand ils le sont, nous avons besoin d'une référence qui permet à la fonction d'accéder à d'autres membres sur le même objet. thisest le contexte de la fonction. Il donne accès à d'autres propriétés.

Les données

Vous vous demandez peut-être pourquoi nous n'avons pas défini et initialisé la productspropriété sur l'objet prototype lui-même.

On ne devrait pas faire ça. Les prototypes doivent être utilisés pour partager des comportements, pas des données. Le partage de données conduira à avoir les mêmes produits sur plusieurs objets de panier. Considérez le code ci-dessous:

const cartPrototype = { products:[], addProduct: function(product){ this.products.push(product); }, getTotalPrice: function(){} } const cart1 = Object.create(cartPrototype); cart1.addProduct({name: 'orange', price: 1.25}); cart1.addProduct({name: 'lemon', price: 1.75}); console.log(cart1.getTotalPrice()); //3 const cart2 = Object.create(cartPrototype); console.log(cart2.getTotalPrice()); //3

Les objets cart1et cart2héritant du comportement commun de l ' cartPrototypepartagent également les mêmes données. Nous ne voulons pas de ça. Les prototypes doivent être utilisés pour partager des comportements, pas des données.

Classe

Le système prototype n'est pas une manière courante de construire des objets. Les développeurs sont plus habitués à créer des objets à partir de classes.

La syntaxe de classe permet une manière plus familière de créer des objets partageant un comportement commun. Il crée toujours le même prototype dans les coulisses mais la syntaxe est plus claire et nous évitons également le problème lié aux données précédent. La classe offre un emplacement spécifique pour définir les données distinctes pour chaque objet.

Voici le même objet créé à l'aide de la syntaxe du sucre de classe:

class Cart{ constructor(){ this.products = []; } addProduct(product){ this.products.push(product); } getTotalPrice(){ return this.products.reduce((total, p) => total + p.price, 0); } } const cart = new Cart(); cart.addProduct({name: 'orange', price: 1.25}); cart.addProduct({name: 'lemon', price: 1.75}); console.log(cart.getTotalPrice()); //3 const cart2 = new Cart(); console.log(cart2.getTotalPrice()); //0

Notice that the class has a constructor method that initialized that data distinct for each new object. The data in the constructor is not shared between instances. In order to create a new instance, we use the new keyword.

I think the class syntax is more clear and familiar to most developers. Nevertheless, it does a similar thing, it creates a prototype with all the methods and uses it to define new objects. The prototype can be accessed with Cart.prototype.

It turns out that the prototype system is flexible enough to allow the class syntax. So the class system can be simulated using the prototype system.

Private Properties

The only thing is that the products property on the new object is public by default.

console.log(cart.products); //[{name: "orange", price: 1.25} // {name: "lemon", price: 1.75}]

We can make it private using the hash # prefix.

Private properties are declared with #name syntax. # is a part of the property name itself and should be used for declaring and accessing the property. Here is an example of declaring products as a private property:

class Cart{ #products constructor(){ this.#products = []; } addProduct(product){ this.#products.push(product); } getTotalPrice(){ return this.#products.reduce((total, p) => total + p.price, 0); } } console.log(cart.#products); //Uncaught SyntaxError: Private field '#products' must be declared in an enclosing class

Factory Functions

Another option is to create objects as collections of closures.

Closure is the ability of a function to access variables and parameters from the other function even after the outer function has executed. Take a look at the cart object built with what is called a factory function.

function Cart() { const products = []; function addProduct(product){ products.push(product); } function getTotalPrice(){ return products.reduce((total, p) => total + p.price, 0); } return { addProduct, getTotalPrice } } const cart = Cart(); cart.addProduct({name: 'orange', price: 1.25}); cart.addProduct({name: 'lemon', price: 1.75}); console.log(cart.getTotalPrice()); //3

addProduct and getTotalPrice are two inner functions accessing the variable products from their parent. They have access to the products variable event after the parent Cart has executed. addProduct and getTotalPrice are two closures sharing the same private variable.

Cart is a factory function.

The new object cart created with the factory function has the products variable private. It cannot be accessed from the outside.

console.log(cart.products); //undefined

Factory functions don’t need the new keyword but you can use it if you want. It will return the same object no matter if you use it or not.

Recap

Usually, we work with two types of objects, data structures that have public data and no behavior and object-oriented objects that have private data and public behavior.

Data structures can be easily built using the object literal syntax.

JavaScript offers two innovative ways of creating object-oriented objects. The first is using a prototype object to share the common behavior. Objects inherit from other objects. Classes offer a nice sugar syntax to create such objects.

The other option is to define objects are collections of closures.

For more on closures and function programming techniques check out my book series Functional Programming with JavaScript and React.

The Functional Programming in JavaScript book is coming out.