La Programación Orientada a Objetos (POO) en JavaScript se fundamenta en un modelo de prototipos, lo que la diferencia de lenguajes con clases tradicionales como Java o C++. Para dominar este paradigma, es esencial comprender la relación entre intsancias, prototipos y el comportamiento del operador new.
Conceptos Fundamentales
En JavaScript, la jerarquía se divide principalmente en tres pilares:
- Objeto: La entidad base de la que deriva casi todo.
- Clase (Función Constructora): El molde o abstracción que define las propiedades y métodos comunes.
- Instancia: El objeto concreto creado a partir de un molde.
A continuación, se detalla la clasificación de tipos integrados en el lenguaje:
| Instancia de ejemplo | Constructor (Clase) | Tipo Base |
|---|---|---|
| 42 | Number | Object |
| "Hola" | String | Object |
| [true, false] | Array | Object |
| function(){} | Function | Object |
| new Date() | Date | Object |
| { id: 1 } | Object | Object |
El proceso de instanciación con el operador new
Cuando invocamos una función constructora utilizando la palabra clave new, el motor de JavaScript realiza una serie de pasos automáticos que difieren de una ejecución de función convencional:
function Vehiculo() {
this.ruedas = 4;
}
// Ejecución normal: 'this' apunta al contexto global (window/undefined)
Vehiculo();
// Ejecución como constructor: Crea una nueva instancia
const miAuto = new Vehiculo();
Pasos internos del operador new:
- Se genera un nuevo contexto de ejecución y un objeto vacío en memoria.
- Se vincula el prototipo del nuevo objeto al
prototypede la función constructora. - Se asigna el valor de
thisal nuevo objeto creado. - Se ejecuta el código interno de la función.
- Retorno lógico:
- Si la función devuelve explícitamente un objeto (tipo de referencia), se retornará dicho objeto.
- Si no hay retorno o se devuelve un valor primitivo, el motor retorna automáticamente la instancia creada.
// Caso 1: Retorno de tipo primitivo (se ignora)
function Sensor() {
this.estado = "activo";
return 500;
}
const s1 = new Sensor(); // Resultado: { estado: "activo" }
// Caso 2: Retorno de objeto (sustituye la instancia)
function Monitor() {
this.resolucion = "4K";
return { marca: "Dell" };
}
const m1 = new Monitor(); // Resultado: { marca: "Dell" }
Arquitectura de Prototipos y la Cadena de Prototipado
La herencia en JavaScript funciona mediante una estructura de "cadena". Cada objeto tiene un enlace interno a otro objeto llamado su prototipo.
- prototype: Es una propiedad exclusiva de las funciones (clases) que define los métodos y atributos que serán compartidos por todas las instancias.
- __proto__: Es un puntero presente en todos los objetos que señala al
prototypede la clase de la cual fueron creados. - constructor: Una propiedad dentro del objeto
prototypeque apunta de vuelta a la función constructora original.
Mecanismo de búsqueda
Cuando se intenta acceder a una propiedad, JavaScript busca primero en la propia instancia (propiedadse privadas). Si no la encuentra, sigue el enlace __proto__ hacia el prototipo de la clase. Este proceso continúa hacia arriba en la cadena hasta llegar a Object.prototype. Si el valor no se encuentra en ningún nivel, devuelve undefined.
Análisis práctico de herencia y contexto
Considere el siguiente escenario donde se mezclan métodos de instancia y métodos de prototipo:
function Dispositivo(nombre) {
this.nombre = nombre;
this.encender = function() {
console.log("Encendiendo " + this.nombre);
};
}
Dispositivo.prototype.encender = function() {
console.log("Método genérico de encendido");
};
Dispositivo.prototype.apagar = function() {
console.log("Apagando dispositivo...");
};
let d1 = new Dispositivo("Laptop");
let d2 = new Dispositivo("Móvil");
// Comparaciones lógicas
console.log(d1.encender === d2.encender); // false (son funciones únicas creadas en el constructor)
console.log(d1.apagar === d2.apagar); // true (comparten la misma referencia en el prototipo)
console.log(d1.__proto__.apagar === Dispositivo.prototype.apagar); // true
// Ejecución y resolución de 'this'
d1.encender(); // "Encendiendo Laptop" (Prioridad al método privado)
d1.__proto__.encender(); // undefined (El prototipo no tiene la propiedad 'nombre')
En este ejemplo, d1.encender es una función propia de la instancia, ocupando un espacio único en memoria por cada objeto creado. En cambio, apagar reside en Dispositivo.prototype, optimizando el uso de memoria ya que todas las instancias apuntan a la misma ubicación física de la función.