Programación orientada a objetos en JavaScript: explicada con ejemplos

JavaScript no es un lenguaje orientado a objetos basado en clases. Pero todavía tiene formas de utilizar la programación orientada a objetos (OOP).

En este tutorial, explicaré OOP y le mostraré cómo usarlo.

Según Wikipedia, la programación basada en clases es

un estilo de programación orientada a objetos (OOP) en el que la herencia se produce mediante la definición de clases de objetos, en lugar de que la herencia se produzca únicamente a través de los objetos

El modelo más popular de OOP está basado en clases.

Pero como mencioné, JavaScript no es un lenguaje basado en clases, es un lenguaje basado en prototipos.

Según la documentación de Mozilla:

Un lenguaje basado en prototipos tiene la noción de un objeto prototípico, un objeto que se utiliza como plantilla a partir de la cual se obtienen las propiedades iniciales de un nuevo objeto.

Eche un vistazo a este código:

let names = { fname: "Dillion", lname: "Megida" } console.log(names.fname); console.log(names.hasOwnProperty("mname")); // Expected Output // Dillion // false 

La variable de objeto namestiene solo dos propiedades: fnamey lname. Ningún método en absoluto.

Entonces, ¿de dónde hasOwnPropertyviene?

Bueno, viene del Objectprototipo.

Intente registrar el contenido de la variable en la consola:

console.log(names); 

Cuando expanda los resultados en la consola, obtendrá esto:

¿Note la última propiedad - __proto__? Intente expandirlo:

Verá un conjunto de propiedades debajo del Objectconstructor. Todas estas propiedades provienen del Objectprototipo global . Si observa de cerca, también notará nuestro oculto hasOwnProperty.

En otras palabras, todos los objetos tienen acceso al Objectprototipo de. No poseen estas propiedades, pero se les concede acceso a las propiedades del prototipo.

La __proto__propiedad

Esto apunta al objeto que se utiliza como prototipo.

Esta es la propiedad de cada objeto que le da acceso a la Object prototypepropiedad.

Cada objeto tiene esta propiedad por defecto, que se refiere a Object Protoypeexcepto cuando se configura de otra manera (es decir, cuando el objeto __proto__apunta a otro prototipo).

Modificar la __proto__propiedad

Esta propiedad se puede modificar indicando explícitamente que debe hacer referencia a otro prototipo. Se utilizan los siguientes métodos para lograr esto:

Object.create()

function DogObject(name, age) { let dog = Object.create(constructorObject); dog.name = name; dog.age = age; return dog; } let constructorObject = { speak: function(){ return "I am a dog" } } let bingo = DogObject("Bingo", 54); console.log(bingo); 

En la consola, esto es lo que tendría:

¿Observa la __proto__propiedad y el speakmétodo?

Object.create usa el argumento que se le pasa para convertirse en el prototipo.

new palabra clave

function DogObject(name, age) { this.name = name; this.age = age; } DogObject.prototype.speak = function() { return "I am a dog"; } let john = new DogObject("John", 45); 

john's __proto__ property is directed to DogObject's prototype. But remember, DogObject's prototype is an object (key and value pair), hence it also has a __proto__ property which refers to the global Object protoype.

This technique is referred to as PROTOTYPE CHAINING.

Note that: the new keyword approach does the same thing as Object.create() but only makes it easier as it does some things automatically for you.

And so...

Every object in Javascript has access to the Object's prototype by default. If configured to use another prototype, say prototype2, then prototype2 would also have access to the Object's prototype by default, and so on.

Object + Function Combination

You are probably confused by the fact that DogObject is a function (function DogObject(){}) and it has properties accessed with a dot notation. This is referred to as a function object combination.

When functions are declared, by default they are given a lot of properties attached to it. Remember that functions are also objects in JavaScript data types.

Now, Class

JavaScript introduced the class keyword in ECMAScript 2015. It makes JavaScript seem like an OOP language. But it is just syntatic sugar over the existing prototyping technique. It continues its prototyping in the background but makes the outer body look like OOP. We'll now look at how that's possible.

The following example is a general usage of a class in JavaScript:

class Animals { constructor(name, specie) { this.name = name; this.specie = specie; } sing() { return `${this.name} can sing`; } dance() { return `${this.name} can dance`; } } let bingo = new Animals("Bingo", "Hairy"); console.log(bingo); 

This is the result in the console:

The __proto__ references the Animals prototype (which in turn references the Object prototype).

From this, we can see that the constructor defines the major features while everything outside the constructor (sing() and dance()) are the bonus features (prototypes).

In the background, using the new keyword approach, the above translates to:

function Animals(name, specie) { this.name = name; this.specie = specie; } Animals.prototype.sing = function(){ return `${this.name} can sing`; } Animals.prototype.dance = function() { return `${this.name} can dance`; } let Bingo = new Animals("Bingo", "Hairy"); 

Subclassing

This is a feature in OOP where a class inherits features from a parent class but possesses extra features which the parent doesn't.

The idea here is, for example, say you want to create a cats class. Instead of creating the class from scratch - stating the name, age and species property afresh, you'd inherit those properties from the parent animals class.

This cats class can then have extra properties like color of whiskers.

Let's see how subclasses are done with class.

Here, we need a parent which the subclass inherits from. Examine the following code:

class Animals { constructor(name, age) { this.name = name; this.age = age; } sing() { return `${this.name} can sing`; } dance() { return `${this.name} can dance`; } } class Cats extends Animals { constructor(name, age, whiskerColor) { super(name, age); this.whiskerColor = whiskerColor; } whiskers() { return `I have ${this.whiskerColor} whiskers`; } } let clara = new Cats("Clara", 33, "indigo"); 

With the above, we get the following outputs:

console.log(clara.sing()); console.log(clara.whiskers()); // Expected Output // "Clara can sing" // "I have indigo whiskers" 

When you log the contents of clara out in the console, we have:

You'll notice that clara has a __proto__ property which references the constructor Cats and gets access to the whiskers() method. This __proto__ property also has a __proto__ property which references the constructor Animals thereby getting access to sing() and dance(). name and age are properties that exist on every object created from this.

Using the Object.create method approach, the above translates to:

function Animals(name, age) { let newAnimal = Object.create(animalConstructor); newAnimal.name = name; newAnimal.age = age; return newAnimal; } let animalConstructor = { sing: function() { return `${this.name} can sing`; }, dance: function() { return `${this.name} can dance`; } } function Cats(name, age, whiskerColor) { let newCat = Animals(name, age); Object.setPrototypeOf(newCat, catConstructor); newCat.whiskerColor = whiskerColor; return newCat; } let catConstructor = { whiskers() { return `I have ${this.whiskerColor} whiskers`; } } Object.setPrototypeOf(catConstructor, animalConstructor); const clara = Cats("Clara", 33, "purple"); clara.sing(); clara.whiskers(); // Expected Output // "Clara can sing" // "I have purple whiskers" 

Object.setPrototypeOf is a method which takes in two arguments - the object (first argument) and the desired prototype (second argument).

From the above, the Animals function returns an object with the animalConstructor as prototype. The Cats function returns an object with catConstructor as it's prototype. catConstructor on the other hand, is given a prototype of animalConstructor.

Therefore, ordinary animals only have access to the animalConstructor but cats have access to the catConstructor and the animalConstructor.

Wrapping Up

JavaScript leverages its prototype nature to welcome OOP developers to its ecosystem. It also provides easy ways to creating prototypes and organize related data.

True OOP languages do not perform prototyping in the background - just take note of that.

A big thanks to Will Sentance's course on Frontend Masters - JavaScript: The Hard Parts of Object Oriented JavaScript. I learned everything you see in this article (plus a little extra research) from his course. You should check it out.

You can hit me up on Twitter at iamdillion for any questions or contributions.

Thanks for reading : )

Useful Resources

  • Object-oriented JavaScript for beginners
  • Introduction to Object Oriented Programming in JavaScript