EcmaScript 6

La mayoría de lenguajes de programación orientados a objetos disponen de la posibilidad de definir la estructura de una clase, sin embargo, JavaScript hasta ahora, permitía trabajar con objetos, pero no ofrecía una forma práctica de definir las clases, más allá de crearlas como una función.

Veamos todo esto mejor con un ejemplo:

function Customer(customerName, customerId) {
  let name = customerName;
  let id = customerId;
  let invoices = [];
  
  this.addInvoice = function(newInvoice) {
    invoices.push(newInvoice);
  }

  this.getName = function() {
    return name;
  }
};

const customer = new Customer("John Doe", 123);

console.log(customer.getName()); // 'John Doe'
console.log(customer.name);      // undefined

Con el código anterior podemos usar una función como si fuera una clase. Uno de los trucos para tener los atributos como privados, es declararlos sin "this". De esta forma, no se verán de forma externa. El problema viene cuando se quiere por ejemplo, utilizarlos en métodos añadidos a posteriori usando por ejemplo:

Customer.prototype.printInvoice = function(index) {
  // Esta línea provocará un error: "Uncaught ReferenceError: invoices is not defined"
  console.log(invoices[index]);
}

Lo que nos llevará a tener que crear getters y setters para que métodos que la propia clase puedan acceder a los atributos, algo que queda un poco confuso.

Lo anterior unido a otros problemas, como un sistema raro de herencia, la propia nomenclatura de saber que se define una clase, pero se utiliza la palabra function, etc. Ha hecho que el lenguaje haya evolucionado hasta disponer en la versión 6 de EcmaScript (recordemos que es el nombre que se usa para el estándar definido de JavaScript), de una definición de clases más ajustada a otros lenguajes.

Veamos la clase anterior definida con ES6:

class Customer {
  #name;
  #id;
  #invoices = [];

  constructor(name, id) {
    this.#name = name;
    this.#id = id;
  }
  
  addInvoice(newInvoice) {
    this.#invoices.push(newInvoice);
  }

  get Name() {
    return this.#name;
  }
};

const customer = new Customer("John Doe", 123);

// 'John Doe'
console.log(customer.Name);
// Uncaught SyntaxError: Private field '#name' must be declared in an enclosing class
console.log(customer.#name);

Pasemos a desgranar un poco las líneas de código:

class Customer

De esta forma se definen ahora las clases en ES6. Es una sintaxis mucho más próxima al resto de lenguajes como Java, C++, C#, Python, etc.

#name;
#id;
#invoices = [];

Las tres líneas de arriba, definen los atributos de la clase. Podréis observar como tienen prefijado un símbolo "#". Esto indica que el atributo (o también el método), es privado. Lo que permite que éste no sea visible de forma externa. Por desgracia aún no existe el concepto de "protected" en JavaScript, de forma que no podemos hacer que métodos o atributos no sean visibles al exterior pero sí a clases hijas. Por lo que a día de hoy, o un elemento es visible al exterior o no es visible para nadie.

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

Estas sentencias definen el constructor que tendrá la clase. Al contrario de lo que sucede con otros lenguajes, en JavaScript no podemos tener sobrecarga de constructores, por lo que sólo podemos definir uno. Si tuviéramos varios, se lanzará un error:

class Test {
  constructor(first) {
    console.log(first);
  }
  constructor(first, second) {
    console.log(first, second);
  }
}
// El código anterior provocará este error: Uncaught SyntaxError: A class may only have one constructor
addInvoice(newInvoice) {
  this.#invoices.push(newInvoice);
}

En el fragmento anterior se puede observar la definición de un método, en este caso público (no lleva el símbolo "#"), llamado addInvoice. Aquí podemos ver también que para acceder a atributos y métodos privados, tenemos que incluir el almohadilla en su nombre.

get Name() {
  return this.#name;
}

Aquí tenemos también otra nomenclatura nueva. Ésta se utiliza para definir getters y setters con un estilo parecido a las propiedades en C#. Es decir, podemos llamarlas desde el exterior como si fueran atributos y no métodos. Veremos su uso unas pocas líneas más adelante.

const customer = new Customer("John Doe", 123);

A nivel de instanciación de la clase, podemos observar como no cambia nada respecto a cuando usamos una función para instanciarla.

console.log(customer.Name);

Si recordáis antes teníamos definido un método getter, pues esta sentencia muestra su uso. Para ello se llama sin los paréntesis, como su fuera un atributo público. Si por ejemplo hubiésemos tenido un setter, este sería su uso:

class Customer {
  ...
  set Name(newName) {
    this.#name = newName;
  }
}

customer.Name = "Pepito Doe";
console.log(customer.#name);

Finalmente, para esta línea vemos que se produce un error. Este es lanzado, ya el intérprete de JavaScript detecta que #name es privado (por su prefijo), y nos advierte que no podemos usarlo de forma externa a la clase.

Y hasta aquí esta primera introducción al nuevo sistema de definición de clase con JavaScript. Seguiremos poco a poco introduciendo los conceptos de herencia, algunos métodos útiles que podemos sobre-escribir de Object, etc.

Espero que os haya podido servir, y como siempre, podéis dejar vuestras dudas, anotaciones, etc, en los comentarios.


Enlaces

Comparte este artículo con quien quieras
Comunicando pestañas del navegador con localStorage
Orientación a objetos con JavaScript (ES6). Parte 2. Herencia

Leave a Comment

Your email address will not be published. Required fields are marked *

Este sitio usa Akismet para reducir el spam. Aprende cómo se procesan los datos de tus comentarios.