Clases en JavaScript: constructor, métodos y prototipos | Nicolás Garzón
Una clase es una sintaxis para crear objetos relacionados, inicializar sus propiedades y compartir métodos mediante prototipos.
JavaScript
Copiar class User {
constructor ( name, role ) {
this . name = name;
this . role = role;
}
greet ( ) {
return ` Hello, ${ this . name} ` ;
}
}
const user = new User ( "Nicolás" , "developer" ) ; JavaScript
Copiar Object. getPrototypeOf ( user) === User . prototype;
Object. hasOwn ( user, "greet" ) ;
Object. hasOwn ( User . prototype, "greet" ) ; La sintaxis class no reemplaza el modelo prototípico de JavaScript. Lo organiza con reglas más claras para construcción, herencia, métodos y campos privados.
Decir que las clases son únicamente “azúcar sintáctica” puede ocultar diferencias importantes frente a funciones constructoras:
No pueden llamarse sin new.
Su cuerpo se ejecuta en modo estricto.
Tienen Temporal Dead Zone.
Sus métodos de prototipo no son enumerables.
Admiten campos privados reales.
La herencia derivada tiene reglas específicas para super.
JavaScript
Copiar class Product {
constructor ( name, price ) {
this . name = name;
this . price = price;
}
} La declaración crea un binding llamado Product.
No puede utilizarse antes de su inicialización.
JavaScript
Copiar new Product ( ) ;
class Product { } La declaración participa en hoisting, pero permanece dentro de la Temporal Dead Zone hasta la línea de la clase.
Las clases también son valores.
JavaScript
Copiar const Product = class {
constructor ( name ) {
this . name = name;
}
} ; Una expresión puede tener un nombre interno.
JavaScript
Copiar const Product = class ProductModel {
static readInternalName ( ) {
return ProductModel. name;
}
} ; ProductModel solo está disponible dentro del cuerpo de la clase.
JavaScript
Copiar const keyboard = new Product (
"Keyboard" ,
120 ,
) ;
Crea una instancia.
La conecta con Product.prototype.
Ejecuta el constructor con la instancia como this.
Retorna la instancia, salvo reglas especiales de retorno.
Una clase siempre requiere new.
JavaScript
Copiar Product ( "Keyboard" , 120 ) ; JavaScript
Copiar class Product {
constructor ( name, price ) {
this . name = name;
this . price = price;
}
} constructor inicializa una nueva instancia. Solo puede existir un constructor explícito por clase.
Si se omite en una clase base, JavaScript utiliza uno equivalente a:
JavaScript
Copiar constructor ( ... args) { } En una clase derivada, el constructor por defecto delega al padre:
JavaScript
Copiar constructor ( ... args) {
super ( ... args) ;
} Evita utilizar el constructor para trabajo asíncrono, red o inicialización que pueda quedar incompleta. Una factory asíncrona comunica mejor ese proceso.
JavaScript
Copiar const user = await User. createFromApi ( id) ; JavaScript
Copiar class Cart {
constructor ( ) {
this . items = [ ] ;
}
addItem ( item ) {
this . items. push ( item) ;
}
calculateTotal ( ) {
return this . items. reduce (
( total, item ) =>
total + item. price * item. quantity,
0 ,
) ;
}
} Los métodos se almacenan en Cart.prototype y se comparten.
JavaScript
Copiar const firstCart = new Cart ( ) ;
const secondCart = new Cart ( ) ;
firstCart. addItem === secondCart. addItem; JavaScript
Copiar const cart = new Cart ( ) ;
const addItem = cart. addItem;
addItem ( { name : "Keyboard" } ) ; Los métodos de clase siguen las reglas de this. Extraerlos elimina el receptor.
JavaScript
Copiar const addItem = cart. addItem . bind ( cart) ; O diseñar una API con parámetros explícitos.
JavaScript
Copiar class Counter {
value = 0 ;
step = 1 ;
increment ( ) {
this . value += this . step;
}
} Los campos públicos se crean como propiedades propias en cada instancia.
JavaScript
Copiar const counter = new Counter ( ) ;
Object. hasOwn ( counter, "value" ) ;
Object. hasOwn ( Counter . prototype, "value" ) ; Pueden usar valores calculados desde la instancia.
JavaScript
Copiar class User {
createdAt = new Date ( ) ;
constructor ( name ) {
this . name = name;
}
} Cada instancia obtiene su propia fecha.
Un campo de instancia crea un valor por objeto.
JavaScript
Copiar class Cart {
items = [ ] ;
} Cada carrito recibe un array distinto.
En cambio, poner un array mutable en el prototipo lo compartiría.
JavaScript
Copiar function Cart ( ) { }
Cart . prototype. items = [ ] ; Todas las instancias encontrarían el mismo array heredado hasta que alguna cree una propiedad propia.
JavaScript
Copiar class Counter {
value = 0 ;
increment = ( ) => {
this . value++ ;
} ;
} increment es una propiedad propia con una arrow function que captura la instancia.
JavaScript
Copiar button. addEventListener (
"click" ,
counter. increment,
) ; JavaScript
Copiar firstCounter. increment === secondCounter. increment; Se crea una función por instancia. Utilízalo cuando el binding automático aporte valor, no como regla general.
JavaScript
Copiar class Rectangle {
constructor ( width, height ) {
this . width = width;
this . height = height;
}
get area ( ) {
return this . width * this . height;
}
set size ( { width, height } ) {
this . width = width;
this . height = height;
}
} JavaScript
Copiar const rectangle = new Rectangle ( 10 , 5 ) ;
rectangle. area;
rectangle. size = { width : 20 , height : 4 } ; Los accessors se definen en el prototipo y utilizan la instancia como this.
No ocultes efectos inesperados o trabajo excesivo detrás de una lectura de propiedad.
JavaScript
Copiar const methodName = "calculateTotal" ;
class Cart {
[ methodName] ( ) {
return 0 ;
}
} La expresión se evalúa cuando se define la clase, no cada vez que se crea una instancia.
Los nombres calculados son útiles para symbols y protocolos.
JavaScript
Copiar class Collection {
* [ Symbol. iterator] ( ) {
yield * this . items;
}
} JavaScript
Copiar class User {
static createGuest ( ) {
return new User ( "Guest" , "viewer" ) ;
}
constructor ( name, role ) {
this . name = name;
this . role = role;
}
} Se llaman sobre la clase.
JavaScript
Copiar User. createGuest ( ) ; No están disponibles en la instancia.
JavaScript
Copiar user. createGuest ( ) ; Campos privados, miembros estáticos y bloques estáticos se profundizan en su propia nota.
JavaScript
Copiar function createEntityClass ( type ) {
return class Entity {
constructor ( id ) {
this . id = id;
this . type = type;
}
} ;
}
const Product = createEntityClass ( "product" ) ;
const Order = createEntityClass ( "order" ) ; Una clase puede retornarse, almacenarse y pasarse como cualquier otro valor.
Aun así, generar clases dinámicamente puede dificultar debugging y tipos. Úsalo cuando exista una necesidad real.
JavaScript
Copiar class Employee {
constructor ( name ) {
this . name = name;
}
describe ( ) {
return this . name;
}
}
class Manager extends Employee {
constructor ( name, team ) {
super ( name) ;
this . team = team;
}
} Una clase derivada debe llamar super() antes de utilizar this en su constructor.
La relación entre herencia, sobrescritura, super y composición se estudia en Herencia y composición .
JavaScript
Copiar class Account {
#balance = 0 ;
deposit ( amount ) {
this . #balance += amount;
}
} #balance es un campo privado del lenguaje, no una convención de nombre.
Se profundiza en Propiedades privadas y estáticas .
JavaScript
Copiar user instanceof User ; instanceof suele comprobar si User.prototype aparece en la cadena de la instancia.
No valida la forma completa ni garantiza que el objeto provenga de datos confiables.
Para datos recibidos desde JSON, una clase no aparece automáticamente.
JavaScript
Copiar const data = JSON . parse (
'{"name":"Nicolás"}' ,
) ;
data instanceof User ; Debes reconstruir la instancia si el dominio lo necesita.
JavaScript
Copiar const user = new User ( data. name, data. role) ; JavaScript
Copiar JSON . stringify ( user) ; JSON serializa propiedades propias enumerables. Los métodos del prototipo no se incluyen.
Los campos privados tampoco se serializan automáticamente.
Una clase puede definir una representación explícita.
JavaScript
Copiar class User {
toJSON ( ) {
return {
name : this . name,
role : this . role,
} ;
}
} Mantén separada la entidad viva de su formato de transporte cuando tengan necesidades distintas.
Usa un objeto literal cuando necesitas una estructura única o una configuración.
JavaScript
Copiar const configuration = {
retryCount : 3 ,
} ; Una clase puede aportar valor cuando:
Se crearán varias instancias con el mismo comportamiento.
Existen invariantes de construcción.
La identidad del tipo es relevante.
Los métodos representan comportamiento del dominio.
Hay estado privado o lifecycle controlado.
No conviertas cada objeto de datos en una clase.
JavaScript
Copiar const order = new Order ( data) ;
Métodos compartidos por prototipo.
instanceof y una identidad de tipo.
Campos privados nativos.
Herencia mediante extends.
JavaScript
Copiar const order = createOrder ( data) ;
Retorno explícito.
Composición flexible.
Closures para encapsulación.
Puede devolver formas distintas.
La elección depende del contrato y del dominio, no de una superioridad absoluta.
JavaScript
Copiar class Product {
#cost;
constructor ( { id, name, cost, price } ) {
if ( ! id) {
throw new TypeError ( "Product ID is required" ) ;
}
if ( ! name?. trim ( ) ) {
throw new TypeError ( "Product name is required" ) ;
}
this . id = id;
this . name = name. trim ( ) ;
this . #cost = cost;
this . price = price;
}
calculateMargin ( ) {
return this . price - this . #cost;
}
rename ( name ) {
if ( ! name?. trim ( ) ) {
throw new TypeError ( "Name is required" ) ;
}
this . name = name. trim ( ) ;
}
} La clase protege invariantes y reúne comportamiento relacionado con el producto.
No realiza red ni persistencia; esas responsabilidades pueden vivir en servicios externos.
Pensar que una clase reemplaza el sistema de prototipos.
Llamar una clase sin new.
Usarla antes de su inicialización.
Creer que los métodos de instancia son propiedades propias.
Extraer un método y esperar que conserve this.
Crear arrow fields para todos los métodos sin considerar memoria e identidad.
Guardar arrays mutables en el prototipo.
Hacer I/O asíncrono dentro del constructor.
Modelar cada objeto JSON como una clase.
Crear jerarquías profundas solo para reutilizar métodos.
Mezclar entidad, persistencia, transporte y UI dentro de una sola clase.
Las clases crean instancias sobre el modelo prototípico.
Requieren new y tienen Temporal Dead Zone.
Los métodos se comparten mediante .prototype.
Los campos públicos son propiedades propias.
Los métodos no están ligados automáticamente.
Los campos arrow conservan this, pero crean una función por instancia.
Getters y setters viven en el prototipo.
Las clases pueden tener miembros estáticos y privados.
No todo objeto necesita convertirse en clase.
Clases y factories son herramientas diferentes.
¿Qué comparaciones son verdaderas?
JavaScript
Copiar class Counter {
value = 0 ;
increment ( ) {
this . value++ ;
}
}
const first = new Counter ( ) ;
const second = new Counter ( ) ; Respuesta first.value !== second.value no compara referencias porque son números, pero cada instancia tiene su propia propiedad value. first.increment === second.increment es true porque el método se comparte mediante Counter.prototype.
Propiedades privadas y estáticas explica qué pertenece a cada instancia, qué pertenece a la clase y cómo funciona la privacidad con #.