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

Datos inmutables en Javascript

Datos inmutables en Javascript

En Javascript todo parece mutable, es decir, que se puede cambiar, pero lo cierto es que también nos ofrece varios mecanismos para conseguir que los datos que manejamos, especialmente los objetos, sean inmutables. Te invitamos a descubrir cómo…

Copiar objetos en Javascript

Copiar objetos en Javascript

Copiar objetos no es algo sencillo, incluso se podría decir que en si mismo no es posible, ya que el concepto «copiar» no entra dentro del paradigma de los objetos. No obstante, por medio de instrucciones como Object.assign() hemos aprendido como obtener objetos con las mismas propiedades, pero está técnica no se puede aplicar a todos los tipos de objetos disponibles en Javascript. Vamos a ver cómo podemos copiar cualquier tipo de objeto…

Descubre los Javascript Array Internals

Descubre los Javascript Array Internals

El Array es una de las estructuras más utilizadas en Javascript y no siempre bien comprendida. Hoy os invitamos a analizar el comportamiento interno de este objeto y descubrir cómo Javascript implementa las diferente acciones con los Array y que operaciones internas se realizan en cada caso.

Web Components: pasado, presente y futuro

Web Components: pasado, presente y futuro

Los Web Components aparecieron en el panorama de desarrollo hace ya bastante tiempo. Desde su presentación se les ha prestado mucha atención, pero lo cierto es que no han sido acogidos de forma generalizada, quizás por la difusión de nuevos y potentes frameworks. Nos preguntamos qué ha pasado con este estándar y, sobre todo, que puede pasar de aquí en adelante con el uso práctico de los componentes web.