Menu

Clases en JavaScript: constructor, métodos y new

Cómo funcionan realmente las clases en JavaScript: constructor, métodos, campos de instancia, getters y setters, y el modelo mental detrás de class.

Una clase es el plano de los objetos

Una clase describe la forma y el comportamiento de cierto tipo de objeto. Defines el plano una sola vez y, a partir de ahí, creas tantas instancias como necesites. Cada instancia lleva sus propios datos, pero comparte los mismos métodos con las demás.

La estructura básica es así:

index.js
Output
Click Run to see the output here.

class User { ... } define el molde. Con new User(...) creas una instancia. Cada instancia tiene su propio name y email, pero todas comparten el mismo método greet definido en la clase.

Dos instancias, dos conjuntos de datos, un solo conjunto de métodos. Esa es toda la idea.

El constructor prepara cada instancia

El constructor es un método especial que se ejecuta una única vez por instancia, justo en el momento en que new la crea. Su tarea es inicializar el objeto recién creado, normalmente copiando los argumentos sobre this:

index.js
Output
Click Run to see the output here.

this dentro del constructor hace referencia a la nueva instancia que se está creando. Al asignar this.x = x, colocas x en ese objeto concreto. Cada llamada a new Point(...) recibe su propio this, y por tanto sus propios x e y.

Si no defines un constructor, JavaScript te proporciona uno vacío por defecto. Solo necesitas escribirlo cuando realmente tengas algo que inicializar.

La palabra clave new es la que pone en marcha toda la maquinaria

Llamar a una clase sin new lanza un error:

const p = Point(3, 4);
// TypeError: Class constructor Point cannot be invoked without 'new'

Es algo intencional. new hace cuatro cosas en este orden:

  1. Crea un objeto vacío nuevo.
  2. Lo enlaza con el prototype de la clase (donde viven los métodos).
  3. Ejecuta el constructor con this apuntando al nuevo objeto.
  4. Devuelve ese objeto.

Sin new, no pasa nada de esto. La propia clase impone la regla para que no se te olvide por descuido — una mejora real respecto a los tiempos anteriores a class, cuando olvidar new corrompía el objeto global sin avisar.

Los métodos se comparten, los campos son por instancia

Los métodos definidos dentro del cuerpo de la clase viven en el prototype: hay una única copia compartida por todas las instancias. En cambio, los campos de instancia (todo lo que asignas a this) son independientes, una copia distinta cada vez.

index.js
Output
Click Run to see the output here.

Cada instancia tiene su propio count, así que al incrementar una no se afecta la otra. Pero a.increment y b.increment son literalmente la misma función: está guardada una sola vez en el prototipo y se busca ahí cada vez que la llamas desde una instancia. Por eso las clases escalan tan bien: mil instancias no generan mil copias de cada método.

Campos de clase

Puedes declarar los campos de instancia al principio del cuerpo de la clase, fuera del constructor:

index.js
Output
Click Run to see the output here.

Las declaraciones de campos se ejecutan antes del cuerpo del constructor, como si estuvieran escritas al principio de este. Resultan útiles cuando el valor inicial no depende de los argumentos del constructor: quedan más limpias que llenar el constructor con líneas tipo this.count = 0; this.step = 1;.

Cuando los valores dependen de argumentos, mantén la asignación dentro del constructor, que es donde esos argumentos están disponibles.

Getters y setters en JavaScript

Un getter o setter tiene pinta de método, pero se comporta como si accedieras a una propiedad. Lo lees o le asignas un valor sin paréntesis, y por detrás se ejecuta la función:

index.js
Output
Click Run to see the output here.

Fíjate en cómo se usan: t.fahrenheit (sin paréntesis) dispara el getter, y t.fahrenheit = 100 dispara el setter. Desde fuera parece una propiedad normal y corriente, pero en realidad se calcula al vuelo a partir de celsius.

Los getters vienen muy bien para valores derivados. Los setters, para validar o normalizar un valor al asignarlo. Eso sí, no abuses de ellos: si un getter hace mucho trabajo interno, quien lea tu código se va a llevar una sorpresa al ver que un simple "acceso a una propiedad" está ejecutando lógica pesada.

Métodos abreviados, nombres computados y this

Los métodos dentro de una clase se declaran con sintaxis abreviada: sin la palabra clave function ni : entre el nombre y el cuerpo:

index.js
Output
Click Run to see the output here.

Devolver this desde add es lo que habilita el encadenamiento de métodos: cada llamada te devuelve la misma instancia para que el siguiente método opere sobre ella.

Ojo con un detalle: los métodos no quedan ligados automáticamente a su instancia. Si extraes un método y lo invocas por su cuenta, pierdes el this:

index.js
Output
Click Run to see the output here.

Al llamar a g.hello() funciona porque el . provee el this. Al invocar fn() por su cuenta, no. Si necesitas un método desligado y ya vinculado (algo muy típico en manejadores de eventos), haz el bind dentro del constructor o usa un campo de clase con arrow function: hello = () => \Hola, ${this.name}`;`.

Ejemplo de clase en JavaScript: todo junto

Juntemos las piezas — campos, constructor, métodos y un getter:

index.js
Output
Click Run to see the output here.

Se lee a simple vista: la clase deja claro qué es una BankAccount y qué puede hacer.

Las clases son funciones, en realidad

Algo que conviene interiorizar: class es, en gran parte, azúcar sintáctico. Por debajo, una clase es una función —concretamente, una función constructora— y sus métodos viven en ClassName.prototype:

index.js
Output
Click Run to see the output here.

typeof User devuelve "function". El método greet vive en User.prototype. Las instancias acceden a greet recorriendo la cadena de prototipos, el mismo mecanismo que existe en el lenguaje desde el primer día. Las clases simplemente te dan una sintaxis más limpia para montarlo.

Tener claro este modelo mental te servirá más adelante: la herencia, instanceof y la depuración de cadenas de prototipos cobran mucho más sentido cuando recuerdas que una clase no es más que una función con métodos colgando de su prototipo.

Lo que viene: herencia

Hasta ahora cada clase va por su cuenta. Pero en la práctica casi siempre acabamos construyendo jerarquías de clases: un Dog que es un tipo de Animal, o un AdminUser que es un tipo de User. Las palabras clave extends y super son las que se encargan de eso, y las veremos a continuación.

Preguntas frecuentes

¿Cómo se crea una clase en JavaScript?

Usa la palabra clave class seguida de un nombre y, dentro de las llaves, define un método constructor y el resto de métodos que necesites. Para crear una instancia, llama a la clase con new ClassName(...). Por ejemplo: class User { constructor(name) { this.name = name; } } y luego new User('Ada').

¿Qué hace el constructor en una clase de JavaScript?

El constructor se ejecuta una sola vez, justo cuando haces new ClassName(...). Su trabajo es preparar la nueva instancia, normalmente asignando los argumentos a this como propiedades. Si no escribes uno, JavaScript te pone un constructor vacío por defecto de forma automática.

¿Cuál es la diferencia entre una clase y una función en JavaScript?

Por debajo, una clase no deja de ser una función: concretamente una función constructora con sus métodos colgados del prototipo. La palabra clave class es básicamente azúcar sintáctico, pero obliga a llamarla con new, hace que extends funcione de forma limpia y convierte los métodos en no enumerables. Para código nuevo, es mejor usar class que las viejas funciones constructoras hechas a mano.

Aprende a programar con Coddy

COMENZAR