JavaScript no es un lenguaje orientado a objetos basado en clases. Pero todavía tiene formas de usar la programación orientada a objetos (POO).
En este tutorial, explicaré POO y te mostraré cómo usarlo.
Según Wikipedia, la programación basada en clases es:
"...un estilo de programación orientada a objetos (POO), 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 POO 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 toma el concepto de objeto prototípico, un objeto que se utiliza como una plantilla a partir de la cual se obtiene el conjunto inicial de propiedades de un nuevo objeto."
Demos un vistazo a este código:
let nombres = {
nombre: "Juan",
apellido: "Pérez"
}
console.log(nombres.nombre);
console.log(nombres.hasOwnProperty("segNombre"));
// Resultado esperado:
// Juan
// false
El objeto de la variable nombres
solo tiene dos propiedades: nombre
y apellido
. Ningún método en absoluto.
Entonces, ¿de dónde viene hasOwnProperty
?
Bueno, viene del prototipo Object
.
Intenta registrar el contenido de la variable en la consola:
console.log(nombres);
Cuando expandas los resultados en la consola, obtendrás esto:

¿Notas la última propiedad: <prototype>? Intenta expandirlo:

Verás un conjunto de propiedades en el constructor Object
. Todas estas propiedades provienen del prototipo Object
global. Si observas de cerca, también notarás nuestra propiedad hasOwnProperty
oculta.
En otras palabras, todos los objetos tienen acceso al prototipo de Object
. No poseen estas propiedades, pero se les concede acceso a las propiedades del prototipo.
La propiedad <prototype>
Esto apunta al objeto que se utiliza como prototipo.
Esta es la propiedad de cada objeto que le da acceso a la propiedad Object prototype
(Prototipo de objeto).
Cada objeto tiene esta propiedad por defecto, que se refiere al prototipo de Objeto excepto cuando se configura de otra manera (es decir, cuando el <prototype>
del objeto apunta a otro prototipo).
Modificando la propiedad <prototype>
Esta propiedad se puede modificar, indicando explícitamente que debería referirse a otro prototipo. Se utilizan los siguientes métodos para lograr esto:
Método Object.create()
:
function Perro(nombre, edad) {
let perro = Object.create(ObjetoConstructor);
perro.nombre = nombre;
perro.edad = edad;
return perro;
}
let ObjetoConstructor = {
habla: function(){
return "¡Soy un perro!"
}
}
let firulais = Perro("Firulais", 9);
console.log(firulais);
En la consola, esto es lo que tendrías:

¿Observas la propiedad <prototype>
y el método habla
?
Object.create
usa el argumento que se le pasa para convertirse en el prototipo.
Palabra clave new
:
function Perro(nombre, edad) {
this.nombre = nombre;
this.edad = edad;
}
Perro.prototype.habla = function() {
return "¡Soy un perro!";
}
let bobby = new Perro("Bobby", 12);
La propiedad <prototype>
de bobby
, es dirigida al prototipo de Perro
. Pero recuerda, el prototipo de Perro
es un objeto (par clave y valor), por lo tanto, también tiene una propiedad <prototype>
que se refiere al prototipo de objeto global.
Esta técnica se conoce como PROTOTYPE CHAINING (encadenamiento de prototipos).
Ten en cuenta que: la palabra clave new
, hace lo mismo que Object.create()
pero solo lo hace más fácil, ya que hace algunas cosas automáticamente por ti.
Y entonces...
Cada objeto en JavaScript tiene acceso al prototipo de Object
por defecto. Si está configurado para usar otro prototipo, digamos prototype2
, entonces prototype2
también tendría acceso al prototipo de Object
por defecto, y así sucesivamente.
Combinación de objeto + función
Probablemente estés confundido por el hecho de que Perro
es una función (function Perro(){}
), y tiene propiedades a las que se accede con una notación de puntos. Esto se conoce como una combinación de objeto y función.
Cuando se declaran funciones, por defecto, se les asignan muchas propiedades adjuntas. Recuerda que las funciones también son objetos en los tipos de datos de JavaScript.
Ahora, clases
JavaScript introdujo la palabra clave class
en ECMAScript 2015. Hace que JavaScript parezca un lenguaje POO. Pero solo es azúcar sintáctico sobre la técnica de creación de prototipos existente. Continúa con la creación de prototipos en segundo plano, pero hace que el cuerpo exterior parezca POO. Ahora veremos cómo es posible.
El siguiente ejemplo es un uso general de una class
en JavaScript:
class Animales {
constructor(nombre, especie) {
this.nombre = nombre;
this.especie = especie;
}
canta() {
return `${this.nombre} puede cantar`;
}
baila() {
return `${this.nombre} puede bailar`;
}
}
let bongo = new Animales("Bongo", "Peludo");
console.log(bongo);
Este es el resultado en consola:

El <prototype>
hace referencia al prototipo de Animales
(que a su vez hace referencia al prototipo de Object
).
A partir de esto, podemos ver que el constructor define las características principales, mientras que todo lo que está fuera del constructor (canta()
y baila()
) son las características adicionales (prototipos).
En segundo plano, utilizando el enfoque de la palabra clave new
, lo anterior se traduce en:
function Animales(nombre, especie) {
this.nombre = nombre;
this.especie = especie;
}
Animales.prototype.canta = function(){
return `${this.nombre} puede cantar`;
}
Animales.prototype.baila = function() {
return `${this.nombre} puede bailar`;
}
let bongo = new Animales("Bongo", "Peludo");
Sub-clases
Esta es una característica en POO, donde una clase hereda características de una clase padre, pero posee características adicionales que el padre no tiene.
La idea aquí es, por ejemplo, decir que quieres crear una clase de Gatos
. En lugar de crear la clase desde cero, indicando de nuevo las propiedades del nombre, la edad o la especie, heredaría esas propiedades de la clase padre Animales
.
Esta clase Gatos
puede tener propiedades adicionales como el color de los bigotes. Veamos cómo se hacen las sub-clases con class
.
Aquí, necesitamos un padre del que herede la sub-clase. Examinemos el siguiente código:
class Animales {
constructor(nombre, edad) {
this.nombre = nombre;
this.edad = edad;
}
canta() {
return `${this.nombre} puede cantar`;
}
baila() {
return `${this.nombre} puede bailar`;
}
}
class Gatos extends Animales {
constructor(nombre, edad, colorBigotes) {
super(nombre, edad);
this.colorBigotes = colorBigotes;
}
bigotes() {
return `Tengo bigotes color ${this.colorBigotes}`;
}
}
let clara = new Gatos("Clara", 33, "índigo");
Con lo anterior, obtenemos los siguientes resultados:
console.log(clara.canta());
console.log(clara.bigotes());
// Resultado esperado
// "Clara puede cantar"
// "Tengo bigotes color índigo"
Cuando registras el contenido de clara
en la consola, tenemos:

Notarás que clara
tiene una propiedad <prototype>
que hace referencia al constructor Gatos
y obtiene acceso al método bigotes()
. Esta propiedad <prototype>
también tiene una propiedad <prototype>
que hace referencia al constructor Animales
obteniendo así acceso a canta()
y baila()
.
nombre
y edad
son propiedades que existen en cada objeto creado a partir de este. Usando el enfoque del método Object.create
, lo anterior se traduce en:
function Animales(nombre, edad) {
let nuevoAnimal = Object.create(ConstructorAnimal);
nuevoAnimal.nombre = nombre;
nuevoAnimal.edad = edad;
return nuevoAnimal;
}
let ConstructorAnimal = {
canta: function() {
return `${this.nombre} puede cantar`;
},
baila: function() {
return `${this.nombre} puede bailar`;
}
}
function Gatos(nombre, edad, colorBigotes) {
let nuevoGato = Animales(nombre, edad);
Object.setPrototypeOf(nuevoGato, ConstructorGato);
nuevoGato.colorBigotes = colorBigotes;
return nuevoGato;
}
let ConstructorGato = {
bigotes() {
return `Tengo bigotes color ${this.colorBigotes}`;
}
}
Object.setPrototypeOf(ConstructorGato, ConstructorAnimal);
const clara = Gatos("Clara", 33, "púrpura");
clara.sing();
clara.whiskers();
// Resultado esperado
// "Clara puede cantar"
// "Tengo bigotes color púrpura"
Object.setPrototypeOf
es un método que toma dos argumentos: el objeto (primer argumento) y el prototipo deseado (segundo argumento).
De lo anterior, la función Animales
devuelve un objeto con ConstructorAnimal
como prototipo. La función Gatos
devuelve un objeto con ConstructorGato
como prototipo. ConstructorGato
, por otro lado, recibe un prototipo de ConstructorAnimal
.
Por lo tanto, los animales comunes solo tienen acceso al ConstructorAnimal
, pero los gatos tienen acceso al ConstructorGato
y al ConstructorAnimal
.
Terminando
JavaScript aprovecha su naturaleza de prototipo para dar la bienvenida a los desarrolladores de POO a su ecosistema. También proporciona formas sencillas de crear prototipos y organizar datos relacionados.
Los verdaderos lenguajes de programación orientada a objetos no realizan prototipos en segundo plano, solo toma nota de eso.
Un gran agradecimiento al curso de Will Sentance: "Frontend Masters - JavaScript: The Hard Parts of Object Oriented JavaScript". Aprendí todo lo que ves en este artículo (más un poco de investigación adicional) de su curso. Deberías darle un vistazo.
Puedes contactarme en Twitter en iamdillion para cualquier pregunta o contribuciones.
Gracias por leer :)
Recurso útil
Traducido del artículo de Dillion Megida - Object Oriented Programming in JavaScript – Explained with Examples