Seleccionar página
class-objectEs posible que sea un ferviente defensor de la programación funcional y considere que las clases son algo absolutamente innecesario; también es posible que considere que la sintáxis de las clases que ahora están disponibles en Javascript es superflua y que es posible implementar prácticamente el mismo comportamiento con funciones, prototipos y los métodos del objeto Object de ES5. Nosotros nos declaramos profundamente agnóticos a las diferentes corrientes y seguimos una especie eclecticismo, incluso sincretismo, donde intentamos aprovechar todo lo que tenemos a nuestro alcance sin entrar en disquisiciones sobre los modelos -más o menos puros- de programación. En cualquier caso, conocer la sintaxis de ES6 para las clases es algo que va a ser interesante para todos. Por ello presentamos a continuación un resumen sintético de lo que ofrecen las clases de ES6. En otros artículos profundizaremos en algunos de estos aspectos.

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)

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

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

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

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.