Clases en JavaScript

En el último artículo, se introdujeron algunos de los conceptos básicos de la programación orientada a objetos (en-US) (POO), y discutimos un ejemplo donde usamos principios de la POO para modelar los profesores y alumnos de una escuela.

También hablamos acerca de cómo podemos usar prototipos y constructores para implementar un modelo como éste, además, vimos que Javascript también proporciona características que se asemejan más a los conceptos de la POO clásica.

Prerequisitos: Conocimientos básicos de informática, comprensión básica de HTML y CSS, familiaridad con conceptos básicos de Javascript (mira Primeros pasos y Construyendo con bloques) y lo esencial de JSOO (Javascript orientado a objetos)(mira Introducción a los objetos y Programación orientada a objetos (en-US))
Objetivo: Comprender como utilizar las características que Javascript proporciona para implementar aplicaciones usando programación orientada a objetos "clásica".

Clases y constructores

Puedes utilizar la palabra clave class para declarar una clase. A continuación se muestra la declaración de la clase Persona de nuestro artículo anterior.

js
class Person {
  name;

  constructor(name) {
    this.name = name;
  }

  introduceSelf() {
    console.log(`¡Hola!, soy ${this.name}`);
  }
}

Esto declara una clase llamada Person con:

  • una propiedad name.
  • un constructor que recibe un parámetro name que se usa para inicializar la propiedad name del nuevo objeto.
  • un método introduceSelf() que puede hacer referencia a las propiedades del objeto usando this.

La declaración name; es opcional y puedes omitirla, ya que la línea this.name = name; en el constructor crea la propiedad name antes de inicializarla. En cualquier caso, enlistar las propiedades de manera explícita en la declaración de la clase hará que sea más fácil para las personas leyendo tu código saber cuáles son las propiedades que conforman a dicha clase.

Cuando declaras una propiedad, también puedes inicializarla con un valor por defecto con una línea como name = '';.

El constructor se define utilizando la palabra clave constructor. Al igual que un constructor fuera de una clase, se encargará de:

  • crear un nuevo objeto
  • asociar this al nuevo objeto, para que puedas hacer referencia a this dentro de tu código en el constructor
  • ejecutar el código en el constructor
  • retornar el objeto recién creado.

Dada la declaración de clase anterior, puedes crear y utilizar una nueva instancia de Person de la siguiente manera:

js
const gil = new Person("Gil");

gil.introduceSelf(); // ¡Hola!, soy Gil

Observa que para llamar al constructor usamos el nombre de la clase, Person en este ejemplo.

Omitiendo el constructor

En caso de que no necesites algún tipo de inicialización, puedes omitir al constructor, en este caso un constructor por defecto es generado.

js
class Animal {
  sleep() {
    console.log("zzzzzzz");
  }
}

const spot = new Animal();

spot.sleep(); // 'zzzzzzz'

Herencia

A partir de la clase Person, vamos a definir la subclase Professor.

js
class Professor extends Person {
  teaches;

  constructor(name, teaches) {
    super(name);
    this.teaches = teaches;
  }

  introduceSelf() {
    console.log(
      `Mi nombre es ${this.name}, yo seré tu profesor de ${this.teaches}`,
    );
  }

  grade(paper) {
    const grade = Math.floor(Math.random() * (5 - 1) + 1);
    console.log(grade);
  }
}

Para expresar que una clase hereda de otra clase, utilizamos la palabra clave extends.

Debido a que la clase Professor agrega una nueva propiedad teaches, ésta se declara en el cuerpo de la clase.

Cuando creamos un nuevo Professor, queremos establecer el nombre de teaches que enseña, para esto definimos un constructor que recibe name y teaches como argumentos. Lo primero que hace el constructor es utilizar super() para llamar al constructor de la superclase, mientras le pasa el parámetro name. El constructor de la superclase se encarga de establecer el valor de la propiedad name. Enseguida, el constructor de la clase Professor establece el valor de la propiedad teaches.

Nota: Si una subclase debe realizar algún tipo de inialización, es obligatorio que esta llame al constructor de la superclase utilizando super() con los paramétros que el constructor de la superclase espera.

Podemos observar que hemos sobreescrito el método introduceSelf() de la superclase y a su vez agregamos un nuevo método grade() para calificar las tareas de los alumnos (Nuestro profesor no es muy bueno que digamos, solo asigna calificaciones aleatorias a los ensayos).

Con esta declaración ahora podemos crear y usar profesores:

js
const walsh = new Professor("Walsh", "Psicología");
walsh.introduceSelf(); // 'Mi nombre es Walsh, yo seré tu profesor de Psicología'

walsh.grade("mi tarea"); // una calificación aleatoria

Encapsulamiento

Finalmente, veamos cómo implementar el encapsulamiento en JavaScript. En el anterior artículo hablamos de cómo nos gustaría que la propiedad year de Student fuera privada, de esta forma podríamos cambiar las reglas sobre la clase de tiro con arco sin estropear el código que depende de la clase Student.

Aquí podemos ver la declaración de la clase Student que se encarga precisamente de ello:

js
class Student extends Person {
  #year;

  constructor(name, year) {
    super(name);
    this.#year = year;
  }

  introduceSelf() {
    console.log(
      `¡Hola! me llamo ${this.name} y estoy en el año ${this.#year}.`,
    );
  }

  canStudyArchery() {
    return this.#year > 1;
  }
}

En la clase anterior, year es una propiedad de dato privada. Podemos crear un objeto Student que puede acceder a la propiedad #year internamente, sin embargo, si algún código que se encuentre afuera de la clase intenta acceder a la propiedad #year, el navegador lanzará un error:

js
const summers = new Student("Summers", 2);

summers.introduceSelf(); // ¡Hola! me llamo Summers y estoy en el año 2.
summers.canStudyArchery(); // true

summers.#year; // SyntaxError (Error de sintaxis)

Las propiedades de datos privadas deben ser declaradas en la propia declaración de la clase y sus nombres deben empezar con #.

Métodos privados

En un clase puedes incluir tanto propiedades de dato privadas como métodos privados. Al igual que las propiedades de dato privadas, su nombre debe empezar con # y solo pueden ser llamados por los propios métodos del objeto:

js
class Example {
  somePublicMethod() {
    this.#somePrivateMethod();
  }

  #somePrivateMethod() {
    console.log("¿Me llamaste?");
  }
}

const myExample = new Eaxmple();

myExample.somePublicMethod(); // '¿Me llamaste?'

myExample.#somePrivateMethod(); // SyntaxError (Error de Sintaxis)

¡Pon a prueba tus habilidades!

Haz llegado al final de este artículo pero, ¿Aún recuerdas la información más importante? Puedes encontrar retos para asegurarte de retener esta información antes de continuar - véase Pon aprueba tus habilidades: Javascript orientado a objetos (en-US).

Resumen

En este artículo repasamos las herramientas principales que existen en JavaScript para crear programas orientados a objetos. A pesar de que no abarcamos este tema completamente, lo que vimos debería ser suficiente para iniciar. Nuestro artículo sobre clases es un buen lugar para aprender más.