Sintáxis básica
Para crear una clase en ES6 vamos a utilizar la palabra reservada class, en el modo «use strict», de esta forma:
"use stric"; class Person { constructor(firstname, lastname, birthday) { this.firstname = firstname; this.lastname = lastname; this.birthday = new Date(birthday); } getAge() { var ageDifMs = Date.now() - this.birthday.getTime(); var ageDate = new Date(ageDifMs); return Math.abs(ageDate.getUTCFullYear() - 1970); } }
typeof de una clase
Si comprobamos el tipo de Person nos vamos a encontrar que typeof devuelve function, pero a diferencia de las funciones, no vamos a poder llamar directamente a Person y siempre vamos a tener que utilizar new:
// Comprobamos el tipo de Person // Devuelve function console.log(typeof Person); // Intentamos llamar a Person como una función // TypeError: Class constructors cannot be invoked without 'new' Person('Pablo', 'Almunia', '07-08-1966'); // Creamos un objeto con new var p = new Person('Pablo', 'Almunia', '07-08-1966');
Definición y uso
A diferencia de las funciones, las clases no pueden ser utilizadas antes de ser definidas. Las funciones en Javascript sufren un proceso denominado hoisting por el que las variables declaradas con var y las funciones son desplazadas al principio del alcance donde se encuentra y como consecuencia pueden ser llamadas antes de su declaración. En el caso de las clases esto no funciona así y si intentamos utilizar una clase antes de definirla obtendremos un error:
"use strict"; // Producirá un error, ya que estamos usando una clase antes de declararla // ReferenceError: Person is not defined var p = new Person('Pablo', 'Almunia', '07-08-1966'); class Person { constructor(firstname, lastname, birthday) { this.firstname = firstname; this.lastname = lastname; this.birthday = new Date(birthday); } get age() { var ageDifMs = Date.now() - this.birthday.getTime(); var ageDate = new Date(ageDifMs); return Math.abs(ageDate.getUTCFullYear() - 1970); } }
Expresiones de clases
De forma similar a las funciones, es posible asignar la definición de una clase a una variable. A diferencia de las funciones, no se aceptan clases anónimas, pero al igual que en caso de las funciones, el nombre de la clase queda oculto y sólo es posible utilizar el nombre de la variable que se ha utilizado en la asignación:
"use strict"; const People = class Person { constructor(firstname, lastname, birthday) { this.firstname = firstname; this.lastname = lastname; this.birthday = new Date(birthday); } getClassName() { return Person.name; } }; var p = new People(); console.log(p.getClassName()); var p2 = new Person();
Constructor
Dentro de la definición de la clase no es posible escribir código directamente, ya que como tal no se comporta como una función, lo que vamos a encontrar es la definición de métodos. Uno de estos métodos se comporta de una manera especial: constructor. Esta función será llamada cuando se instancie un objeto de esta clase por medio de new. Este pseudométodo tiene algunas características singulares, como que representa la función de la propia clase:
// Es verdadero console.log(Person === Person.prototype.constructor);
El constructor no tiene por qué devolver un objeto, de forma implícita devuelve el objeto this, pero podemos devolver lo que queramos y por lo tanto podemos hacer que haga cosas bastante curiosas sobrescribiendo el resultado de llamar a new:
"use strict"; class Person { constructor(firstname, lastname, birthday) { return { lastname, firstname, birthday: new Date(birthday) }; } getAge() { var ageDifMs = Date.now() - this.birthday.getTime(); var ageDate = new Date(ageDifMs); return Math.abs(ageDate.getUTCFullYear() - 1970); } } // Creamos un objeto var p = new Person('Pablo', 'Almunia', '07-08-1966'); // Podemos llamar a las propiedades del objeto devuelto por el constructor console.log(p.firstname, p.lastname); // No podemos llamar el método getAge(), ya que no está disponible en el objeto // devuelto por el constructor, aunque exista en la definición de la clase console.log(p.getAge());
Si no se define ningún método constructor se utilizará un constructor por defecto.
Métodos
El resto de métodos se definen en el cuerpo de la clase sin necesidad de utilizar function, basta con poner el nombre del método y paréntesis con los parámetros que recibirá (o ninguno) y escribir el cuerpo de la función.
"use strict"; class Person { constructor(firstname, lastname, birthday) { this.firstname = firstname; this.lastname = lastname; this.birthday = new Date(birthday); } getAge() { var ageDifMs = Date.now() - this.birthday.getTime(); var ageDate = new Date(ageDifMs); return Math.abs(ageDate.getUTCFullYear() - 1970); } toString() { return this.firstname + ' ' + this.lastname + ' (' + this.getAge() + ')'; } } var p = new Person('Pablo', 'Almunia', '07-08-1966'); // Produce una llamada implícita a toString() console.log('Person: ' + p);
Métodos estáticos
Si lo necesitamos podemos escribir métodos estáticos, es decir, que pueden ser invocados desde la clase sin necesidad de que se cree una instancia de la misma. Para ello tenemos que poner la palabra static antes del nombre del método:
"use strict"; class Person { constructor(firstname, lastname, birthday) { this.firstname = firstname; this.lastname = lastname; this.birthday = new Date(birthday); } static getClassName() { return 'Person class'; } } // Devuelve "Person class" console.log(Person.getClassName());
Propiedades
No es posible definir propiedades en el cuerpo de una clase. Para poderlas definir podemos utilizar el constructor (o desde cualquier otro método), tal y como hemos visto en el primer ejemplo, donde se definen las propiedades firstname, lastname y birthday por medio de this.
Una alternativa a esta forma de crear las propiedades es utilizar métodos get y/o set para definir dos funciones que nos sirvan para gestionar su acceso:
"use strict"; class Person { constructor(firstname, lastname, birthday) { this.firstname = firstname; this.lastname = lastname; this.birthday = new Date(birthday); } get age() { var ageDifMs = Date.now() - this.birthday.getTime(); var ageDate = new Date(ageDifMs); return Math.abs(ageDate.getUTCFullYear() - 1970); } } var p = new Person('Pablo', 'Almunia', '07-08-1966'); console.log(p.firstname, p.lastname); console.log(p.age);
Los métodos get y set también pueden ser estáticos si les ponemos static antes de su definición.
Herencia
Aunque en ES5 podemos utilizar la cadena de prototipos para realizar una herencia, con la sintaxis de clases de ES6 esto es mucho más sencillo y claro. Para ello debemos utilizar extends en la declaración de la clase:
"use strict"; class Person { constructor(firstname, lastname, birthday) { this.firstname = firstname; this.lastname = lastname; this.birthday = new Date(birthday); } get age() { var ageDifMs = Date.now() - this.birthday.getTime(); var ageDate = new Date(ageDifMs); return Math.abs(ageDate.getUTCFullYear() - 1970); } } class Employee extends Person { constructor(firstname, lastname, birthday, position) { super(firstname, lastname, birthday); this.position = position; } } // Creamos un objeto var p = new Employee('Pablo', 'Almunia', '07-08-1966', 'Architect'); // Consultamos sus propiedades console.log(p.firstname, p.lastname, p.position); console.log(p.age);
super
Para que un constructor o un método puedan llamar a miembros de la clase padre debe utilizar super que es una referencia a la clase superior. En el caso del constructor se le llamará simplemente con los parámetros, en el caso de querer llamara métodos o propiedades concretas usaremos super.nombredelmiembro.
"use strict"; class Person { constructor(firstname, lastname, birthday) { this.firstname = firstname; this.lastname = lastname; this.birthday = new Date(birthday); } get age() { var ageDifMs = Date.now() - this.birthday.getTime(); var ageDate = new Date(ageDifMs); return Math.abs(ageDate.getUTCFullYear() - 1970); } } class Employee extends Person { constructor(firstname, lastname, birthday, position) { super(firstname, lastname, birthday); this.position = position; } get yearsUntilRetirement() { return 67 - super.age; } } // Creamos un objeto var p = new Employee('Pablo', 'Almunia', '07-08-1966', 'Architect'); // Consultamos sus propiedades console.log(p.firstname, p.lastname, p.position); console.log(p.yearsUntilRetirement);
Constructor por defecto
Si la clase hija no dispone de constructor, se incluye un constructor por defecto que llama al constructor de la clase padre con los parámetros que se hayan pasado al invocar a new:
"use strict"; class Person { constructor(firstname, lastname, birthday) { this.firstname = firstname; this.lastname = lastname; this.birthday = new Date(birthday); } get age() { var ageDifMs = Date.now() - this.birthday.getTime(); var ageDate = new Date(ageDifMs); return Math.abs(ageDate.getUTCFullYear() - 1970); } } class Unemployed extends Person { get yearsUntilRetirement() { return 67 - super.age; } } // Creamos un objeto var p = new Unemployed('Pablo', 'Almunia', '07-08-1966'); // Consultamos sus propiedades console.log(p.firstname, p.lastname, p.position); console.log(p.yearsUntilRetirement);
Generadores e iterables
Las clases pueden tener métodos generadores, simplemente hay que utilizar el asterisco (*) antes del nombre del método y será un generador.
De esta forma podemos hacer que nuestros objetos sean iterables, es decir, que puedan ser utilizados por medio de for of y devolver las propiedades del objeto en cada llamada:
"use strict"; class Employee { constructor() { this.positions = []; for (let n in arguments) { this.positions.push(arguments[n]); } } * [Symbol.iterator]() { for (let pos of this.positions) { yield pos; } } } // Creamos un objeto var p = new Employee('Developer', 'Analyst', 'Architec'); // Iteramos sus elementos for (let pos of p) { console.log(pos); }
Como se puede observar, se ha utilizado Symbol.iterator entre corchetes como nombre de la función generadora. En primer lugar hay que comentar que el uso de corchetes permite definir nombres de métodos calculados. En segundo lugar, Symbol.iterator nos permite indicar que la clase es iterable de forma sencilla.
Conclusión
Aunque hay algunos aspectos de las clases que trataremos posteriormente, la verdad es que su sintaxis es bastante sencilla. Aunque podamos discutir su utilidad o la conveniencia de su uso, lo cierto es que simplifican el uso de los objetos y sus prototipos que podemos hacer en ES5. Ciertamente no hay nada que no pudiéramos hacer ya, pero estamos seguros que esta forma de definir los objetos es, en la práctica, clara y fácil de manejar.
Novedades
HTTP2 para programadores. Enviar mensajes del servidor al cliente con Server Sent Event (sin WebSockets)
En esta charla, organizada por MadridJS, Pablo Almunia nos muestra cómo la mayoría de nosotros cuando oímos hablar por primera vez de HTTP2 nos ilusionamos con las posibilidades que presumiblemente se abrían para el desarrollo de soluciones web avanzadas y cómo muchos nos sentimos defraudados con lo que realmente se podía implementar.
En esta charla podemos ver cómo funciona el HTTP2, que debemos tener en cuenta en el servidor para hace uso de este protocolo y, sobre todo, cómo podemos enviar información desde el servidor al cliente de forma efectiva y fácil. Veremos con detenimiento cómo por medio de los Server-Sent Events (SSE) podemos recibir en el cliente datos enviados desde el servidor sin utilizar websocket, simplificando enormemente la construcción de aplicaciones con comunicación bidireccional.
Observables en Javascript con Proxies
En esta charla, organizada por MadridJS, Pablo Almunia nos habla de la observación reactiva de objetos en Javascript por medio de Proxy. Se describe paso a paso cómo funcionan los Proxies y en que casos pueden ser nuestro mejor aliado. Veremos que no hay que tenerles miedo, son bastante sencillos de utilizar, y nos ofrecen una gran abanico de posibilidades.
Aplicaciones JAMStack, SEO friendly y escalables con NextJS
En esta charla de Madrid JS, Rafael Ventura nos describe las funcionalidades clave de NextJS, nos muestra en vivo cómo desarrollar una completa aplicación JAMStack con Server Side Rendering (SSR) y Static Site Generation (SSG) y termina mostrando como publicar esta aplicación en Vercel.
Stencil JS: mejora el Time To Market de tu producto, por Rubén Aguilera
En esta charla Rubén Aguilera nos cuenta los problemas que tienen muchas empresas a la hora de sacar productos accesibles, vistosos y usables en el Time To Market que requiere Negocio y cómo podemos minimizar este tiempo gracias al DevUI con StencilJS para adecuar una aplicación de Angular a las exigencias del mercado en tiempo record.